Files
ProjectE/apps/web/src/components/gantt/gantt-dependency-arrow.tsx
T
Hermes 7041906e7d feat(plane-lift): API routes, UI components, migrations — Phase 2
New API routes:
- statuses.ts: CRUD for custom task statuses
- automations.ts: automation rules engine
- timeline.ts: entity timeline/activity view
- activity.ts: activity feed endpoint

New UI components:
- gantt/: gantt chart (6 files: chart, task-bar, milestone, timeline, deps, utils)
- automation-rule-builder.tsx: visual rule editor
- notification-center.tsx: in-app notifications
- quick-add-bar.tsx: global quick-add
- entities/: detail-page, activity, comments, inline-edit, note-editor
- tasks/: recurrence-picker

New hooks:
- use-optimistic-patch.ts: optimistic UI updates

New libs:
- nlp-parser.ts + test: natural language task parsing
- notify.ts: notification dispatch
- automation-engine.ts: rule evaluation

DB migrations:
- 0007_custom_task_statuses.sql
- 0008_automation_rules.sql
- 0009_notifications.sql
- migrate-task-statuses.ts: backfill script

Modified:
- tasks.ts: plane-lift integration (stateId/moduleId/cycleId)
- analytics.ts: updated for new schema
- canvas/$id.tsx: restored
2026-09-07 18:09:03 +00:00

50 lines
1.6 KiB
TypeScript

import type { TimelineTask } from "./gantt-utils";
export interface TaskPosition {
/** Left edge of the task's bar in timeline pixels. */
startX: number;
/** Right edge of the task's bar in timeline pixels. */
endX: number;
/** Vertical center of the task's row in pixels. */
y: number;
}
interface GanttDependencyArrowProps {
/** The task being depended on (arrow originates at the end of its bar). */
fromTask: TimelineTask;
/** The blocked task (arrowhead lands at the start of its bar). */
toTask: TimelineTask;
taskPositions: ReadonlyMap<string, TaskPosition>;
}
const BEND = 12;
/**
* SVG elbow arrow from the end of the blocking task's bar to the start of the
* blocked task's bar. Rendered inside the chart's overlay <svg> — the marker
* is defined there under the id `gantt-arrow`.
*/
export function GanttDependencyArrow({ fromTask, toTask, taskPositions }: GanttDependencyArrowProps) {
const from = taskPositions.get(fromTask.id);
const to = taskPositions.get(toTask.id);
if (!from || !to) return null;
const x1 = from.endX;
const y1 = from.y;
// If the target bar starts before the source ends, drop the arrowhead just
// past the source end so the elbow path never doubles back on itself.
const x2 = Math.max(to.startX, from.endX + BEND);
const y2 = to.y;
const d = `M ${x1} ${y1} H ${x1 + BEND} L ${x2 - BEND} ${y2} H ${x2}`;
return (
<path
d={d}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
markerEnd="url(#gantt-arrow)"
className="text-muted-foreground/70"
/>
);
}