From 56541301b9116e42c45357618fa74b79fc86d94f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:37:07 +0000 Subject: [PATCH 01/12] Initial plan From 2c61cbf64d9aa6751dab00389d66c6f9bb4b4e64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:51:25 +0000 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E9=80=B2=E6=8D=97=E5=88=97?= =?UTF-8?q?=E3=81=AE=E7=B7=A8=E9=9B=86=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit タスクテーブルに進捗列の表示と編集を追加し、step=5のcommit正規化を実装する。 opt-in表示を維持し、READMEとexampleの利用例を更新する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- README.md | 26 ++++ example/package-lock.json | 127 ++++++------------ example/src/App.tsx | 18 ++- package-lock.json | 18 --- src/components/task-list/overlay-editor.tsx | 9 ++ src/components/task-list/task-list-header.tsx | 1 + src/components/task-list/task-list-table.tsx | 4 + src/components/task-list/task-list.tsx | 29 +++- src/helpers/task-helper.ts | 27 ++++ src/test/overlay-editor.test.tsx | 42 ++++++ src/test/task-helper.test.tsx | 24 ++++ src/test/task-list-commit.test.tsx | 53 +++++++- src/test/task-list-table-editing.test.tsx | 14 ++ src/types/public-types.ts | 1 + 14 files changed, 285 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 1b600fc49..26f270667 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,32 @@ const calendarConfig: CalendarConfig = { - TaskListHeader: `React.FC<{ headerHeight: number; rowWidth: string; fontFamily: string; fontSize: string;}>;` - TaskListTable: `React.FC<{ rowHeight: number; rowWidth: string; fontFamily: string; fontSize: string; locale: string; tasks: Task[]; selectedTaskId: string; setSelectedTask: (taskId: string) => void; }>;` +### TaskList 列の表示制御(visibleFields) + +TaskList の表示列は `visibleFields` で指定します。`DEFAULT_VISIBLE_FIELDS` に progress 列は含まれないため、**進捗列は opt-in** です。 + +```typescript +import { DEFAULT_VISIBLE_FIELDS, VisibleField } from "@levelcaptech/gantt-task-react-custom"; + +const visibleFields: VisibleField[] = [ + "name", + "start", + "end", + "progress", + ...DEFAULT_VISIBLE_FIELDS.filter(field => + !["name", "start", "end"].includes(field) + ), +]; + +; +``` + +progress セルの編集は `step=5` で確定し、`onCellCommit` には `"0"〜"100"` の文字列が通知されます。 + ### Task | パラメーター名 | 型 | 説明 | diff --git a/example/package-lock.json b/example/package-lock.json index b2f250adb..c7bded69a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -23,6 +23,46 @@ "cross-env": "^7.0.3" } }, + "..": { + "name": "@levelcaptech/gantt-task-react-custom", + "version": "0.5.0", + "license": "MIT", + "devDependencies": { + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^7.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^14.2.1", + "@types/classnames": "^2.3.0", + "@types/jest": "^27.5.1", + "@types/node": "^15.0.1", + "@types/react": "^18.0.5", + "@types/react-dom": "^18.0.5", + "cross-env": "^7.0.3", + "gh-pages": "^3.1.0", + "microbundle-crl": "^0.13.11", + "mini-css-extract-plugin": "^2.5.1", + "npm-run-all": "^4.1.5", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.6.0", + "prettier": "^2.7.1", + "react-scripts": "^5.0.1", + "typescript": "^4.7.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^7.0.0", + "@dnd-kit/utilities": "^3.2.2", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "license": "MIT" @@ -2073,63 +2113,6 @@ "postcss-selector-parser": "^6.0.10" } }, - "node_modules/@dnd-kit/accessibility": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", - "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/core": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", - "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@dnd-kit/accessibility": "^3.1.1", - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/sortable": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", - "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@dnd-kit/utilities": "^3.2.0", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.0.7", - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/utilities": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", - "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "license": "MIT", @@ -3056,19 +3039,8 @@ "license": "MIT" }, "node_modules/@levelcaptech/gantt-task-react-custom": { - "version": "0.1.0", - "resolved": "file:..", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^7.0.0", - "@dnd-kit/utilities": "^3.2.2", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } + "resolved": "..", + "link": true }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", @@ -15314,21 +15286,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.2", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tapable": { "version": "2.3.0", "license": "MIT", diff --git a/example/src/App.tsx b/example/src/App.tsx index 2c929b468..cc8157f92 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,6 +100,16 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; +const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ + "name", + "start", + "end", + "progress", + ...DEFAULT_VISIBLE_FIELDS.filter(field => + !["name", "start", "end"].includes(field) + ), +]; + const resolveCellCommitValue = ( columnId: VisibleField, value: string, @@ -118,6 +128,10 @@ const resolveCellCommitValue = ( const parsedNumber = Number(value); return Number.isNaN(parsedNumber) ? fallbackValue : parsedNumber; } + case "progress": { + const parsedNumber = Number(value); + return Number.isNaN(parsedNumber) ? fallbackValue : parsedNumber; + } default: return value; } @@ -265,7 +279,7 @@ const App = () => { locale="ja-JP" calendar={calendarConfig} TooltipContent={JapaneseTooltip} - visibleFields={DEFAULT_VISIBLE_FIELDS} + visibleFields={VISIBLE_FIELDS_WITH_PROGRESS} onTaskUpdate={handleTaskUpdate} onCellCommit={handleCellCommit} effortDisplayUnit={effortUnit} @@ -287,7 +301,7 @@ const App = () => { locale="ja-JP" calendar={calendarConfig} TooltipContent={JapaneseTooltip} - visibleFields={DEFAULT_VISIBLE_FIELDS} + visibleFields={VISIBLE_FIELDS_WITH_PROGRESS} onTaskUpdate={handleTaskUpdate} onCellCommit={handleCellCommit} effortDisplayUnit={effortUnit} diff --git a/package-lock.json b/package-lock.json index b20b53248..303148f1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21826,24 +21826,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", diff --git a/src/components/task-list/overlay-editor.tsx b/src/components/task-list/overlay-editor.tsx index 6b93d6dab..994568996 100644 --- a/src/components/task-list/overlay-editor.tsx +++ b/src/components/task-list/overlay-editor.tsx @@ -47,6 +47,7 @@ const resolveOverlayInputType = ( return "date"; case "plannedEffort": case "actualEffort": + case "progress": return "number"; case "process": case "status": @@ -116,6 +117,13 @@ export const OverlayEditor: React.FC = ({ } return []; }, [editingState.columnId]); + const numberInputAttributes = useMemo>( + () => + editingState.columnId === "progress" + ? { min: 0, max: 100, step: 5 } + : {}, + [editingState.columnId] + ); const portalRoot = useMemo(() => { if (typeof document === "undefined") { @@ -416,6 +424,7 @@ export const OverlayEditor: React.FC = ({ defaultValue={defaultValueRef.current} style={{ height: "100%" }} ref={handleInputElementRef} + {...(inputType === "number" ? numberInputAttributes : {})} readOnly={editingState.pending} onKeyDown={handleKeyDown} onBlur={handleBlur} diff --git a/src/components/task-list/task-list-header.tsx b/src/components/task-list/task-list-header.tsx index bb8bc3440..6fe50b746 100644 --- a/src/components/task-list/task-list-header.tsx +++ b/src/components/task-list/task-list-header.tsx @@ -46,6 +46,7 @@ export const TaskListHeaderDefault: React.FC<{ name: "タスク名", start: "開始日", end: "終了日", + progress: "進捗", process: "工程", assignee: "担当者", plannedStart: "予定開始", diff --git a/src/components/task-list/task-list-table.tsx b/src/components/task-list/task-list-table.tsx index efcbdb0e8..fa547d3a3 100644 --- a/src/components/task-list/task-list-table.tsx +++ b/src/components/task-list/task-list-table.tsx @@ -5,6 +5,7 @@ import { getDefaultWidth, TaskListEditingStateContext } from "./task-list"; import { formatDate, formatEffort, + formatProgress, getStatusBadgeText, getStatusColor, normalizeProcess, @@ -57,6 +58,7 @@ export const TaskListTableDefault: React.FC<{ "name", "start", "end", + "progress", "process", "assignee", "plannedStart", @@ -273,6 +275,8 @@ export const TaskListTableDefault: React.FC<{ return {formatDate(t.start)}; case "end": return {formatDate(t.end)}; + case "progress": + return {formatProgress(t.progress)}; case "process": return {normalizeProcess(t.process)}; case "assignee": diff --git a/src/components/task-list/task-list.tsx b/src/components/task-list/task-list.tsx index f02c8bf32..67d3689fc 100644 --- a/src/components/task-list/task-list.tsx +++ b/src/components/task-list/task-list.tsx @@ -22,6 +22,7 @@ import { import { formatDate, parseDateFromInput, + parseProgressInput, sanitizeEffortInput, } from "../../helpers/task-helper"; import { ParsedTime, parseTimeString } from "../../helpers/time-helper"; @@ -306,6 +307,16 @@ export const TaskList: React.FC = ({ const rowId = editingState.rowId; const columnId = editingState.columnId; const task = tasks.find(row => row.id === rowId); + const resolveProgressCommit = () => { + if (columnId !== "progress") { + return null; + } + const parsedValue = parseProgressInput(value); + if (parsedValue === null) { + return { invalid: true, normalizedValue: null }; + } + return { invalid: false, normalizedValue: `${parsedValue}` }; + }; const resolveActualsCommit = () => { if (!task) { return null; @@ -364,6 +375,21 @@ export const TaskList: React.FC = ({ updatedFields: Object.keys(updatedFields).length > 0 ? updatedFields : null, }; }; + const progressCommit = resolveProgressCommit(); + if (progressCommit?.invalid) { + setEditingState(prev => { + if ( + prev.mode !== "editing" || + prev.pending || + prev.rowId !== rowId || + prev.columnId !== columnId + ) { + return prev; + } + return { ...prev, errorMessage: "0〜100 の数値を入力してください" }; + }); + return; + } const actualsCommit = resolveActualsCommit(); setEditingState(prev => { if ( @@ -377,7 +403,8 @@ export const TaskList: React.FC = ({ return { ...prev, pending: true, errorMessage: null }; }); try { - const commitValue = actualsCommit?.normalizedValue ?? value; + const commitValue = + progressCommit?.normalizedValue ?? actualsCommit?.normalizedValue ?? value; await onCellCommit({ rowId, columnId, value: commitValue, trigger }); if (actualsCommit?.updatedFields && onUpdateTask) { onUpdateTask(rowId, actualsCommit.updatedFields); diff --git a/src/helpers/task-helper.ts b/src/helpers/task-helper.ts index 83f6c7cb5..1f8ab45e1 100644 --- a/src/helpers/task-helper.ts +++ b/src/helpers/task-helper.ts @@ -76,6 +76,33 @@ export const sanitizeEffortInput = (value: string) => { return parsed; }; +const clampProgress = (value: number) => Math.min(100, Math.max(0, value)); + +export const normalizeProgress = (progress?: number) => { + if (progress === undefined || progress === null || !Number.isFinite(progress)) { + return null; + } + const rounded = Math.round(progress / 5) * 5; + return clampProgress(rounded); +}; + +export const formatProgress = (progress?: number): string => { + const normalized = normalizeProgress(progress); + return normalized === null ? "" : `${normalized}`; +}; + +export const parseProgressInput = (value: string) => { + if (value.trim() === "") { + return null; + } + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + return null; + } + const rounded = Math.round(parsed / 5) * 5; + return clampProgress(rounded); +}; + const DEFAULT_TASK_PROCESS: TaskProcess = ( TASK_PROCESS_OPTIONS.includes("その他") ? "その他" diff --git a/src/test/overlay-editor.test.tsx b/src/test/overlay-editor.test.tsx index 5d074a379..0f8e07593 100644 --- a/src/test/overlay-editor.test.tsx +++ b/src/test/overlay-editor.test.tsx @@ -164,6 +164,7 @@ describe("OverlayEditor", () => { ["name", "INPUT", "text", "タスク名"], ["start", "INPUT", "date", "2026-01-01"], ["plannedEffort", "INPUT", "number", 8], + ["progress", "INPUT", "number", 45], ["process", "SELECT", "", "レビュー"], ["status", "SELECT", "", "完了"], ])( @@ -211,6 +212,47 @@ describe("OverlayEditor", () => { } ); + it("sets progress input constraints", async () => { + const rectSpy = jest + .spyOn(HTMLElement.prototype, "getBoundingClientRect") + .mockReturnValue(rect as DOMRect); + const rafSpy = jest + .spyOn(window, "requestAnimationFrame") + .mockImplementation(callback => { + callback(0); + return 1; + }); + const { taskListRef, headerRef, bodyRef } = createRefs(); + + render( +
+
+
+
+ 45 +
+
+ +
+ ); + + const overlayInput = await screen.findByTestId("overlay-editor-input"); + + expect(overlayInput).toHaveAttribute("min", "0"); + expect(overlayInput).toHaveAttribute("max", "100"); + expect(overlayInput).toHaveAttribute("step", "5"); + + rectSpy.mockRestore(); + rafSpy.mockRestore(); + }); + it("focuses the input when editing starts", async () => { const rectSpy = jest .spyOn(HTMLElement.prototype, "getBoundingClientRect") diff --git a/src/test/task-helper.test.tsx b/src/test/task-helper.test.tsx index 2f93f3345..a20d18523 100644 --- a/src/test/task-helper.test.tsx +++ b/src/test/task-helper.test.tsx @@ -3,6 +3,8 @@ import { parseDateFromInput, formatEffort, sanitizeEffortInput, + formatProgress, + parseProgressInput, normalizeProcess, normalizeStatus, getStatusBadgeText, @@ -67,6 +69,28 @@ describe("task-helper sanitizeEffortInput", () => { }); }); +describe("task-helper progress helpers", () => { + it("formats progress with 5-step rounding and clamp", () => { + expect(formatProgress(42)).toBe("40"); + expect(formatProgress(101)).toBe("100"); + expect(formatProgress(-3)).toBe("0"); + }); + + it("returns empty string when progress is invalid", () => { + expect(formatProgress(Number.NaN)).toBe(""); + }); + + it("parses progress input with rounding", () => { + expect(parseProgressInput("47")).toBe(45); + expect(parseProgressInput("100")).toBe(100); + }); + + it("rejects invalid progress input", () => { + expect(parseProgressInput("")).toBeNull(); + expect(parseProgressInput("abc")).toBeNull(); + }); +}); + describe("task-helper normalize helpers", () => { it("normalizes process to defined options", () => { expect(normalizeProcess("設計")).toBe("設計"); diff --git a/src/test/task-list-commit.test.tsx b/src/test/task-list-commit.test.tsx index 486af7ed3..50f9a22c9 100644 --- a/src/test/task-list-commit.test.tsx +++ b/src/test/task-list-commit.test.tsx @@ -28,6 +28,12 @@ const MockTaskListTable: React.FC = () => { > Start Effort +
Task 1
@@ -37,6 +43,9 @@ const MockTaskListTable: React.FC = () => {
1
+
+ 50 +
); }; @@ -54,7 +63,8 @@ const createTask = (overrides: Partial = {}): Task => ({ const renderTaskList = ( onCellCommit: jest.Mock, onUpdateTask?: jest.Mock, - tasks: Task[] = [createTask()] + tasks: Task[] = [createTask()], + visibleFields: VisibleField[] = ["name"] ) => { render( ()} @@ -194,4 +204,43 @@ describe("TaskList onCellCommit", () => { expect((update.start as Date).getHours()).toBe(13); expect((update.start as Date).getMinutes()).toBe(30); }); + + it("normalizes progress commit to 5-step and clamps to 100", async () => { + const onCellCommit = jest.fn().mockResolvedValue(undefined); + renderTaskList(onCellCommit, undefined, [createTask({ progress: 50 })], [ + "name", + "progress", + ]); + + fireEvent.click(screen.getByTestId("start-edit-progress")); + const input = await screen.findByTestId("overlay-editor-input"); + fireEvent.change(input, { target: { value: "103" } }); + fireEvent.keyDown(input, { key: "Enter" }); + + await waitFor(() => expect(onCellCommit).toHaveBeenCalledTimes(1)); + const commitPayload = onCellCommit.mock.calls[0][0]; + expect(commitPayload.columnId).toBe("progress"); + expect(commitPayload.value).toBe("100"); + }); + + it("shows error when progress input is invalid", async () => { + const onCellCommit = jest.fn().mockResolvedValue(undefined); + renderTaskList(onCellCommit, undefined, [createTask({ progress: 50 })], [ + "name", + "progress", + ]); + + fireEvent.click(screen.getByTestId("start-edit-progress")); + const input = await screen.findByTestId("overlay-editor-input"); + fireEvent.change(input, { target: { value: "" } }); + fireEvent.keyDown(input, { key: "Enter" }); + + await waitFor(() => + expect(screen.getByRole("alert")).toHaveTextContent( + "0〜100 の数値を入力してください" + ) + ); + expect(onCellCommit).not.toHaveBeenCalled(); + expect(screen.getByTestId("overlay-editor-input")).toBeInTheDocument(); + }); }); diff --git a/src/test/task-list-table-editing.test.tsx b/src/test/task-list-table-editing.test.tsx index 6a6769f58..82d0bc749 100644 --- a/src/test/task-list-table-editing.test.tsx +++ b/src/test/task-list-table-editing.test.tsx @@ -299,6 +299,7 @@ describe("TaskListTable cell display", () => { createMockTask("task-1", "Task 1", { start: new Date(2026, 1, 1), end: new Date(2026, 1, 10), + progress: 42, process: "開発", assignee: "田中太郎", plannedStart: new Date(2026, 1, 1), @@ -315,6 +316,7 @@ describe("TaskListTable cell display", () => { "name", "start", "end", + "progress", "process", "assignee", "plannedStart", @@ -334,6 +336,7 @@ describe("TaskListTable cell display", () => { // start and plannedStart share the same date for this fixture expect(screen.getAllByText("2026-02-01")).toHaveLength(2); expect(screen.getByText("2026-02-10")).toBeInTheDocument(); + expect(screen.getByText("40")).toBeInTheDocument(); expect(screen.getByText("開発")).toBeInTheDocument(); expect(screen.getByText("田中太郎")).toBeInTheDocument(); expect(screen.getByText("2026-02-15")).toBeInTheDocument(); @@ -350,6 +353,17 @@ describe("TaskListTable cell display", () => { expect(screen.queryAllByRole("spinbutton")).toHaveLength(0); }); + it("does not render progress column when not included in visibleFields", () => { + render( + + ); + + expect(document.querySelector('[data-column-id="progress"]')).toBeNull(); + }); + it("keeps edit triggers available for overlay editing", () => { const context = createEditingContext("selected", "task-1", "name"); diff --git a/src/types/public-types.ts b/src/types/public-types.ts index 854b91ed8..6e092cdb8 100644 --- a/src/types/public-types.ts +++ b/src/types/public-types.ts @@ -7,6 +7,7 @@ export type VisibleField = | "name" | "start" | "end" + | "progress" | "process" | "assignee" | "plannedStart" From fe6746a9b53483204cfbfb11f1512520c9f0c589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:51:42 +0000 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=B7=AE=E5=88=86=E3=82=92=E9=99=A4=E5=8E=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 依存関係変更がないためpackage-lockの更新を取り消し、差分を最小化する。 実装内容と無関係な生成物がPRに混在しないように調整する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/package-lock.json | 127 +++++++++++++++++++++++++------------- package-lock.json | 18 ++++++ 2 files changed, 103 insertions(+), 42 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index c7bded69a..b2f250adb 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -23,46 +23,6 @@ "cross-env": "^7.0.3" } }, - "..": { - "name": "@levelcaptech/gantt-task-react-custom", - "version": "0.5.0", - "license": "MIT", - "devDependencies": { - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^7.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "^13.3.0", - "@testing-library/user-event": "^14.2.1", - "@types/classnames": "^2.3.0", - "@types/jest": "^27.5.1", - "@types/node": "^15.0.1", - "@types/react": "^18.0.5", - "@types/react-dom": "^18.0.5", - "cross-env": "^7.0.3", - "gh-pages": "^3.1.0", - "microbundle-crl": "^0.13.11", - "mini-css-extract-plugin": "^2.5.1", - "npm-run-all": "^4.1.5", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-normalize": "^10.0.1", - "postcss-preset-env": "^7.6.0", - "prettier": "^2.7.1", - "react-scripts": "^5.0.1", - "typescript": "^4.7.4" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^7.0.0", - "@dnd-kit/utilities": "^3.2.2", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "license": "MIT" @@ -2113,6 +2073,63 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", + "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dnd-kit/utilities": "^3.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.7", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "license": "MIT", @@ -3039,8 +3056,19 @@ "license": "MIT" }, "node_modules/@levelcaptech/gantt-task-react-custom": { - "resolved": "..", - "link": true + "version": "0.1.0", + "resolved": "file:..", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^7.0.0", + "@dnd-kit/utilities": "^3.2.2", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", @@ -15286,6 +15314,21 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.2", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/tapable": { "version": "2.3.0", "license": "MIT", diff --git a/package-lock.json b/package-lock.json index 303148f1a..b20b53248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21826,6 +21826,24 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", From e44f7836ce77cf52b4e2e7fb787426c03f980e8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:53:05 +0000 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20example=E3=81=AE=E9=80=B2=E6=8D=97?= =?UTF-8?q?=E5=88=97=E8=A8=AD=E5=AE=9A=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit visibleFieldsのベース列と数値系コミット処理を共通化し、重複を削減する。 挙動は変更せず、サンプルの可読性と保守性を高める。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/src/App.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index cc8157f92..1c646cc56 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,14 +100,12 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; +const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; + const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ - "name", - "start", - "end", + ...BASE_VISIBLE_FIELDS, "progress", - ...DEFAULT_VISIBLE_FIELDS.filter(field => - !["name", "start", "end"].includes(field) - ), + ...DEFAULT_VISIBLE_FIELDS.filter(field => !BASE_VISIBLE_FIELDS.includes(field)), ]; const resolveCellCommitValue = ( @@ -124,10 +122,7 @@ const resolveCellCommitValue = ( return Number.isNaN(parsedDate.getTime()) ? fallbackValue : parsedDate; } case "plannedEffort": - case "actualEffort": { - const parsedNumber = Number(value); - return Number.isNaN(parsedNumber) ? fallbackValue : parsedNumber; - } + case "actualEffort": case "progress": { const parsedNumber = Number(value); return Number.isNaN(parsedNumber) ? fallbackValue : parsedNumber; From 45eafcd2d6901b19fbc664ae003fffcceff5e6f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:54:07 +0000 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20=E9=80=B2=E6=8D=97=E5=88=97?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=A8=E3=82=B5=E3=83=B3=E3=83=97?= =?UTF-8?q?=E3=83=AB=E6=B3=A8=E8=A8=98=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 進捗表示テストの意図が明確になるよう値を揃え、サンプルの基準列用途を補足した。 機能挙動は維持しつつレビュー指摘への対応を行う。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/src/App.tsx | 1 + src/test/task-list-table-editing.test.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 1c646cc56..1b621c216 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,6 +100,7 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; +// 表示順と重複除外の両方に使う基準列(進捗列は別途追加) const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ diff --git a/src/test/task-list-table-editing.test.tsx b/src/test/task-list-table-editing.test.tsx index 82d0bc749..124b07ebb 100644 --- a/src/test/task-list-table-editing.test.tsx +++ b/src/test/task-list-table-editing.test.tsx @@ -299,7 +299,7 @@ describe("TaskListTable cell display", () => { createMockTask("task-1", "Task 1", { start: new Date(2026, 1, 1), end: new Date(2026, 1, 10), - progress: 42, + progress: 40, process: "開発", assignee: "田中太郎", plannedStart: new Date(2026, 1, 1), From 13e6c743c43e66bb4b49844d95cba7f257846ade Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:54:54 +0000 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=E3=82=B5=E3=83=B3=E3=83=97?= =?UTF-8?q?=E3=83=AB=E5=88=97=E6=A7=8B=E6=88=90=E3=81=AE=E9=87=8D=E8=A4=87?= =?UTF-8?q?=E3=82=92=E9=98=B2=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VISIBLE_FIELDS_WITH_PROGRESSの構築時にprogress重複を避けるよう除外条件を補強する。 将来DEFAULT_VISIBLE_FIELDSに変更があっても意図した表示順を維持する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/src/App.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 1b621c216..10a73bc38 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -106,7 +106,9 @@ const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ ...BASE_VISIBLE_FIELDS, "progress", - ...DEFAULT_VISIBLE_FIELDS.filter(field => !BASE_VISIBLE_FIELDS.includes(field)), + ...DEFAULT_VISIBLE_FIELDS.filter( + field => !BASE_VISIBLE_FIELDS.includes(field) && field !== "progress" + ), ]; const resolveCellCommitValue = ( From e2389d12a6130a2553a1cd41ce2135d817776b67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:55:57 +0000 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=E9=80=B2=E6=8D=97=E8=A1=A8?= =?UTF-8?q?=E8=A8=98=E3=81=A8=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB=E6=B3=A8?= =?UTF-8?q?=E8=A8=98=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit progress正規化の型前提に合わせてチェックを簡素化し、READMEの丸め表現を明確化する。 サンプルのコメント表現も進捗列の構成に合わせて整理する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- README.md | 2 +- example/src/App.tsx | 2 +- src/helpers/task-helper.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26f270667..2992b0b52 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ const visibleFields: VisibleField[] = [ />; ``` -progress セルの編集は `step=5` で確定し、`onCellCommit` には `"0"〜"100"` の文字列が通知されます。 +progress セルの編集では、入力値は 5 の倍数に丸められ、`onCellCommit` には `"0"〜"100"` の文字列が通知されます。 ### Task diff --git a/example/src/App.tsx b/example/src/App.tsx index 10a73bc38..7fa13d40e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,7 +100,7 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; -// 表示順と重複除外の両方に使う基準列(進捗列は別途追加) +// 表示順と重複除外の両方に使う基準列(進捗列を含む定義は後続で構築) const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ diff --git a/src/helpers/task-helper.ts b/src/helpers/task-helper.ts index 1f8ab45e1..c038018a1 100644 --- a/src/helpers/task-helper.ts +++ b/src/helpers/task-helper.ts @@ -79,7 +79,7 @@ export const sanitizeEffortInput = (value: string) => { const clampProgress = (value: number) => Math.min(100, Math.max(0, value)); export const normalizeProgress = (progress?: number) => { - if (progress === undefined || progress === null || !Number.isFinite(progress)) { + if (progress === undefined || !Number.isFinite(progress)) { return null; } const rounded = Math.round(progress / 5) * 5; From faa71ec96477afb20f13deab0968313d871a13b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:56:36 +0000 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20example=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A8=E9=87=8D=E8=A4=87=E6=9D=A1=E4=BB=B6=E3=82=92?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit サンプルの基準列コメントを自然な表現に整え、不要な進捗除外条件を削除する。 仕様には影響しないが、読みやすさを維持する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/src/App.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 7fa13d40e..ae81a4f9a 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,15 +100,13 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; -// 表示順と重複除外の両方に使う基準列(進捗列を含む定義は後続で構築) +// 表示順と重複除外の両方に使う基準列(進捗列を含む定義は以下で構築する) const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ ...BASE_VISIBLE_FIELDS, "progress", - ...DEFAULT_VISIBLE_FIELDS.filter( - field => !BASE_VISIBLE_FIELDS.includes(field) && field !== "progress" - ), + ...DEFAULT_VISIBLE_FIELDS.filter(field => !BASE_VISIBLE_FIELDS.includes(field)), ]; const resolveCellCommitValue = ( From 09f7a38f57fe29b91a6b66f1025c9575c93848e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:57:14 +0000 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20example=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E7=B0=A1=E6=BD=94=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基準列コメントをより自然な表現に整え、サンプルの可読性を高める。 機能の挙動は変更しない。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- example/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index ae81a4f9a..46d41bcc8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,7 +100,7 @@ const JapaneseTooltip: React.FC<{ type TaskFieldValue = Task[VisibleField]; -// 表示順と重複除外の両方に使う基準列(進捗列を含む定義は以下で構築する) +// 表示順と重複除外に使う基準の列(進捗列を含む定義は以下で構築する) const BASE_VISIBLE_FIELDS: VisibleField[] = ["name", "start", "end"]; const VISIBLE_FIELDS_WITH_PROGRESS: VisibleField[] = [ From 3488427ee646d66463f8bb36bce6944ab503bad3 Mon Sep 17 00:00:00 2001 From: Yoshio Nishiyama <99854263+LevelCapTech@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:42:22 +0900 Subject: [PATCH 10/12] fix: Update src/components/task-list/task-list-table.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/components/task-list/task-list-table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/task-list/task-list-table.tsx b/src/components/task-list/task-list-table.tsx index fa547d3a3..dc97bd026 100644 --- a/src/components/task-list/task-list-table.tsx +++ b/src/components/task-list/task-list-table.tsx @@ -276,7 +276,7 @@ export const TaskListTableDefault: React.FC<{ case "end": return {formatDate(t.end)}; case "progress": - return {formatProgress(t.progress)}; + return {t.progress}; case "process": return {normalizeProcess(t.process)}; case "assignee": From f27358c436a8de0db13b6a74fdd404a3ebe1833a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:47:26 +0000 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=E9=80=B2=E6=8D=97=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AE=E4=B8=B8=E3=82=81=E8=A7=A3=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 進捗列の表示は実値(クランプのみ)にし、ガント表示との不整合を解消する。 関連テストの期待値も実値に合わせて更新する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- src/components/task-list/task-list-table.tsx | 1 - src/helpers/task-helper.ts | 3 +-- src/test/task-helper.test.tsx | 4 ++-- src/test/task-list-table-editing.test.tsx | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/task-list/task-list-table.tsx b/src/components/task-list/task-list-table.tsx index dc97bd026..0b1d89ea1 100644 --- a/src/components/task-list/task-list-table.tsx +++ b/src/components/task-list/task-list-table.tsx @@ -5,7 +5,6 @@ import { getDefaultWidth, TaskListEditingStateContext } from "./task-list"; import { formatDate, formatEffort, - formatProgress, getStatusBadgeText, getStatusColor, normalizeProcess, diff --git a/src/helpers/task-helper.ts b/src/helpers/task-helper.ts index c038018a1..16eccd5e5 100644 --- a/src/helpers/task-helper.ts +++ b/src/helpers/task-helper.ts @@ -82,8 +82,7 @@ export const normalizeProgress = (progress?: number) => { if (progress === undefined || !Number.isFinite(progress)) { return null; } - const rounded = Math.round(progress / 5) * 5; - return clampProgress(rounded); + return clampProgress(progress); }; export const formatProgress = (progress?: number): string => { diff --git a/src/test/task-helper.test.tsx b/src/test/task-helper.test.tsx index a20d18523..d89b47fc7 100644 --- a/src/test/task-helper.test.tsx +++ b/src/test/task-helper.test.tsx @@ -70,8 +70,8 @@ describe("task-helper sanitizeEffortInput", () => { }); describe("task-helper progress helpers", () => { - it("formats progress with 5-step rounding and clamp", () => { - expect(formatProgress(42)).toBe("40"); + it("formats progress with clamp only", () => { + expect(formatProgress(42)).toBe("42"); expect(formatProgress(101)).toBe("100"); expect(formatProgress(-3)).toBe("0"); }); diff --git a/src/test/task-list-table-editing.test.tsx b/src/test/task-list-table-editing.test.tsx index 124b07ebb..53c02c12e 100644 --- a/src/test/task-list-table-editing.test.tsx +++ b/src/test/task-list-table-editing.test.tsx @@ -299,7 +299,7 @@ describe("TaskListTable cell display", () => { createMockTask("task-1", "Task 1", { start: new Date(2026, 1, 1), end: new Date(2026, 1, 10), - progress: 40, + progress: 42, process: "開発", assignee: "田中太郎", plannedStart: new Date(2026, 1, 1), @@ -336,7 +336,7 @@ describe("TaskListTable cell display", () => { // start and plannedStart share the same date for this fixture expect(screen.getAllByText("2026-02-01")).toHaveLength(2); expect(screen.getByText("2026-02-10")).toBeInTheDocument(); - expect(screen.getByText("40")).toBeInTheDocument(); + expect(screen.getByText("42")).toBeInTheDocument(); expect(screen.getByText("開発")).toBeInTheDocument(); expect(screen.getByText("田中太郎")).toBeInTheDocument(); expect(screen.getByText("2026-02-15")).toBeInTheDocument(); From 88158b27c4a84e001ea2858a7f78fe6ee1413d9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:51:10 +0000 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20=E9=80=B2=E6=8D=97=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AE=E3=82=AF=E3=83=A9=E3=83=B3=E3=83=97=E7=B5=B1?= =?UTF-8?q?=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 進捗列の表示はformatProgressに統一し、表示側はクランプのみでガント表示と整合させる。 補足コメントをJSDoc化して意図を明確化する。 Co-authored-by: LevelCapTech <99854263+LevelCapTech@users.noreply.github.com> --- src/components/task-list/task-list-table.tsx | 3 ++- src/helpers/task-helper.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/task-list/task-list-table.tsx b/src/components/task-list/task-list-table.tsx index 0b1d89ea1..fa547d3a3 100644 --- a/src/components/task-list/task-list-table.tsx +++ b/src/components/task-list/task-list-table.tsx @@ -5,6 +5,7 @@ import { getDefaultWidth, TaskListEditingStateContext } from "./task-list"; import { formatDate, formatEffort, + formatProgress, getStatusBadgeText, getStatusColor, normalizeProcess, @@ -275,7 +276,7 @@ export const TaskListTableDefault: React.FC<{ case "end": return {formatDate(t.end)}; case "progress": - return {t.progress}; + return {formatProgress(t.progress)}; case "process": return {normalizeProcess(t.process)}; case "assignee": diff --git a/src/helpers/task-helper.ts b/src/helpers/task-helper.ts index 16eccd5e5..acb940960 100644 --- a/src/helpers/task-helper.ts +++ b/src/helpers/task-helper.ts @@ -78,6 +78,7 @@ export const sanitizeEffortInput = (value: string) => { const clampProgress = (value: number) => Math.min(100, Math.max(0, value)); +/** 表示用に 0〜100 の範囲へクランプする(5刻み丸めは行わない) */ export const normalizeProgress = (progress?: number) => { if (progress === undefined || !Number.isFinite(progress)) { return null; @@ -90,6 +91,7 @@ export const formatProgress = (progress?: number): string => { return normalized === null ? "" : `${normalized}`; }; +/** commit 用に 5 刻みへ丸めたうえで 0〜100 にクランプする */ export const parseProgressInput = (value: string) => { if (value.trim() === "") { return null;