diff --git a/packages/frontend/src/pages/Timeline.tsx b/packages/frontend/src/pages/Timeline.tsx
index aea2715..2fc1c41 100644
--- a/packages/frontend/src/pages/Timeline.tsx
+++ b/packages/frontend/src/pages/Timeline.tsx
@@ -10,6 +10,7 @@ import { useSelection } from "@/components/selection-provider";
import { SprintForm } from "@/components/sprint-form";
import StatusTag from "@/components/status-tag";
import TopBar from "@/components/top-bar";
+import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { BREATHING_ROOM } from "@/lib/layout";
import {
useIssues,
@@ -21,8 +22,10 @@ import {
import { cn } from "@/lib/utils";
const DAY_MS = 24 * 60 * 60 * 1000;
-const TIMELINE_LABEL_WIDTH = 240;
-const WEEK_COLUMN_WIDTH = 140;
+const TIMELINE_LABEL_DEFAULT_SIZE = 420;
+const TIMELINE_LABEL_MIN_SIZE = 300;
+const TIMELINE_LABEL_MAX_SIZE = "55%";
+const WEEK_COLUMN_WIDTH = 160;
const addDays = (value: Date, days: number) =>
new Date(value.getFullYear(), value.getMonth(), value.getDate() + days);
@@ -176,9 +179,9 @@ export default function Timeline() {
const statuses = selectedOrganisation?.Organisation.statuses ?? {};
- const gridTemplateColumns = useMemo(() => {
- if (weeks.length === 0) return `${TIMELINE_LABEL_WIDTH}px 1fr`;
- return `${TIMELINE_LABEL_WIDTH}px repeat(${weeks.length}, ${WEEK_COLUMN_WIDTH}px)`;
+ const weekGridTemplateColumns = useMemo(() => {
+ if (weeks.length === 0) return "1fr";
+ return `repeat(${weeks.length}, ${WEEK_COLUMN_WIDTH}px)`;
}, [weeks.length]);
const todayMarker = useMemo(() => {
@@ -243,152 +246,139 @@ export default function Timeline() {
{selectedOrganisationId && selectedProjectId && sprints.length > 0 && (
-
-
+
-
+
Sprint
- {weeks.map((week) => (
-
- {formatWeekLabel(week)}
-
- ))}
-
-
- {sprints.map((sprint, sprintIndex) => {
- const sprintIssues = issueGroup.issuesBySprint.get(sprint.id) ?? [];
- const barStyle = getSprintBarStyle(sprint);
- const showTodayLabel = sprintIndex === 0;
- return (
-
+ {sprints.map((sprint) => {
+ const sprintIssues = issueGroup.issuesBySprint.get(sprint.id) ?? [];
+ return (
-
-
- {sprint.name}
-
-
- {getSprintDateRange(sprint)}
-
-
- {sprintIssues.length === 0 && (
-
No issues assigned.
- )}
- {sprintIssues.length > 0 && (
-
- {sprintIssues.map((issue) => (
-
- ))}
-
- )}
+
-
+
+
+
+
+
+ {weeks.map((week, index) => (
- {weeks.map((week, index) => (
-
- ))}
+ {formatWeekLabel(week)}
- {todayMarker && (
+ ))}
+
+
+ {sprints.map((sprint, sprintIndex) => {
+ const sprintIssues = issueGroup.issuesBySprint.get(sprint.id) ?? [];
+ const barStyle = getSprintBarStyle(sprint);
+ const showTodayLabel = sprintIndex === 0;
+ return (
+
-
- {showTodayLabel && (
-
-
- TODAY
-
+
+
+
+ {weeks.map((week, index) => (
+
+ ))}
+
+ {todayMarker && (
+
+
+ {showTodayLabel && (
+
+
+ TODAY
+
+
+ )}
)}
-
- )}
- {barStyle && (
-
+ }
/>
- }
- />
- )}
+ )}
+
+
+ );
+ })}
+
+
- );
- })}
-
-
-
-
Backlog
- {issueGroup.unassigned.length === 0 && (
-
No unassigned issues.
- )}
- {issueGroup.unassigned.length > 0 && (
-
- {issueGroup.unassigned.map((issue) => (
-
- ))}
-
- )}
-
-
-
+
+
)}
@@ -396,6 +386,74 @@ export default function Timeline() {
);
}
+function SprintLabelContent({
+ sprint,
+ sprintIssues,
+ statuses,
+}: {
+ sprint: SprintRecord;
+ sprintIssues: IssueResponse[];
+ statuses: Record;
+}) {
+ return (
+ <>
+
+
+ {sprint.name}
+
+
{getSprintDateRange(sprint)}
+
+ {sprintIssues.length === 0 && (
+ No issues assigned.
+ )}
+ {sprintIssues.length > 0 && (
+
+ {sprintIssues.map((issue) => (
+
+ ))}
+
+ )}
+ >
+ );
+}
+
+function BacklogLabelContent({
+ issueGroup,
+ statuses,
+}: {
+ issueGroup: IssueGroup;
+ statuses: Record;
+}) {
+ return (
+ <>
+ Backlog
+ {issueGroup.unassigned.length === 0 && (
+ No unassigned issues.
+ )}
+ {issueGroup.unassigned.length > 0 && (
+
+ {issueGroup.unassigned.map((issue) => (
+
+ ))}
+
+ )}
+ >
+ );
+}
+
function IssueLine({ issue, statusColour }: { issue: IssueResponse; statusColour: string }) {
const [open, setOpen] = useState(false);
@@ -412,8 +470,8 @@ function IssueLine({ issue, statusColour }: { issue: IssueResponse; statusColour
"hover:text-foreground cursor-pointer",
)}
>
+ {issue.Issue.number.toString().padStart(3, "0")}
- #{issue.Issue.number.toString().padStart(3, "0")}
{issue.Issue.title}
}