diff --git a/apps/web/src/components/gantt/GanttChart.test.tsx b/apps/web/src/components/gantt/GanttChart.test.tsx
index 2ed6f9b..8e25088 100644
--- a/apps/web/src/components/gantt/GanttChart.test.tsx
+++ b/apps/web/src/components/gantt/GanttChart.test.tsx
@@ -91,7 +91,7 @@ describe("GanttChart", () => {
render();
const taskRow = screen.getAllByText("Completed Task")[0].closest("[role='row']");
- expect(taskRow).toHaveClass(/completed/i);
+ expect(taskRow?.className).toMatch(/Completed/i);
});
it("should visually distinguish in-progress tasks", () => {
@@ -106,7 +106,7 @@ describe("GanttChart", () => {
render();
const taskRow = screen.getAllByText("Active Task")[0].closest("[role='row']");
- expect(taskRow).toHaveClass(/in-progress/i);
+ expect(taskRow?.className).toMatch(/InProgress/i);
});
});
diff --git a/apps/web/src/components/gantt/GanttChart.tsx b/apps/web/src/components/gantt/GanttChart.tsx
index 63b4363..8539ba7 100644
--- a/apps/web/src/components/gantt/GanttChart.tsx
+++ b/apps/web/src/components/gantt/GanttChart.tsx
@@ -3,7 +3,8 @@
import type { GanttTask, GanttChartProps, TimelineRange, GanttBarPosition } from "./types";
import { TaskStatus } from "@mosaic/shared";
import { formatDate, isPastTarget, isApproachingTarget } from "@/lib/utils/date-format";
-import { useMemo } from "react";
+import { useMemo, useCallback } from "react";
+import styles from "./gantt.module.css";
/**
* Represents a dependency line between two tasks
@@ -111,11 +112,11 @@ function getStatusClass(status: TaskStatus): string {
function getRowStatusClass(status: TaskStatus): string {
switch (status) {
case TaskStatus.COMPLETED:
- return "gantt-row-completed";
+ return styles.rowCompleted;
case TaskStatus.IN_PROGRESS:
- return "gantt-row-in-progress";
+ return styles.rowInProgress;
case TaskStatus.PAUSED:
- return "gantt-row-paused";
+ return styles.rowPaused;
default:
return "";
}
@@ -232,20 +233,26 @@ export function GanttChart({
[showDependencies, sortedTasks, timelineRange]
);
- const handleTaskClick = (task: GanttTask) => (): void => {
- if (onTaskClick) {
- onTaskClick(task);
- }
- };
-
- const handleKeyDown = (task: GanttTask) => (e: React.KeyboardEvent): void => {
- if (e.key === "Enter" || e.key === " ") {
- e.preventDefault();
+ const handleTaskClick = useCallback(
+ (task: GanttTask) => (): void => {
if (onTaskClick) {
onTaskClick(task);
}
- }
- };
+ },
+ [onTaskClick]
+ );
+
+ const handleKeyDown = useCallback(
+ (task: GanttTask) => (e: React.KeyboardEvent): void => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ if (onTaskClick) {
+ onTaskClick(task);
+ }
+ }
+ },
+ [onTaskClick]
+ );
return (
-
- {/* CSS for status classes */}
-
);
}
diff --git a/apps/web/src/components/gantt/gantt.module.css b/apps/web/src/components/gantt/gantt.module.css
new file mode 100644
index 0000000..a81d090
--- /dev/null
+++ b/apps/web/src/components/gantt/gantt.module.css
@@ -0,0 +1,12 @@
+/* Gantt Chart Status Row Styles */
+.rowCompleted {
+ background-color: #f0fdf4; /* green-50 */
+}
+
+.rowInProgress {
+ background-color: #eff6ff; /* blue-50 */
+}
+
+.rowPaused {
+ background-color: #fefce8; /* yellow-50 */
+}
diff --git a/apps/web/src/components/gantt/types.ts b/apps/web/src/components/gantt/types.ts
index b4151b1..ef5ef3b 100644
--- a/apps/web/src/components/gantt/types.ts
+++ b/apps/web/src/components/gantt/types.ts
@@ -58,31 +58,49 @@ export interface GanttChartProps {
showDependencies?: boolean;
}
+/**
+ * Type guard to check if a value is a valid date string
+ */
+function isDateString(value: unknown): value is string {
+ return typeof value === 'string' && !isNaN(Date.parse(value));
+}
+
+/**
+ * Type guard to check if a value is an array of strings
+ */
+function isStringArray(value: unknown): value is string[] {
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
+}
+
/**
* Helper to convert a base Task to GanttTask
* Uses createdAt as startDate if not in metadata, dueDate as endDate
*/
export function toGanttTask(task: Task): GanttTask | null {
// For Gantt chart, we need both start and end dates
- const startDate =
- (task.metadata?.startDate as string | undefined)
- ? new Date(task.metadata.startDate as string)
- : task.createdAt;
-
- const endDate = task.dueDate || new Date();
+ const metadataStartDate = task.metadata?.startDate;
+ const startDate = isDateString(metadataStartDate)
+ ? new Date(metadataStartDate)
+ : task.createdAt;
+
+ const endDate = task.dueDate ?? new Date();
// Validate dates
if (!startDate || !endDate) {
return null;
}
+ // Extract dependencies with type guard
+ const metadataDependencies = task.metadata?.dependencies;
+ const dependencies = isStringArray(metadataDependencies)
+ ? metadataDependencies
+ : undefined;
+
return {
...task,
startDate,
endDate,
- dependencies: Array.isArray(task.metadata?.dependencies)
- ? (task.metadata.dependencies as string[])
- : undefined,
+ dependencies,
isMilestone: task.metadata?.isMilestone === true,
};
}