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(
+
+ );
+
+ 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;