@elevasis/ui 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{chunk-F6RBK7NJ.js → chunk-22UVE3RA.js} +1 -1
  2. package/dist/{chunk-OCP2MBTY.js → chunk-27COZ5AH.js} +3 -118
  3. package/dist/chunk-2DZACNOX.js +1111 -0
  4. package/dist/chunk-3ONP2CEB.js +1842 -0
  5. package/dist/{chunk-ZY4MWZW2.js → chunk-5XGBMKUY.js} +3 -3
  6. package/dist/chunk-BZZCNLT6.js +12 -0
  7. package/dist/{chunk-SWIAK47F.js → chunk-G3G2QEB6.js} +5 -5
  8. package/dist/chunk-IDACMRGQ.js +115 -0
  9. package/dist/{chunk-2XWEOJSX.js → chunk-IPRMGSCV.js} +1 -1
  10. package/dist/chunk-J5KWNRSD.js +45 -0
  11. package/dist/{chunk-NKV5MEWQ.js → chunk-KRTZTBVP.js} +10 -8
  12. package/dist/{chunk-Q3FTQP2M.js → chunk-PEZ4WOPF.js} +1 -1
  13. package/dist/chunk-TUMSNGTX.js +35 -0
  14. package/dist/{chunk-PEATQEEP.js → chunk-WN764MR7.js} +2 -2
  15. package/dist/chunk-WSL5MNAI.js +955 -0
  16. package/dist/{chunk-IWFIKQR5.js → chunk-ZG7MLOBE.js} +1 -1
  17. package/dist/components/index.css +10 -14
  18. package/dist/components/index.js +46 -3922
  19. package/dist/features/auth/index.css +10 -14
  20. package/dist/features/crm/index.css +589 -0
  21. package/dist/features/crm/index.d.ts +2833 -0
  22. package/dist/features/crm/index.js +33 -0
  23. package/dist/features/dashboard/index.css +10 -14
  24. package/dist/features/dashboard/index.js +5 -5
  25. package/dist/features/delivery/index.css +589 -0
  26. package/dist/features/delivery/index.d.ts +2648 -0
  27. package/dist/features/delivery/index.js +33 -0
  28. package/dist/features/lead-gen/index.css +589 -0
  29. package/dist/features/lead-gen/index.d.ts +451 -0
  30. package/dist/features/lead-gen/index.js +42 -0
  31. package/dist/features/monitoring/index.css +10 -14
  32. package/dist/features/monitoring/index.js +6 -6
  33. package/dist/features/operations/index.css +10 -14
  34. package/dist/features/operations/index.js +10 -8
  35. package/dist/features/seo/index.d.ts +41 -0
  36. package/dist/features/seo/index.js +5 -0
  37. package/dist/features/settings/index.css +10 -14
  38. package/dist/features/settings/index.js +4 -4
  39. package/dist/graph/index.css +0 -14
  40. package/dist/graph/index.js +1 -1
  41. package/dist/hooks/index.css +10 -14
  42. package/dist/hooks/index.js +3 -3
  43. package/dist/hooks/published.css +10 -14
  44. package/dist/hooks/published.js +2 -2
  45. package/dist/index.css +10 -14
  46. package/dist/index.js +4 -4
  47. package/dist/layout/index.js +3 -1
  48. package/package.json +19 -3
@@ -0,0 +1,955 @@
1
+ import { TableSelectionToolbar, SortableHeader } from './chunk-TUMSNGTX.js';
2
+ import { PageContainer } from './chunk-BZZCNLT6.js';
3
+ import { SubshellSidebarSection, SubshellNavItem } from './chunk-27COZ5AH.js';
4
+ import { FilterBar } from './chunk-PDHTXPSF.js';
5
+ import { CustomModal } from './chunk-GBMNCNHX.js';
6
+ import { useProjects, useProject, useProjectNotes, useDeleteProject as useDeleteProject$1, useUpdateMilestone, useCreateNote } from './chunk-PEZ4WOPF.js';
7
+ import { useDeleteProject, useTableSort, sortData, usePaginationState, useTableSelection } from './chunk-IPRMGSCV.js';
8
+ import { SubshellContentContainer } from './chunk-RX4UWZZR.js';
9
+ import { StatusBadge, EmptyState, PageTitleCaption, CenteredErrorState, StatCard } from './chunk-Y3D3WFJG.js';
10
+ import { PAGE_SIZE_DEFAULT, formatTimeAgo, formatDate } from './chunk-IOKL7BKE.js';
11
+ import { useRouterContext } from './chunk-Q7DJKLEN.js';
12
+ import { SimpleGrid, Card, Stack, Group, ThemeIcon, Text, RingProgress, Paper, Timeline, Badge, Collapse, ActionIcon, Select, TextInput, Center, Loader, Table, Checkbox, Pagination, Title, Button, Alert, CopyButton, Tooltip, Divider, Tabs, Textarea } from '@mantine/core';
13
+ import { IconBriefcase, IconHeartbeat, IconFlag, IconFileText, IconCalendar, IconInbox, IconLock, IconAlertTriangle, IconCircleCheck, IconClock, IconChevronDown, IconChevronRight, IconDownload, IconListCheck, IconMessageCircle, IconChecklist, IconSearch, IconArrowLeft, IconCheck, IconCopy, IconPlus, IconX, IconNotes } from '@tabler/icons-react';
14
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
15
+ import { useState, useMemo } from 'react';
16
+
17
+ function ringColor(completed, total) {
18
+ if (total === 0) return "gray";
19
+ const pct = completed / total;
20
+ if (pct > 0.75) return "green";
21
+ if (pct > 0.5) return "yellow";
22
+ return "gray";
23
+ }
24
+ function ringValue(completed, total) {
25
+ if (total === 0) return 0;
26
+ return Math.round(completed / total * 100);
27
+ }
28
+ function formatDate2(iso) {
29
+ return new Date(iso).toLocaleDateString();
30
+ }
31
+ function daysRelative(targetEndDate) {
32
+ const now = /* @__PURE__ */ new Date();
33
+ const end = new Date(targetEndDate);
34
+ const diffMs = end.getTime() - now.getTime();
35
+ const diffDays = Math.ceil(diffMs / 864e5);
36
+ if (diffDays >= 0) {
37
+ return { label: `${diffDays} day${diffDays === 1 ? "" : "s"} remaining`, overdue: false };
38
+ }
39
+ return { label: `${Math.abs(diffDays)} day${Math.abs(diffDays) === 1 ? "" : "s"} overdue`, overdue: true };
40
+ }
41
+ function HealthStatusCard({
42
+ status,
43
+ milestoneCount,
44
+ completedMilestones,
45
+ taskCount,
46
+ completedTasks,
47
+ startDate,
48
+ targetEndDate
49
+ }) {
50
+ const milestoneRingValue = ringValue(completedMilestones, milestoneCount);
51
+ const taskRingValue = ringValue(completedTasks, taskCount);
52
+ const timeInfo = targetEndDate ? daysRelative(targetEndDate) : null;
53
+ return /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
54
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
55
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
56
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconHeartbeat, { size: 14 }) }),
57
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Status" })
58
+ ] }),
59
+ /* @__PURE__ */ jsx(StatusBadge, { status, size: "md" })
60
+ ] }) }),
61
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
62
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
63
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFlag, { size: 14 }) }),
64
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Milestones" })
65
+ ] }),
66
+ /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
67
+ /* @__PURE__ */ jsx(
68
+ RingProgress,
69
+ {
70
+ size: 48,
71
+ thickness: 5,
72
+ sections: [{ value: milestoneRingValue, color: ringColor(completedMilestones, milestoneCount) }]
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
76
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
77
+ completedMilestones,
78
+ "/",
79
+ milestoneCount
80
+ ] }),
81
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "complete" })
82
+ ] })
83
+ ] })
84
+ ] }) }),
85
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
86
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
87
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFileText, { size: 14 }) }),
88
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Tasks" })
89
+ ] }),
90
+ /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
91
+ /* @__PURE__ */ jsx(
92
+ RingProgress,
93
+ {
94
+ size: 48,
95
+ thickness: 5,
96
+ sections: [{ value: taskRingValue, color: ringColor(completedTasks, taskCount) }]
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
100
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
101
+ completedTasks,
102
+ "/",
103
+ taskCount
104
+ ] }),
105
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "approved" })
106
+ ] })
107
+ ] })
108
+ ] }) }),
109
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
110
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
111
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconCalendar, { size: 14 }) }),
112
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Timeline" })
113
+ ] }),
114
+ /* @__PURE__ */ jsx(Stack, { gap: 2, children: startDate && targetEndDate ? /* @__PURE__ */ jsxs(Fragment, { children: [
115
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
116
+ formatDate2(startDate),
117
+ " \u2192 ",
118
+ formatDate2(targetEndDate)
119
+ ] }),
120
+ timeInfo && /* @__PURE__ */ jsx(Text, { size: "xs", c: timeInfo.overdue ? "red" : "dimmed", children: timeInfo.label })
121
+ ] }) : startDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
122
+ "Started ",
123
+ formatDate2(startDate)
124
+ ] }) : targetEndDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
125
+ "Due ",
126
+ formatDate2(targetEndDate)
127
+ ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No dates set" }) })
128
+ ] }) })
129
+ ] });
130
+ }
131
+
132
+ // src/features/delivery/_shared.ts
133
+ var projectStatusColors = {
134
+ active: "blue",
135
+ on_track: "green",
136
+ at_risk: "yellow",
137
+ blocked: "red",
138
+ completed: "teal",
139
+ paused: "gray"
140
+ };
141
+ var milestoneStatusColors = {
142
+ upcoming: "gray",
143
+ in_progress: "blue",
144
+ completed: "green",
145
+ overdue: "red",
146
+ blocked: "orange"
147
+ };
148
+ var taskStatusColors = {
149
+ pending: "gray",
150
+ planned: "gray",
151
+ in_progress: "blue",
152
+ submitted: "yellow",
153
+ approved: "green",
154
+ rejected: "red",
155
+ revision_requested: "orange"
156
+ };
157
+ var taskTypeColors = {
158
+ documentation: "blue",
159
+ code: "violet",
160
+ report: "cyan",
161
+ design: "pink",
162
+ other: "gray"
163
+ };
164
+ var noteTypeColors = {
165
+ call_note: "blue",
166
+ status_update: "green",
167
+ issue: "yellow",
168
+ blocker: "red"
169
+ };
170
+ function formatStatusLabel(status) {
171
+ return status.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
172
+ }
173
+ function calculateProgress(completed, total) {
174
+ if (total === 0) return 0;
175
+ return Math.round(completed / total * 100);
176
+ }
177
+ var MILESTONE_ICONS = {
178
+ upcoming: IconClock,
179
+ in_progress: IconFlag,
180
+ completed: IconCircleCheck,
181
+ overdue: IconAlertTriangle,
182
+ blocked: IconLock
183
+ };
184
+ function MilestoneTimeline({ milestones, tasks }) {
185
+ const sorted = [...milestones].sort((a, b) => a.sequence - b.sequence);
186
+ const defaultExpanded = new Set(sorted.filter((m) => m.status === "in_progress").map((m) => m.id));
187
+ const [expanded, setExpanded] = useState(defaultExpanded);
188
+ if (sorted.length === 0) {
189
+ return /* @__PURE__ */ jsx(EmptyState, { icon: IconInbox, title: "No milestones yet" });
190
+ }
191
+ function toggleMilestone(id) {
192
+ setExpanded((prev) => {
193
+ const next = new Set(prev);
194
+ if (next.has(id)) {
195
+ next.delete(id);
196
+ } else {
197
+ next.add(id);
198
+ }
199
+ return next;
200
+ });
201
+ }
202
+ const activeIndex = sorted.reduce((last, m, i) => m.status !== "upcoming" ? i : last, -1);
203
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Timeline, { active: activeIndex, bulletSize: 28, lineWidth: 2, color: "var(--color-primary)", children: sorted.map((milestone) => {
204
+ const Icon = MILESTONE_ICONS[milestone.status] || IconFlag;
205
+ const color = milestoneStatusColors[milestone.status] || "gray";
206
+ const isExpanded = expanded.has(milestone.id);
207
+ const milestoneTasks = tasks.filter((t) => t.milestone_id === milestone.id);
208
+ const ChevronIcon = isExpanded ? IconChevronDown : IconChevronRight;
209
+ return /* @__PURE__ */ jsx(
210
+ Timeline.Item,
211
+ {
212
+ bullet: /* @__PURE__ */ jsx(ThemeIcon, { size: 28, variant: "filled", color, radius: "xl", children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
213
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
214
+ /* @__PURE__ */ jsxs(
215
+ Group,
216
+ {
217
+ gap: 8,
218
+ style: { cursor: "pointer", userSelect: "none" },
219
+ onClick: () => toggleMilestone(milestone.id),
220
+ children: [
221
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 700, children: milestone.name }),
222
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color, children: formatStatusLabel(milestone.status) }),
223
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 16, variant: "transparent", color: "dimmed", children: /* @__PURE__ */ jsx(ChevronIcon, { size: 14 }) })
224
+ ]
225
+ }
226
+ ),
227
+ milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
228
+ "Due ",
229
+ new Date(milestone.due_date).toLocaleDateString()
230
+ ] }),
231
+ /* @__PURE__ */ jsx(Collapse, { in: isExpanded, children: /* @__PURE__ */ jsx(Stack, { gap: 6, mt: 6, children: milestoneTasks.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "No tasks" }) : milestoneTasks.map((task) => /* @__PURE__ */ jsxs(Card, { withBorder: true, p: "xs", children: [
232
+ /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", children: [
233
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, style: { flex: 1, minWidth: 0 }, children: task.name }),
234
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
235
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskStatusColors[task.status] || "gray", children: formatStatusLabel(task.status) })
236
+ ] }),
237
+ task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", mt: 4, children: [
238
+ "Due ",
239
+ new Date(task.due_date).toLocaleDateString()
240
+ ] })
241
+ ] }, task.id)) }) })
242
+ ] })
243
+ },
244
+ milestone.id
245
+ );
246
+ }) }) });
247
+ }
248
+ function TaskCard({ task }) {
249
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "sm", p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
250
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
251
+ /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
252
+ /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", style: { flexShrink: 0 }, children: task.name }),
253
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
254
+ /* @__PURE__ */ jsx(StatusBadge, { status: task.status, size: "xs" })
255
+ ] }),
256
+ task.file_url && /* @__PURE__ */ jsx(
257
+ ActionIcon,
258
+ {
259
+ component: "a",
260
+ href: task.file_url,
261
+ target: "_blank",
262
+ rel: "noopener noreferrer",
263
+ variant: "light",
264
+ size: "sm",
265
+ "aria-label": "Download file",
266
+ children: /* @__PURE__ */ jsx(IconDownload, { size: 14 })
267
+ }
268
+ )
269
+ ] }),
270
+ task.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: task.description }),
271
+ task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
272
+ "Due ",
273
+ new Date(task.due_date).toLocaleDateString()
274
+ ] })
275
+ ] }) });
276
+ }
277
+ var ProjectsSidebarTop = () => {
278
+ return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconBriefcase, label: "Projects" });
279
+ };
280
+ var PROJECT_ITEMS = [{ label: "Projects", to: "/projects", icon: IconBriefcase, exact: true }];
281
+ var WORK_ITEMS = [
282
+ { label: "Tasks", to: "/projects/tasks", icon: IconChecklist, exact: false },
283
+ { label: "Milestones", to: "/projects/milestones", icon: IconFlag, exact: false }
284
+ ];
285
+ var COMMUNICATION_ITEMS = [{ label: "Notes", to: "/projects/notes", icon: IconNotes, exact: false }];
286
+ var ProjectsSidebarMiddle = ({ currentPath, onNavigate } = {}) => {
287
+ const { currentPath: routerCurrentPath, navigate } = useRouterContext();
288
+ const resolvedCurrentPath = currentPath ?? routerCurrentPath;
289
+ const resolvedNavigate = onNavigate ?? navigate;
290
+ const renderItems = (items) => items.map((item) => {
291
+ const isActive = item.exact ? resolvedCurrentPath === item.to || resolvedCurrentPath === `${item.to}/` : resolvedCurrentPath.startsWith(item.to);
292
+ return /* @__PURE__ */ jsx(
293
+ SubshellNavItem,
294
+ {
295
+ icon: item.icon,
296
+ label: item.label,
297
+ isActive,
298
+ onClick: () => resolvedNavigate(item.to)
299
+ },
300
+ item.to
301
+ );
302
+ });
303
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
304
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(PROJECT_ITEMS) }),
305
+ /* @__PURE__ */ jsxs(Fragment, { children: [
306
+ /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconListCheck, label: "Work", withTopBorder: true }),
307
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(WORK_ITEMS) })
308
+ ] }),
309
+ /* @__PURE__ */ jsxs(Fragment, { children: [
310
+ /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconMessageCircle, label: "Communication", withTopBorder: true }),
311
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(COMMUNICATION_ITEMS) })
312
+ ] })
313
+ ] });
314
+ };
315
+ var ProjectsSidebar = () => {
316
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
317
+ /* @__PURE__ */ jsx(ProjectsSidebarTop, {}),
318
+ /* @__PURE__ */ jsx(ProjectsSidebarMiddle, {})
319
+ ] });
320
+ };
321
+ var deliveryManifest = {
322
+ key: "delivery",
323
+ label: "Projects",
324
+ navEntry: {
325
+ label: "Projects",
326
+ icon: IconBriefcase,
327
+ link: "/projects",
328
+ featureKey: "delivery"
329
+ },
330
+ sidebar: ProjectsSidebar,
331
+ subshellRoutes: ["/projects"]
332
+ };
333
+ function AllTasksPage() {
334
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
335
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "All Tasks", caption: "Project tasks are scoped to an individual project" }) }),
336
+ /* @__PURE__ */ jsxs(Paper, { children: [
337
+ /* @__PURE__ */ jsx(
338
+ EmptyState,
339
+ {
340
+ icon: IconChecklist,
341
+ title: "Project task lists are not aggregated here",
342
+ description: "Open a project detail view to inspect its tasks. The shared contract does not provide a cross-project task list.",
343
+ py: "xl"
344
+ }
345
+ ),
346
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", pb: "xl", children: "This page is kept for route parity only." })
347
+ ] })
348
+ ] }) });
349
+ }
350
+ function UpcomingMilestonesPage() {
351
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
352
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Upcoming Milestones", caption: "Milestones are scoped to an individual project" }) }),
353
+ /* @__PURE__ */ jsxs(Paper, { children: [
354
+ /* @__PURE__ */ jsx(
355
+ EmptyState,
356
+ {
357
+ icon: IconFlag,
358
+ title: "Project milestone lists are not aggregated here",
359
+ description: "Open a project detail view to inspect its milestones. The shared contract does not provide a cross-project milestone list.",
360
+ py: "xl"
361
+ }
362
+ ),
363
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", pb: "xl", children: "This page is kept for route parity only." })
364
+ ] })
365
+ ] }) });
366
+ }
367
+ var STATUS_OPTIONS = [
368
+ { value: "active", label: "Active" },
369
+ { value: "on_track", label: "On Track" },
370
+ { value: "at_risk", label: "At Risk" },
371
+ { value: "blocked", label: "Blocked" },
372
+ { value: "completed", label: "Completed" },
373
+ { value: "paused", label: "Paused" }
374
+ ];
375
+ function ProjectsListPage({ onProjectClick } = {}) {
376
+ const [statusFilter, setStatusFilter] = useState(null);
377
+ const [searchQuery, setSearchQuery] = useState("");
378
+ const [showBatchDelete, setShowBatchDelete] = useState(false);
379
+ const { data: projects, isLoading, error } = useProjects();
380
+ const deleteProject = useDeleteProject();
381
+ const { sort, toggleSort } = useTableSort("updated");
382
+ const filteredProjects = useMemo(() => {
383
+ const source = projects ?? [];
384
+ const query = searchQuery.trim().toLowerCase();
385
+ return source.filter((project) => {
386
+ const matchesStatus = !statusFilter || project.status === statusFilter;
387
+ const matchesSearch = !query || project.name.toLowerCase().includes(query);
388
+ return matchesStatus && matchesSearch;
389
+ });
390
+ }, [projects, searchQuery, statusFilter]);
391
+ const sortAccessors = useMemo(
392
+ () => ({
393
+ name: (p) => p.name || "",
394
+ status: (p) => p.status || "",
395
+ updated: (p) => p.updated_at || ""
396
+ }),
397
+ []
398
+ );
399
+ const sortedProjects = useMemo(() => sortData(filteredProjects, sort, sortAccessors), [filteredProjects, sort, sortAccessors]);
400
+ const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [statusFilter, searchQuery], sortedProjects.length);
401
+ const paginatedProjects = useMemo(
402
+ () => sortedProjects.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
403
+ [sortedProjects, pagination.offset]
404
+ );
405
+ const selection = useTableSelection(paginatedProjects, sortedProjects);
406
+ const handleDeleteSelected = async () => {
407
+ await Promise.all([...selection.selectedIds].map((projectId) => deleteProject.mutateAsync(projectId)));
408
+ setShowBatchDelete(false);
409
+ selection.clear();
410
+ };
411
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
412
+ /* @__PURE__ */ jsxs(PageContainer, { children: [
413
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Projects", caption: "Client delivery tracking and milestone management" }) }),
414
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
415
+ /* @__PURE__ */ jsxs(
416
+ FilterBar,
417
+ {
418
+ actions: /* @__PURE__ */ jsx(
419
+ TableSelectionToolbar,
420
+ {
421
+ selectedCount: selection.selectedCount,
422
+ onDelete: () => setShowBatchDelete(true),
423
+ isDeleting: deleteProject.isPending
424
+ }
425
+ ),
426
+ children: [
427
+ /* @__PURE__ */ jsx(
428
+ Select,
429
+ {
430
+ placeholder: "All Statuses",
431
+ data: STATUS_OPTIONS,
432
+ style: { minWidth: 180 },
433
+ size: "sm",
434
+ clearable: true,
435
+ value: statusFilter,
436
+ onChange: setStatusFilter
437
+ }
438
+ ),
439
+ /* @__PURE__ */ jsx(
440
+ TextInput,
441
+ {
442
+ placeholder: "Search by name...",
443
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
444
+ style: { minWidth: 250 },
445
+ size: "sm",
446
+ value: searchQuery,
447
+ onChange: (e) => setSearchQuery(e.currentTarget.value)
448
+ }
449
+ )
450
+ ]
451
+ }
452
+ ),
453
+ isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : error ? /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load projects" }) : !sortedProjects.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconBriefcase, title: "No projects found" }) : /* @__PURE__ */ jsxs(Table, { children: [
454
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
455
+ /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
456
+ Checkbox,
457
+ {
458
+ checked: selection.isPageAllSelected,
459
+ indeterminate: selection.isPagePartiallySelected,
460
+ onChange: selection.togglePage
461
+ }
462
+ ) }),
463
+ /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
464
+ /* @__PURE__ */ jsx(SortableHeader, { column: "status", sort, onToggle: toggleSort, children: "Status" }),
465
+ /* @__PURE__ */ jsx(Table.Th, { children: "Milestones" }),
466
+ /* @__PURE__ */ jsx(Table.Th, { children: "Tasks" }),
467
+ /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, children: "Updated" })
468
+ ] }) }),
469
+ /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedProjects.map((project) => {
470
+ const completedMilestones = project.completedMilestones ?? 0;
471
+ const milestoneCount = project.milestoneCount ?? 0;
472
+ const completedTasks = project.completedTasks ?? 0;
473
+ const taskCount = project.taskCount ?? 0;
474
+ const rowProps = onProjectClick ? {
475
+ style: { cursor: "pointer" },
476
+ onClick: () => onProjectClick(project.id)
477
+ } : void 0;
478
+ return /* @__PURE__ */ jsxs(Table.Tr, { ...rowProps, children: [
479
+ /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
480
+ Checkbox,
481
+ {
482
+ checked: selection.isSelected(project.id),
483
+ onChange: () => selection.toggle(project.id)
484
+ }
485
+ ) }),
486
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: project.name }) }),
487
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", size: "sm", children: formatStatusLabel(project.status || "unknown") }) }),
488
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
489
+ completedMilestones,
490
+ "/",
491
+ milestoneCount
492
+ ] }) }),
493
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
494
+ completedTasks,
495
+ "/",
496
+ taskCount
497
+ ] }) }),
498
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(project.updated_at) }) })
499
+ ] }, project.id);
500
+ }) })
501
+ ] }),
502
+ sortedProjects.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
503
+ Pagination,
504
+ {
505
+ value: pagination.page,
506
+ onChange: pagination.setPage,
507
+ total: pagination.totalPages(sortedProjects.length),
508
+ size: "sm"
509
+ }
510
+ ) })
511
+ ] }) })
512
+ ] }),
513
+ /* @__PURE__ */ jsx(
514
+ CustomModal,
515
+ {
516
+ opened: showBatchDelete,
517
+ onClose: () => !deleteProject.isPending && setShowBatchDelete(false),
518
+ size: "sm",
519
+ loading: deleteProject.isPending,
520
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
521
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
522
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
523
+ /* @__PURE__ */ jsxs(Title, { order: 4, children: [
524
+ "Delete ",
525
+ selection.selectedCount,
526
+ " Projects"
527
+ ] })
528
+ ] }),
529
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
530
+ "Are you sure you want to delete",
531
+ " ",
532
+ /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
533
+ " ",
534
+ "selected projects?"
535
+ ] }),
536
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
537
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
538
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteProject.isPending, children: "Cancel" }),
539
+ /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteProject.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
540
+ ] })
541
+ ] })
542
+ }
543
+ )
544
+ ] });
545
+ }
546
+ var noteTypeOptions = [
547
+ { value: "call_note", label: "Call Note" },
548
+ { value: "status_update", label: "Status Update" },
549
+ { value: "issue", label: "Issue" },
550
+ { value: "blocker", label: "Blocker" }
551
+ ];
552
+ function parseChecklist(milestone) {
553
+ const raw = milestone.checklist;
554
+ if (!Array.isArray(raw)) return [];
555
+ return raw;
556
+ }
557
+ function MilestoneChecklist({ milestone }) {
558
+ const items = parseChecklist(milestone);
559
+ const { mutate: updateMilestone } = useUpdateMilestone();
560
+ const [newItemLabel, setNewItemLabel] = useState("");
561
+ const [adding, setAdding] = useState(false);
562
+ function saveChecklist(updated) {
563
+ const allComplete = updated.length > 0 && updated.every((item) => item.completed);
564
+ const wasComplete = milestone.status === "completed";
565
+ const updates = {
566
+ checklist: updated
567
+ };
568
+ if (allComplete && !wasComplete) {
569
+ updates.status = "completed";
570
+ updates.completed_at = (/* @__PURE__ */ new Date()).toISOString();
571
+ } else if (!allComplete && wasComplete) {
572
+ updates.status = "in_progress";
573
+ updates.completed_at = null;
574
+ }
575
+ updateMilestone({ id: milestone.id, updates });
576
+ }
577
+ function toggleItem(itemId) {
578
+ const updated = items.map((item) => item.id === itemId ? { ...item, completed: !item.completed } : item);
579
+ saveChecklist(updated);
580
+ }
581
+ function addItem() {
582
+ const label = newItemLabel.trim();
583
+ if (!label) return;
584
+ const newItem = { id: crypto.randomUUID(), label, completed: false };
585
+ saveChecklist([...items, newItem]);
586
+ setNewItemLabel("");
587
+ }
588
+ function removeItem(itemId) {
589
+ saveChecklist(items.filter((item) => item.id !== itemId));
590
+ }
591
+ const completedCount = items.filter((item) => item.completed).length;
592
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", mt: "sm", children: [
593
+ items.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
594
+ completedCount,
595
+ "/",
596
+ items.length,
597
+ " complete"
598
+ ] }),
599
+ items.map((item) => /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
600
+ /* @__PURE__ */ jsx(
601
+ Checkbox,
602
+ {
603
+ size: "xs",
604
+ checked: item.completed,
605
+ onChange: () => toggleItem(item.id),
606
+ label: /* @__PURE__ */ jsx(
607
+ Text,
608
+ {
609
+ size: "sm",
610
+ td: item.completed ? "line-through" : void 0,
611
+ c: item.completed ? "dimmed" : "var(--color-text-subtle)",
612
+ children: item.label
613
+ }
614
+ ),
615
+ styles: { input: { cursor: "pointer" }, label: { cursor: "pointer" } }
616
+ }
617
+ ),
618
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "xs", onClick: () => removeItem(item.id), style: { opacity: 0.4 }, children: /* @__PURE__ */ jsx(IconX, { size: 12 }) })
619
+ ] }, item.id)),
620
+ adding ? /* @__PURE__ */ jsx(
621
+ TextInput,
622
+ {
623
+ size: "xs",
624
+ placeholder: "Item label...",
625
+ value: newItemLabel,
626
+ onChange: (event) => setNewItemLabel(event.target.value),
627
+ onKeyDown: (event) => {
628
+ if (event.key === "Enter") {
629
+ addItem();
630
+ setAdding(false);
631
+ }
632
+ if (event.key === "Escape") {
633
+ setNewItemLabel("");
634
+ setAdding(false);
635
+ }
636
+ },
637
+ onBlur: () => {
638
+ if (newItemLabel.trim()) addItem();
639
+ setAdding(false);
640
+ },
641
+ autoFocus: true,
642
+ style: { maxWidth: 250 }
643
+ }
644
+ ) : /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Button, { variant: "subtle", size: "compact-xs", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 12 }), onClick: () => setAdding(true), children: "Add item" }) })
645
+ ] });
646
+ }
647
+ function AddNoteModal({ opened, onClose, projectId }) {
648
+ const [noteType, setNoteType] = useState("call_note");
649
+ const [content, setContent] = useState("");
650
+ const [summary, setSummary] = useState("");
651
+ const { mutate: createNote, isPending } = useCreateNote();
652
+ function handleSubmit() {
653
+ if (!content.trim() || !noteType) return;
654
+ createNote(
655
+ {
656
+ project_id: projectId,
657
+ type: noteType,
658
+ content: content.trim(),
659
+ summary: summary.trim() || null
660
+ },
661
+ {
662
+ onSuccess: () => {
663
+ setContent("");
664
+ setSummary("");
665
+ setNoteType("call_note");
666
+ onClose();
667
+ }
668
+ }
669
+ );
670
+ }
671
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose, size: "md", loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
672
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
673
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Add Note" }),
674
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, disabled: isPending, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
675
+ ] }),
676
+ /* @__PURE__ */ jsx(Select, { label: "Type", data: noteTypeOptions, value: noteType, onChange: setNoteType, required: true }),
677
+ /* @__PURE__ */ jsx(
678
+ TextInput,
679
+ {
680
+ label: "Summary",
681
+ placeholder: "Brief summary (optional)",
682
+ value: summary,
683
+ onChange: (event) => setSummary(event.target.value)
684
+ }
685
+ ),
686
+ /* @__PURE__ */ jsx(
687
+ Textarea,
688
+ {
689
+ label: "Content",
690
+ placeholder: "Note content...",
691
+ minRows: 4,
692
+ value: content,
693
+ onChange: (event) => setContent(event.target.value),
694
+ required: true
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
698
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: onClose, disabled: isPending, children: "Cancel" }),
699
+ /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: isPending, disabled: !content.trim() || !noteType, children: "Add Note" })
700
+ ] })
701
+ ] }) });
702
+ }
703
+ function ProjectDetailPage({ projectId, onBack, backLabel = "Projects" }) {
704
+ const { data: project, isLoading, error } = useProject(projectId);
705
+ const { data: notes } = useProjectNotes({ projectId });
706
+ const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject$1();
707
+ const [addNoteOpen, setAddNoteOpen] = useState(false);
708
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
709
+ if (isLoading) {
710
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Loader, {}) }) });
711
+ }
712
+ if (error) {
713
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "red", children: "Error loading project" }) }) });
714
+ }
715
+ if (!project) {
716
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "Project not found" }) }) });
717
+ }
718
+ const milestones = project.milestones ?? [];
719
+ const tasks = project.tasks ?? [];
720
+ const totalMilestones = milestones.length;
721
+ const completedMilestones = milestones.filter((milestone) => milestone.status === "completed").length;
722
+ const totalTasks = tasks.length;
723
+ const approvedTasks = tasks.filter((task) => task.status === "approved").length;
724
+ const milestoneProgress = calculateProgress(completedMilestones, totalMilestones);
725
+ const taskProgress = calculateProgress(approvedTasks, totalTasks);
726
+ const companyName = project.company?.name ?? null;
727
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
728
+ /* @__PURE__ */ jsx(
729
+ PageTitleCaption,
730
+ {
731
+ title: project.name,
732
+ caption: [companyName, formatStatusLabel(project.status || "unknown")].filter(Boolean).join(" - "),
733
+ rightSection: /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }), onClick: onBack, children: backLabel }) })
734
+ }
735
+ ),
736
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
737
+ /* @__PURE__ */ jsx(
738
+ StatCard,
739
+ {
740
+ variant: "hero",
741
+ icon: IconHeartbeat,
742
+ value: formatStatusLabel(project.status || "unknown"),
743
+ label: "Status",
744
+ valueColor: `var(--mantine-color-${projectStatusColors[project.status || ""] || "gray"}-6)`
745
+ }
746
+ ),
747
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconFlag, value: `${completedMilestones}/${totalMilestones}`, label: "Milestones", children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
748
+ milestoneProgress,
749
+ "% complete"
750
+ ] }) }),
751
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconChecklist, value: `${approvedTasks}/${totalTasks}`, label: "Tasks", children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
752
+ taskProgress,
753
+ "% complete"
754
+ ] }) }),
755
+ /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconCalendar, value: project.start_date ? formatDate(project.start_date) : "-", label: "Timeline", children: project.target_end_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
756
+ "to ",
757
+ formatDate(project.target_end_date)
758
+ ] }) })
759
+ ] }),
760
+ /* @__PURE__ */ jsxs(
761
+ Paper,
762
+ {
763
+ p: "md",
764
+ style: {
765
+ border: "1px solid var(--color-border)",
766
+ borderRadius: "var(--mantine-radius-default)"
767
+ },
768
+ children: [
769
+ /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
770
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "wrap", children: [
771
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
772
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Project Details" }),
773
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", children: formatStatusLabel(project.status || "unknown") })
774
+ ] }),
775
+ /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
776
+ /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", c: "var(--color-text-subtle)", children: project.id }),
777
+ /* @__PURE__ */ jsx(CopyButton, { value: project.id, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { onClick: copy, variant: "subtle", size: "xs", color: copied ? "green" : "var(--color-text-subtle)", children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 }) }) }) })
778
+ ] })
779
+ ] }),
780
+ /* @__PURE__ */ jsxs(Group, { gap: "lg", children: [
781
+ project.start_date && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
782
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Started:" }),
783
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.start_date) })
784
+ ] }),
785
+ project.target_end_date && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
786
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Target:" }),
787
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.target_end_date) })
788
+ ] }),
789
+ project.contract_value != null && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
790
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Contract:" }),
791
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
792
+ "$",
793
+ project.contract_value.toLocaleString(),
794
+ project.metadata && project.metadata.rate_type === "hourly" ? "/hr" : ""
795
+ ] })
796
+ ] }),
797
+ project.company && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
798
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Company:" }),
799
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: project.company.name })
800
+ ] })
801
+ ] })
802
+ ] }),
803
+ /* @__PURE__ */ jsx(Divider, { my: "md" }),
804
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "status", children: [
805
+ /* @__PURE__ */ jsxs(Tabs.List, { children: [
806
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "status", children: "Status" }),
807
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "details", children: "Details" }),
808
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "notes", children: "Notes" }),
809
+ /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", children: "Activity" })
810
+ ] }),
811
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "status", pt: "md", children: milestones.length === 0 ? /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "No milestones yet." }) : /* @__PURE__ */ jsx(
812
+ Timeline,
813
+ {
814
+ active: completedMilestones,
815
+ bulletSize: 20,
816
+ color: "var(--color-primary)",
817
+ styles: {
818
+ itemBullet: {
819
+ borderColor: "color-mix(in srgb, var(--color-primary) 40%, transparent)",
820
+ backgroundColor: "var(--color-surface)"
821
+ },
822
+ item: {
823
+ "--_item-border-color": "color-mix(in srgb, var(--color-primary) 25%, transparent)"
824
+ },
825
+ itemTitle: {
826
+ color: "var(--color-text)"
827
+ }
828
+ },
829
+ children: milestones.map((milestone) => {
830
+ const milestoneTasks = tasks.filter((task) => task.milestone_id === milestone.id);
831
+ return /* @__PURE__ */ jsxs(
832
+ Timeline.Item,
833
+ {
834
+ bullet: milestone.status === "completed" ? /* @__PURE__ */ jsx(
835
+ "div",
836
+ {
837
+ style: {
838
+ width: 12,
839
+ height: 12,
840
+ borderRadius: "50%",
841
+ backgroundColor: "var(--color-primary)",
842
+ boxShadow: "0 0 8px var(--color-primary), 0 0 16px color-mix(in srgb, var(--color-primary) 50%, transparent)"
843
+ }
844
+ }
845
+ ) : void 0,
846
+ title: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
847
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: milestone.name }),
848
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: milestoneStatusColors[milestone.status || ""] || "gray", size: "sm", children: formatStatusLabel(milestone.status || "unknown") }),
849
+ milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
850
+ "Due ",
851
+ formatDate(milestone.due_date)
852
+ ] })
853
+ ] }),
854
+ children: [
855
+ milestone.description && /* @__PURE__ */ jsx(Text, { size: "sm", mt: "xs", children: milestone.description }),
856
+ /* @__PURE__ */ jsx(MilestoneChecklist, { milestone }),
857
+ milestoneTasks.length > 0 && /* @__PURE__ */ jsx(Stack, { gap: "xs", mt: "sm", children: milestoneTasks.map((task) => /* @__PURE__ */ jsx(Card, { withBorder: true, p: "xs", children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "space-between", children: [
858
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: task.name }),
859
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
860
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskTypeColors[task.type || ""] || "gray", size: "xs", children: formatStatusLabel(task.type || "other") }),
861
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskStatusColors[task.status || ""] || "gray", size: "xs", children: formatStatusLabel(task.status || "pending") })
862
+ ] })
863
+ ] }) }, task.id)) })
864
+ ]
865
+ },
866
+ milestone.id
867
+ );
868
+ })
869
+ }
870
+ ) }),
871
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "details", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
872
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
873
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Project Details" }),
874
+ project.description && /* @__PURE__ */ jsxs("div", { children: [
875
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Description" }),
876
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: project.description })
877
+ ] }),
878
+ project.contract_value != null && /* @__PURE__ */ jsxs("div", { children: [
879
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Contract Value" }),
880
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
881
+ "$",
882
+ project.contract_value.toLocaleString()
883
+ ] })
884
+ ] }),
885
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 3, children: [
886
+ /* @__PURE__ */ jsxs("div", { children: [
887
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Start Date" }),
888
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.start_date) })
889
+ ] }),
890
+ /* @__PURE__ */ jsxs("div", { children: [
891
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Target End Date" }),
892
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.target_end_date) })
893
+ ] }),
894
+ /* @__PURE__ */ jsxs("div", { children: [
895
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Actual End Date" }),
896
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.actual_end_date) })
897
+ ] })
898
+ ] })
899
+ ] }) }),
900
+ project.company && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
901
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Company" }),
902
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: project.company.name }),
903
+ project.company.domain && /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: project.company.domain })
904
+ ] }) })
905
+ ] }) }),
906
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "notes", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
907
+ /* @__PURE__ */ jsx(Group, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setAddNoteOpen(true), children: "Add Note" }) }),
908
+ !notes?.length ? /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "No notes yet." }) : /* @__PURE__ */ jsx(Stack, { gap: "sm", children: notes.map((note) => /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
909
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
910
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: noteTypeColors[note.type || ""] || "gray", size: "sm", children: formatStatusLabel(note.type || "note") }),
911
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatTimeAgo(note.occurred_at) })
912
+ ] }),
913
+ note.summary && /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: note.summary }),
914
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: note.content })
915
+ ] }) }, note.id)) })
916
+ ] }) }),
917
+ /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "Activity feed coming soon." }) })
918
+ ] })
919
+ ]
920
+ }
921
+ ),
922
+ /* @__PURE__ */ jsx(AddNoteModal, { opened: addNoteOpen, onClose: () => setAddNoteOpen(false), projectId }),
923
+ /* @__PURE__ */ jsx(CustomModal, { opened: deleteModalOpen, onClose: () => setDeleteModalOpen(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
924
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
925
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Delete project" }),
926
+ /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: () => setDeleteModalOpen(false), children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
927
+ ] }),
928
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
929
+ "Are you sure you want to delete ",
930
+ /* @__PURE__ */ jsx("strong", { children: project.name }),
931
+ "? This cannot be undone."
932
+ ] }),
933
+ /* @__PURE__ */ jsx(Divider, {}),
934
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
935
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setDeleteModalOpen(false), disabled: isDeleting, children: "Cancel" }),
936
+ /* @__PURE__ */ jsx(
937
+ Button,
938
+ {
939
+ color: "red",
940
+ loading: isDeleting,
941
+ onClick: () => deleteProject(project.id, {
942
+ onSuccess: () => {
943
+ setDeleteModalOpen(false);
944
+ onBack();
945
+ }
946
+ }),
947
+ children: "Delete project"
948
+ }
949
+ )
950
+ ] })
951
+ ] }) })
952
+ ] }) }) });
953
+ }
954
+
955
+ export { AllTasksPage, HealthStatusCard, MilestoneTimeline, ProjectDetailPage, ProjectsListPage, ProjectsSidebar, ProjectsSidebarMiddle, ProjectsSidebarTop, TaskCard, UpcomingMilestonesPage, calculateProgress, deliveryManifest, formatStatusLabel, milestoneStatusColors, noteTypeColors, projectStatusColors, taskStatusColors, taskTypeColors };