fix: address code review feedback

- Replace type assertions with type guards in types.ts (isDateString, isStringArray)
- Add useCallback for event handlers (handleTaskClick, handleKeyDown)
- Replace styled-jsx with CSS modules (gantt.module.css)
- Update tests to use CSS module class name patterns
This commit is contained in:
Jason Woltje
2026-01-29 19:32:23 -06:00
parent aa6d466321
commit 16697bfb79
4 changed files with 63 additions and 39 deletions

View File

@@ -91,7 +91,7 @@ describe("GanttChart", () => {
render(<GanttChart tasks={tasks} />);
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(<GanttChart tasks={tasks} />);
const taskRow = screen.getAllByText("Active Task")[0].closest("[role='row']");
expect(taskRow).toHaveClass(/in-progress/i);
expect(taskRow?.className).toMatch(/InProgress/i);
});
});

View File

@@ -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<HTMLDivElement>): 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<HTMLDivElement>): void => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
if (onTaskClick) {
onTaskClick(task);
}
}
},
[onTaskClick]
);
return (
<div
@@ -415,19 +422,6 @@ export function GanttChart({
</div>
</div>
</div>
{/* CSS for status classes */}
<style jsx>{`
.gantt-row-completed {
background-color: #f0fdf4;
}
.gantt-row-in-progress {
background-color: #eff6ff;
}
.gantt-row-paused {
background-color: #fefce8;
}
`}</style>
</div>
);
}

View File

@@ -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 */
}

View File

@@ -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 metadataStartDate = task.metadata?.startDate;
const startDate = isDateString(metadataStartDate)
? new Date(metadataStartDate)
: task.createdAt;
const endDate = task.dueDate || new Date();
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,
};
}