@elevasis/ui 2.34.0 → 2.35.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 (106) hide show
  1. package/dist/api/index.js +3 -3
  2. package/dist/app/index.d.ts +12 -12
  3. package/dist/app/index.js +25 -23
  4. package/dist/charts/index.js +3 -5
  5. package/dist/chunk-26HFM4MH.js +41449 -0
  6. package/dist/{chunk-DTFKWZ7A.js → chunk-4U3XAWCN.js} +502 -484
  7. package/dist/{chunk-ND5TDV2J.js → chunk-57OZ3AEG.js} +1 -1
  8. package/dist/{chunk-E4WQGJNS.js → chunk-7FPLLSHN.js} +14 -1
  9. package/dist/{chunk-RQA2EVN3.js → chunk-AKW7KISS.js} +39 -3
  10. package/dist/chunk-AUDNF2Q7.js +2050 -0
  11. package/dist/{chunk-TYRUKGGD.js → chunk-GX6XBRRF.js} +1 -2
  12. package/dist/{chunk-V6SZ4ECN.js → chunk-LUYVRATI.js} +257 -6
  13. package/dist/{chunk-X4WBGKJQ.js → chunk-R3VCBZDC.js} +50 -3
  14. package/dist/chunk-SIQ3P4OR.js +1764 -0
  15. package/dist/{chunk-RIAXZ6AH.js → chunk-VDOOGGBA.js} +1 -1
  16. package/dist/{chunk-3FV6HBXS.js → chunk-WF7CONXF.js} +23 -23
  17. package/dist/{chunk-3QXJK5IY.js → chunk-YYX7OPZQ.js} +1 -1
  18. package/dist/components/index.d.ts +69 -69
  19. package/dist/components/index.js +20 -2795
  20. package/dist/components/navigation/index.js +25 -5
  21. package/dist/execution/index.d.ts +9 -9
  22. package/dist/execution/index.js +1 -2
  23. package/dist/features/auth/index.js +23 -2
  24. package/dist/features/clients/index.js +20 -26
  25. package/dist/features/crm/index.js +20 -30
  26. package/dist/features/dashboard/index.d.ts +68 -68
  27. package/dist/features/dashboard/index.js +20 -28
  28. package/dist/features/delivery/index.js +20 -30
  29. package/dist/features/knowledge/index.js +25 -9
  30. package/dist/features/lead-gen/index.d.ts +9 -9
  31. package/dist/features/lead-gen/index.js +20 -31
  32. package/dist/features/monitoring/index.js +20 -30
  33. package/dist/features/monitoring/requests/index.js +20 -25
  34. package/dist/features/operations/index.d.ts +153 -153
  35. package/dist/features/operations/index.js +18 -37
  36. package/dist/features/seo/index.js +3 -4
  37. package/dist/features/settings/index.js +20 -27
  38. package/dist/graph/index.js +1 -1
  39. package/dist/hooks/delivery/index.js +30 -2
  40. package/dist/hooks/index.d.ts +85 -85
  41. package/dist/hooks/index.js +20 -21
  42. package/dist/hooks/operations/command-view/utils/transformCommandViewData.d.ts +35 -35
  43. package/dist/hooks/published.d.ts +85 -85
  44. package/dist/hooks/published.js +20 -20
  45. package/dist/index.css +532 -532
  46. package/dist/index.d.ts +9256 -5803
  47. package/dist/index.js +22 -26
  48. package/dist/knowledge/index.d.ts +21 -21
  49. package/dist/knowledge/index.js +8 -15
  50. package/dist/layout/index.js +4 -10
  51. package/dist/organization/index.js +27 -1
  52. package/dist/provider/index.d.ts +47 -21
  53. package/dist/provider/index.js +20 -15
  54. package/dist/provider/published.d.ts +15 -16
  55. package/dist/provider/published.js +20 -11
  56. package/dist/test-utils/index.js +3 -3
  57. package/dist/theme/index.js +2 -3
  58. package/dist/theme/presets/index.d.ts +28 -3
  59. package/dist/theme/presets/index.js +1 -1
  60. package/dist/typeform/index.js +1 -2049
  61. package/dist/types/index.d.ts +68 -68
  62. package/dist/utils/index.d.ts +46 -46
  63. package/dist/utils/index.js +1 -1
  64. package/dist/zustand/index.d.ts +6 -6
  65. package/dist/zustand/index.js +0 -3
  66. package/package.json +5 -5
  67. package/dist/chunk-3AJVNMY5.js +0 -4769
  68. package/dist/chunk-3MEXPLWT.js +0 -265
  69. package/dist/chunk-3ZMAGTWF.js +0 -18
  70. package/dist/chunk-4O4MII5S.js +0 -4716
  71. package/dist/chunk-5EYJ2GIN.js +0 -122
  72. package/dist/chunk-7M2VOCYN.js +0 -1
  73. package/dist/chunk-BPQVTIUP.js +0 -105
  74. package/dist/chunk-BZZCNLT6.js +0 -12
  75. package/dist/chunk-CLDCYJQT.js +0 -1
  76. package/dist/chunk-E565XMTQ.js +0 -17
  77. package/dist/chunk-HRWLKKWM.js +0 -758
  78. package/dist/chunk-IGDYWFNE.js +0 -5198
  79. package/dist/chunk-IIMU5YAJ.js +0 -53
  80. package/dist/chunk-IVGI4GDL.js +0 -1593
  81. package/dist/chunk-JFL3GRD4.js +0 -39
  82. package/dist/chunk-LAWLB6CT.js +0 -951
  83. package/dist/chunk-LGKLC5MG.js +0 -44
  84. package/dist/chunk-LRWTWOGP.js +0 -1778
  85. package/dist/chunk-MP3GPBPX.js +0 -1874
  86. package/dist/chunk-N55DVMAG.js +0 -14
  87. package/dist/chunk-NLBQTDOW.js +0 -12051
  88. package/dist/chunk-O6JXQ6UQ.js +0 -468
  89. package/dist/chunk-OBBQ2JCM.js +0 -68
  90. package/dist/chunk-PDHTXPSF.js +0 -12
  91. package/dist/chunk-PLP3NYPL.js +0 -356
  92. package/dist/chunk-R2XR4FCV.js +0 -48
  93. package/dist/chunk-R66W5UDG.js +0 -26
  94. package/dist/chunk-RYTEQBAO.js +0 -37
  95. package/dist/chunk-SDXSB3HN.js +0 -425
  96. package/dist/chunk-TKAYX2SP.js +0 -204
  97. package/dist/chunk-TUMSNGTX.js +0 -35
  98. package/dist/chunk-VNAZTCHA.js +0 -65
  99. package/dist/chunk-VNFR57DF.js +0 -87
  100. package/dist/chunk-VTXTZXAU.js +0 -539
  101. package/dist/chunk-W73ZABT6.js +0 -85
  102. package/dist/chunk-WU4FNWCW.js +0 -2281
  103. package/dist/chunk-XZGSCABI.js +0 -383
  104. package/dist/chunk-YNWZIWJL.js +0 -1863
  105. /package/dist/{chunk-2RJMVWFJ.js → chunk-GEFWMU26.js} +0 -0
  106. /package/dist/{chunk-22UVE3RA.js → chunk-HENXLGVD.js} +0 -0
@@ -1,1593 +0,0 @@
1
- import { SubshellNavItem } from './chunk-X4WBGKJQ.js';
2
- import { SubshellSidebarSection } from './chunk-IIMU5YAJ.js';
3
- import { milestoneStatusColors, formatStatusLabel, taskTypeColors, taskStatusColors, projectStatusColors, calculateProgress, noteTypeColors } from './chunk-R2XR4FCV.js';
4
- import { PageContainer } from './chunk-BZZCNLT6.js';
5
- import { TableSelectionToolbar, SortableHeader } from './chunk-TUMSNGTX.js';
6
- import { FilterBar } from './chunk-PDHTXPSF.js';
7
- import { CustomModal } from './chunk-R66W5UDG.js';
8
- import { useProjectRealtime, useTableSort, sortData, usePaginationState, useTableSelection, useProjectActivities } from './chunk-IGDYWFNE.js';
9
- import { useCreateTask, useCreateMilestone, useProjectMilestones, useUpdateTask, useProjects, useDeleteProject, useProject, useProjectNotes, useUpdateMilestone, useCreateNote, showApiErrorNotification, projectKeys } from './chunk-XZGSCABI.js';
10
- import { StatusBadge, EmptyState, PageTitleCaption, CenteredErrorState, StatCard, CardHeader } from './chunk-HRWLKKWM.js';
11
- import { SubshellContentContainer } from './chunk-TKAYX2SP.js';
12
- import { PROJECTS_VIEW_ACTION_ID } from './chunk-DTFKWZ7A.js';
13
- import { useRouterContext } from './chunk-Q7DJKLEN.js';
14
- import { PAGE_SIZE_DEFAULT, formatTimeAgo, formatDate, formatRelativeTime } from './chunk-2RJMVWFJ.js';
15
- import { useElevasisServices } from './chunk-KJ3QUBNU.js';
16
- import { useState, useMemo, useCallback } from 'react';
17
- import { Stack, Text, Group, Checkbox, ActionIcon, TextInput, Button, Modal, Title, Textarea, Select, SimpleGrid, Card, ThemeIcon, RingProgress, Paper, Timeline, Badge, Collapse, Center, Loader, Table, Pagination, Alert, CopyButton, Tooltip, Space, Tabs, Divider, Anchor, Chip } from '@mantine/core';
18
- import { IconNotes, IconBriefcase, IconChecklist, IconFlag, IconX, IconPlus, IconHeartbeat, IconFileText, IconCalendar, IconInbox, IconLock, IconAlertTriangle, IconCircleCheck, IconClock, IconChevronDown, IconChevronRight, IconDownload, IconListCheck, IconMessageCircle, IconSearch, IconCheck, IconCopy, IconFileDescription, IconArrowLeft, IconCurrencyDollar, IconBuilding } from '@tabler/icons-react';
19
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
20
- import { useDisclosure } from '@mantine/hooks';
21
- import { useQueryClient, useMutation } from '@tanstack/react-query';
22
-
23
- function Checklist({ items, onToggle, onAdd, onRemove }) {
24
- const [adding, setAdding] = useState(false);
25
- const [newItemLabel, setNewItemLabel] = useState("");
26
- function commitAdd() {
27
- const label = newItemLabel.trim();
28
- if (label) onAdd(label);
29
- setNewItemLabel("");
30
- }
31
- const completedCount = items.filter((item) => item.completed).length;
32
- return /* @__PURE__ */ jsxs(Stack, { gap: "xs", mt: "sm", children: [
33
- items.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
34
- completedCount,
35
- "/",
36
- items.length,
37
- " complete"
38
- ] }),
39
- items.map((item) => /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
40
- /* @__PURE__ */ jsx(
41
- Checkbox,
42
- {
43
- size: "xs",
44
- checked: item.completed,
45
- onChange: () => onToggle(item.id),
46
- label: /* @__PURE__ */ jsx(
47
- Text,
48
- {
49
- size: "sm",
50
- td: item.completed ? "line-through" : void 0,
51
- c: item.completed ? "dimmed" : "var(--color-text-subtle)",
52
- children: item.label
53
- }
54
- ),
55
- styles: { input: { cursor: "pointer" }, label: { cursor: "pointer" } }
56
- }
57
- ),
58
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "xs", onClick: () => onRemove(item.id), style: { opacity: 0.4 }, children: /* @__PURE__ */ jsx(IconX, { size: 12 }) })
59
- ] }, item.id)),
60
- adding ? /* @__PURE__ */ jsx(
61
- TextInput,
62
- {
63
- size: "xs",
64
- placeholder: "Item label...",
65
- value: newItemLabel,
66
- onChange: (event) => setNewItemLabel(event.target.value),
67
- onKeyDown: (event) => {
68
- if (event.key === "Enter") {
69
- commitAdd();
70
- setAdding(false);
71
- }
72
- if (event.key === "Escape") {
73
- setNewItemLabel("");
74
- setAdding(false);
75
- }
76
- },
77
- onBlur: () => {
78
- if (newItemLabel.trim()) commitAdd();
79
- setAdding(false);
80
- },
81
- autoFocus: true,
82
- style: { maxWidth: 250 }
83
- }
84
- ) : /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(
85
- Button,
86
- {
87
- variant: "subtle",
88
- size: "compact-xs",
89
- leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 12 }),
90
- onClick: () => setAdding(true),
91
- children: "Add item"
92
- }
93
- ) })
94
- ] });
95
- }
96
- var TASK_TYPE_OPTIONS = [
97
- { value: "documentation", label: "Documentation" },
98
- { value: "code", label: "Code" },
99
- { value: "report", label: "Report" },
100
- { value: "design", label: "Design" },
101
- { value: "other", label: "Other" }
102
- ];
103
- function CreateDeliveryEntityModal({ opened, onClose, projectId, entityType }) {
104
- const [name, setName] = useState("");
105
- const [description, setDescription] = useState("");
106
- const [taskType, setTaskType] = useState("other");
107
- const [milestoneId, setMilestoneId] = useState(null);
108
- const { mutate: createTask, isPending: isCreatingTask } = useCreateTask();
109
- const { mutate: createMilestone, isPending: isCreatingMilestone } = useCreateMilestone();
110
- const { data: milestones = [] } = useProjectMilestones(entityType === "task" ? projectId : "");
111
- const isPending = entityType === "task" ? isCreatingTask : isCreatingMilestone;
112
- const milestoneOptions = milestones.map((m) => ({ value: m.id, label: m.name }));
113
- function resetForm() {
114
- setName("");
115
- setDescription("");
116
- setTaskType("other");
117
- setMilestoneId(null);
118
- }
119
- function handleClose() {
120
- resetForm();
121
- onClose();
122
- }
123
- function handleSubmit() {
124
- const trimmedName = name.trim();
125
- if (!trimmedName) return;
126
- if (entityType === "task") {
127
- createTask(
128
- {
129
- project_id: projectId,
130
- name: trimmedName,
131
- description: description.trim() || null,
132
- type: taskType ?? "other",
133
- milestone_id: milestoneId ?? null
134
- },
135
- {
136
- onSuccess: () => {
137
- resetForm();
138
- onClose();
139
- }
140
- }
141
- );
142
- } else {
143
- createMilestone(
144
- {
145
- project_id: projectId,
146
- name: trimmedName,
147
- description: description.trim() || null
148
- },
149
- {
150
- onSuccess: () => {
151
- resetForm();
152
- onClose();
153
- }
154
- }
155
- );
156
- }
157
- }
158
- const title = entityType === "task" ? "Create Task" : "Add Milestone";
159
- return /* @__PURE__ */ jsx(Modal, { opened, onClose: handleClose, size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
160
- /* @__PURE__ */ jsx(Title, { order: 3, children: title }),
161
- /* @__PURE__ */ jsx(
162
- TextInput,
163
- {
164
- label: "Name",
165
- placeholder: entityType === "task" ? "Task name..." : "Milestone name...",
166
- value: name,
167
- onChange: (e) => setName(e.target.value),
168
- required: true,
169
- "data-autofocus": true
170
- }
171
- ),
172
- /* @__PURE__ */ jsx(
173
- Textarea,
174
- {
175
- label: "Description",
176
- placeholder: "Optional description...",
177
- minRows: 3,
178
- value: description,
179
- onChange: (e) => setDescription(e.target.value)
180
- }
181
- ),
182
- entityType === "task" && /* @__PURE__ */ jsxs(Fragment, { children: [
183
- /* @__PURE__ */ jsx(Select, { label: "Type", data: TASK_TYPE_OPTIONS, value: taskType, onChange: setTaskType }),
184
- milestoneOptions.length > 0 && /* @__PURE__ */ jsx(
185
- Select,
186
- {
187
- label: "Milestone",
188
- placeholder: "Assign to milestone (optional)",
189
- data: milestoneOptions,
190
- value: milestoneId,
191
- onChange: setMilestoneId,
192
- clearable: true
193
- }
194
- )
195
- ] }),
196
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
197
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
198
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: isPending, disabled: !name.trim(), children: title })
199
- ] })
200
- ] }) });
201
- }
202
- function ringColor(completed, total) {
203
- if (total === 0) return "gray";
204
- const pct = completed / total;
205
- if (pct > 0.75) return "green";
206
- if (pct > 0.5) return "yellow";
207
- return "gray";
208
- }
209
- function ringValue(completed, total) {
210
- if (total === 0) return 0;
211
- return Math.round(completed / total * 100);
212
- }
213
- function formatDate2(iso) {
214
- return new Date(iso).toLocaleDateString();
215
- }
216
- function daysRelative(targetEndDate) {
217
- const now = /* @__PURE__ */ new Date();
218
- const end = new Date(targetEndDate);
219
- const diffMs = end.getTime() - now.getTime();
220
- const diffDays = Math.ceil(diffMs / 864e5);
221
- if (diffDays >= 0) {
222
- return { label: `${diffDays} day${diffDays === 1 ? "" : "s"} remaining`, overdue: false };
223
- }
224
- return { label: `${Math.abs(diffDays)} day${Math.abs(diffDays) === 1 ? "" : "s"} overdue`, overdue: true };
225
- }
226
- function HealthStatusCard({
227
- status,
228
- milestoneCount,
229
- completedMilestones,
230
- taskCount,
231
- completedTasks,
232
- startDate,
233
- targetEndDate
234
- }) {
235
- const milestoneRingValue = ringValue(completedMilestones, milestoneCount);
236
- const taskRingValue = ringValue(completedTasks, taskCount);
237
- const timeInfo = targetEndDate ? daysRelative(targetEndDate) : null;
238
- return /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
239
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
240
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
241
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconHeartbeat, { size: 14 }) }),
242
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Status" })
243
- ] }),
244
- /* @__PURE__ */ jsx(StatusBadge, { status, size: "md" })
245
- ] }) }),
246
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
247
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
248
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFlag, { size: 14 }) }),
249
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Milestones" })
250
- ] }),
251
- /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
252
- /* @__PURE__ */ jsx(
253
- RingProgress,
254
- {
255
- size: 48,
256
- thickness: 5,
257
- sections: [{ value: milestoneRingValue, color: ringColor(completedMilestones, milestoneCount) }]
258
- }
259
- ),
260
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
261
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
262
- completedMilestones,
263
- "/",
264
- milestoneCount
265
- ] }),
266
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "complete" })
267
- ] })
268
- ] })
269
- ] }) }),
270
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
271
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
272
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFileText, { size: 14 }) }),
273
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Tasks" })
274
- ] }),
275
- /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
276
- /* @__PURE__ */ jsx(
277
- RingProgress,
278
- {
279
- size: 48,
280
- thickness: 5,
281
- sections: [{ value: taskRingValue, color: ringColor(completedTasks, taskCount) }]
282
- }
283
- ),
284
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
285
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
286
- completedTasks,
287
- "/",
288
- taskCount
289
- ] }),
290
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "approved" })
291
- ] })
292
- ] })
293
- ] }) }),
294
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
295
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
296
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconCalendar, { size: 14 }) }),
297
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Timeline" })
298
- ] }),
299
- /* @__PURE__ */ jsx(Stack, { gap: 2, children: startDate && targetEndDate ? /* @__PURE__ */ jsxs(Fragment, { children: [
300
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
301
- formatDate2(startDate),
302
- " \u2192 ",
303
- formatDate2(targetEndDate)
304
- ] }),
305
- timeInfo && /* @__PURE__ */ jsx(Text, { size: "xs", c: timeInfo.overdue ? "red" : "dimmed", children: timeInfo.label })
306
- ] }) : startDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
307
- "Started ",
308
- formatDate2(startDate)
309
- ] }) : targetEndDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
310
- "Due ",
311
- formatDate2(targetEndDate)
312
- ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No dates set" }) })
313
- ] }) })
314
- ] });
315
- }
316
- var MILESTONE_ICONS = {
317
- upcoming: IconClock,
318
- in_progress: IconFlag,
319
- completed: IconCircleCheck,
320
- overdue: IconAlertTriangle,
321
- blocked: IconLock
322
- };
323
- function MilestoneTimeline({ milestones, tasks }) {
324
- const sorted = [...milestones].sort((a, b) => a.sequence - b.sequence);
325
- const defaultExpanded = new Set(sorted.filter((m) => m.status === "in_progress").map((m) => m.id));
326
- const [expanded, setExpanded] = useState(defaultExpanded);
327
- if (sorted.length === 0) {
328
- return /* @__PURE__ */ jsx(EmptyState, { icon: IconInbox, title: "No milestones yet" });
329
- }
330
- function toggleMilestone(id) {
331
- setExpanded((prev) => {
332
- const next = new Set(prev);
333
- if (next.has(id)) {
334
- next.delete(id);
335
- } else {
336
- next.add(id);
337
- }
338
- return next;
339
- });
340
- }
341
- const activeIndex = sorted.reduce((last, m, i) => m.status !== "upcoming" ? i : last, -1);
342
- 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) => {
343
- const Icon = MILESTONE_ICONS[milestone.status] || IconFlag;
344
- const color = milestoneStatusColors[milestone.status] || "gray";
345
- const isExpanded = expanded.has(milestone.id);
346
- const milestoneTasks = tasks.filter((t) => t.milestone_id === milestone.id);
347
- const ChevronIcon = isExpanded ? IconChevronDown : IconChevronRight;
348
- return /* @__PURE__ */ jsx(
349
- Timeline.Item,
350
- {
351
- bullet: /* @__PURE__ */ jsx(ThemeIcon, { size: 28, variant: "filled", color, radius: "xl", children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
352
- children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
353
- /* @__PURE__ */ jsxs(
354
- Group,
355
- {
356
- gap: 8,
357
- style: { cursor: "pointer", userSelect: "none" },
358
- onClick: () => toggleMilestone(milestone.id),
359
- children: [
360
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 700, children: milestone.name }),
361
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color, children: formatStatusLabel(milestone.status) }),
362
- /* @__PURE__ */ jsx(ThemeIcon, { size: 16, variant: "transparent", color: "dimmed", children: /* @__PURE__ */ jsx(ChevronIcon, { size: 14 }) })
363
- ]
364
- }
365
- ),
366
- milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
367
- "Due ",
368
- new Date(milestone.due_date).toLocaleDateString()
369
- ] }),
370
- /* @__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: [
371
- /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", children: [
372
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, style: { flex: 1, minWidth: 0 }, children: task.name }),
373
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
374
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskStatusColors[task.status] || "gray", children: formatStatusLabel(task.status) })
375
- ] }),
376
- task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", mt: 4, children: [
377
- "Due ",
378
- new Date(task.due_date).toLocaleDateString()
379
- ] })
380
- ] }, task.id)) }) })
381
- ] })
382
- },
383
- milestone.id
384
- );
385
- }) }) });
386
- }
387
- function parseChecklist(task) {
388
- const raw = task.checklist;
389
- if (!Array.isArray(raw)) return [];
390
- return raw;
391
- }
392
- function TaskCard({ task }) {
393
- const { mutate: updateTask } = useUpdateTask();
394
- const items = parseChecklist(task);
395
- function saveChecklist(updated) {
396
- const updates = {
397
- checklist: updated
398
- };
399
- updateTask({ id: task.id, updates });
400
- }
401
- return /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "sm", p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
402
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
403
- /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
404
- /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", style: { flexShrink: 0 }, children: task.name }),
405
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
406
- /* @__PURE__ */ jsx(StatusBadge, { status: task.status, size: "xs" })
407
- ] }),
408
- task.file_url && /* @__PURE__ */ jsx(
409
- ActionIcon,
410
- {
411
- component: "a",
412
- href: task.file_url,
413
- target: "_blank",
414
- rel: "noopener noreferrer",
415
- variant: "light",
416
- size: "sm",
417
- "aria-label": "Download file",
418
- children: /* @__PURE__ */ jsx(IconDownload, { size: 14 })
419
- }
420
- )
421
- ] }),
422
- task.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: task.description }),
423
- task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
424
- "Due ",
425
- new Date(task.due_date).toLocaleDateString()
426
- ] }),
427
- items.length > 0 && /* @__PURE__ */ jsx(
428
- Checklist,
429
- {
430
- items,
431
- onToggle: (itemId) => {
432
- saveChecklist(items.map((item) => item.id === itemId ? { ...item, completed: !item.completed } : item));
433
- },
434
- onAdd: (label) => {
435
- saveChecklist([...items, { id: crypto.randomUUID(), label, completed: false }]);
436
- },
437
- onRemove: (itemId) => {
438
- saveChecklist(items.filter((item) => item.id !== itemId));
439
- }
440
- }
441
- )
442
- ] }) });
443
- }
444
- var ProjectsSidebarTop = () => {
445
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconBriefcase, label: "Projects" });
446
- };
447
- var DELIVERY_PROJECT_ITEMS = [
448
- { label: "Projects", to: "/projects", icon: IconBriefcase, exact: true }
449
- ];
450
- var DELIVERY_WORK_ITEMS = [
451
- { label: "Tasks", to: "/projects/tasks", icon: IconChecklist, exact: false },
452
- { label: "Milestones", to: "/projects/milestones", icon: IconFlag, exact: false }
453
- ];
454
- var DELIVERY_COMMUNICATION_ITEMS = [
455
- { label: "Notes", to: "/projects/notes", icon: IconNotes, exact: false }
456
- ];
457
- var ProjectsSidebarMiddle = ({
458
- currentPath,
459
- onNavigate,
460
- projectItems = DELIVERY_PROJECT_ITEMS,
461
- workItems = DELIVERY_WORK_ITEMS,
462
- communicationItems = DELIVERY_COMMUNICATION_ITEMS
463
- } = {}) => {
464
- const { currentPath: routerCurrentPath, navigate } = useRouterContext();
465
- const resolvedCurrentPath = currentPath ?? routerCurrentPath;
466
- const resolvedNavigate = onNavigate ?? navigate;
467
- const renderItems = (items) => items.map((item) => {
468
- const isActive = item.exact ? resolvedCurrentPath === item.to || resolvedCurrentPath === `${item.to}/` : resolvedCurrentPath.startsWith(item.to);
469
- return /* @__PURE__ */ jsx(
470
- SubshellNavItem,
471
- {
472
- icon: item.icon,
473
- label: item.label,
474
- isActive,
475
- href: item.to,
476
- onClick: () => resolvedNavigate(item.to)
477
- },
478
- item.to
479
- );
480
- });
481
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
482
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(projectItems) }),
483
- /* @__PURE__ */ jsxs(Fragment, { children: [
484
- /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconListCheck, label: "Work", withTopBorder: true }),
485
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(workItems) })
486
- ] }),
487
- /* @__PURE__ */ jsxs(Fragment, { children: [
488
- /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconMessageCircle, label: "Communication", withTopBorder: true }),
489
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(communicationItems) })
490
- ] })
491
- ] });
492
- };
493
- var ProjectsSidebar = ({ children } = {}) => {
494
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
495
- /* @__PURE__ */ jsx(ProjectsSidebarTop, {}),
496
- children ?? /* @__PURE__ */ jsx(ProjectsSidebarMiddle, {})
497
- ] });
498
- };
499
- var deliveryManifest = {
500
- key: "delivery",
501
- systemId: "projects",
502
- capabilityIds: [PROJECTS_VIEW_ACTION_ID],
503
- icon: IconBriefcase
504
- };
505
- function AllTasksPage() {
506
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
507
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "All Tasks", caption: "Project tasks are scoped to an individual project" }) }),
508
- /* @__PURE__ */ jsxs(Paper, { children: [
509
- /* @__PURE__ */ jsx(
510
- EmptyState,
511
- {
512
- icon: IconChecklist,
513
- title: "Project task lists are not aggregated here",
514
- description: "Open a project detail view to inspect its tasks. The shared contract does not provide a cross-project task list.",
515
- py: "xl"
516
- }
517
- ),
518
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", pb: "xl", children: "This page is kept for route parity only." })
519
- ] })
520
- ] }) });
521
- }
522
- function UpcomingMilestonesPage() {
523
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
524
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Upcoming Milestones", caption: "Milestones are scoped to an individual project" }) }),
525
- /* @__PURE__ */ jsxs(Paper, { children: [
526
- /* @__PURE__ */ jsx(
527
- EmptyState,
528
- {
529
- icon: IconFlag,
530
- title: "Project milestone lists are not aggregated here",
531
- description: "Open a project detail view to inspect its milestones. The shared contract does not provide a cross-project milestone list.",
532
- py: "xl"
533
- }
534
- ),
535
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", pb: "xl", children: "This page is kept for route parity only." })
536
- ] })
537
- ] }) });
538
- }
539
- var STATUS_OPTIONS = [
540
- { value: "active", label: "Active" },
541
- { value: "on_track", label: "On Track" },
542
- { value: "at_risk", label: "At Risk" },
543
- { value: "blocked", label: "Blocked" },
544
- { value: "completed", label: "Completed" },
545
- { value: "paused", label: "Paused" }
546
- ];
547
- function ProjectsListPage({ onProjectClick } = {}) {
548
- useProjectRealtime();
549
- const [statusFilter, setStatusFilter] = useState(null);
550
- const [searchQuery, setSearchQuery] = useState("");
551
- const [showBatchDelete, setShowBatchDelete] = useState(false);
552
- const { data: projects, isLoading, error } = useProjects();
553
- const deleteProject = useDeleteProject();
554
- const { sort, toggleSort } = useTableSort("updated");
555
- const filteredProjects = useMemo(() => {
556
- const source = projects ?? [];
557
- const query = searchQuery.trim().toLowerCase();
558
- return source.filter((project) => {
559
- const matchesStatus = !statusFilter || project.status === statusFilter;
560
- const matchesSearch = !query || project.name.toLowerCase().includes(query);
561
- return matchesStatus && matchesSearch;
562
- });
563
- }, [projects, searchQuery, statusFilter]);
564
- const sortAccessors = useMemo(
565
- () => ({
566
- name: (p) => p.name || "",
567
- status: (p) => p.status || "",
568
- updated: (p) => p.updated_at || ""
569
- }),
570
- []
571
- );
572
- const sortedProjects = useMemo(
573
- () => sortData(filteredProjects, sort, sortAccessors),
574
- [filteredProjects, sort, sortAccessors]
575
- );
576
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [statusFilter, searchQuery], sortedProjects.length);
577
- const paginatedProjects = useMemo(
578
- () => sortedProjects.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
579
- [sortedProjects, pagination.offset]
580
- );
581
- const selection = useTableSelection(paginatedProjects, sortedProjects);
582
- const handleDeleteSelected = async () => {
583
- await Promise.all([...selection.selectedIds].map((projectId) => deleteProject.mutateAsync(projectId)));
584
- setShowBatchDelete(false);
585
- selection.clear();
586
- };
587
- return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
588
- /* @__PURE__ */ jsxs(PageContainer, { children: [
589
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Projects", caption: "Client delivery tracking and milestone management" }) }),
590
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
591
- /* @__PURE__ */ jsxs(
592
- FilterBar,
593
- {
594
- actions: /* @__PURE__ */ jsx(
595
- TableSelectionToolbar,
596
- {
597
- selectedCount: selection.selectedCount,
598
- onDelete: () => setShowBatchDelete(true),
599
- isDeleting: deleteProject.isPending
600
- }
601
- ),
602
- children: [
603
- /* @__PURE__ */ jsx(
604
- Select,
605
- {
606
- placeholder: "All Statuses",
607
- data: STATUS_OPTIONS,
608
- style: { minWidth: 180 },
609
- size: "sm",
610
- clearable: true,
611
- value: statusFilter,
612
- onChange: setStatusFilter
613
- }
614
- ),
615
- /* @__PURE__ */ jsx(
616
- TextInput,
617
- {
618
- placeholder: "Search by name...",
619
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
620
- style: { minWidth: 250 },
621
- size: "sm",
622
- value: searchQuery,
623
- onChange: (e) => setSearchQuery(e.currentTarget.value)
624
- }
625
- )
626
- ]
627
- }
628
- ),
629
- 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: [
630
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
631
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
632
- Checkbox,
633
- {
634
- checked: selection.isPageAllSelected,
635
- indeterminate: selection.isPagePartiallySelected,
636
- onChange: selection.togglePage
637
- }
638
- ) }),
639
- /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
640
- /* @__PURE__ */ jsx(SortableHeader, { column: "status", sort, onToggle: toggleSort, children: "Status" }),
641
- /* @__PURE__ */ jsx(Table.Th, { children: "Milestones" }),
642
- /* @__PURE__ */ jsx(Table.Th, { children: "Tasks" }),
643
- /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, children: "Updated" })
644
- ] }) }),
645
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedProjects.map((project) => {
646
- const completedMilestones = project.completedMilestones ?? 0;
647
- const milestoneCount = project.milestoneCount ?? 0;
648
- const completedTasks = project.completedTasks ?? 0;
649
- const taskCount = project.taskCount ?? 0;
650
- const rowProps = onProjectClick ? {
651
- style: { cursor: "pointer" },
652
- onClick: () => onProjectClick(project.id)
653
- } : void 0;
654
- return /* @__PURE__ */ jsxs(Table.Tr, { ...rowProps, children: [
655
- /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
656
- Checkbox,
657
- {
658
- checked: selection.isSelected(project.id),
659
- onChange: () => selection.toggle(project.id)
660
- }
661
- ) }),
662
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: project.name }) }),
663
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", size: "sm", children: formatStatusLabel(project.status || "unknown") }) }),
664
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
665
- completedMilestones,
666
- "/",
667
- milestoneCount
668
- ] }) }),
669
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
670
- completedTasks,
671
- "/",
672
- taskCount
673
- ] }) }),
674
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(project.updated_at) }) })
675
- ] }, project.id);
676
- }) })
677
- ] }),
678
- sortedProjects.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
679
- Pagination,
680
- {
681
- value: pagination.page,
682
- onChange: pagination.setPage,
683
- total: pagination.totalPages(sortedProjects.length),
684
- size: "sm"
685
- }
686
- ) })
687
- ] }) })
688
- ] }),
689
- /* @__PURE__ */ jsx(
690
- CustomModal,
691
- {
692
- opened: showBatchDelete,
693
- onClose: () => !deleteProject.isPending && setShowBatchDelete(false),
694
- size: "sm",
695
- loading: deleteProject.isPending,
696
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
697
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
698
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
699
- /* @__PURE__ */ jsxs(Title, { order: 4, children: [
700
- "Delete ",
701
- selection.selectedCount,
702
- " Projects"
703
- ] })
704
- ] }),
705
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
706
- "Are you sure you want to delete",
707
- " ",
708
- /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
709
- " ",
710
- "selected projects?"
711
- ] }),
712
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
713
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
714
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteProject.isPending, children: "Cancel" }),
715
- /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteProject.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
716
- ] })
717
- ] })
718
- }
719
- )
720
- ] });
721
- }
722
- function isResumeContext(val) {
723
- return !!val && typeof val === "object" && !Array.isArray(val);
724
- }
725
- function ContextRow({ label, value }) {
726
- return /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
727
- /* @__PURE__ */ jsx(
728
- Text,
729
- {
730
- size: "xs",
731
- ff: "monospace",
732
- c: "var(--color-text-subtle)",
733
- style: { textTransform: "uppercase", letterSpacing: "0.06em", opacity: 0.7 },
734
- children: label
735
- }
736
- ),
737
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "var(--color-text)", style: { lineHeight: 1.55 }, children: value })
738
- ] });
739
- }
740
- function ResumeContextPreview({ tasks }) {
741
- const candidate = tasks.filter((t) => isResumeContext(t.resume_context)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())[0];
742
- if (!candidate) return null;
743
- const ctx = candidate.resume_context;
744
- const hasBody = ctx.current_state || ctx.next_steps;
745
- return /* @__PURE__ */ jsxs(
746
- Paper,
747
- {
748
- p: "md",
749
- style: {
750
- border: "1px solid var(--color-border)",
751
- borderRadius: "var(--mantine-radius-default)",
752
- backgroundColor: "color-mix(in srgb, var(--color-primary) 4%, var(--color-surface))",
753
- position: "relative",
754
- overflow: "hidden"
755
- },
756
- children: [
757
- /* @__PURE__ */ jsx(
758
- "div",
759
- {
760
- style: {
761
- position: "absolute",
762
- inset: "0 auto 0 0",
763
- width: 3,
764
- background: "linear-gradient(180deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 30%, transparent) 100%)"
765
- }
766
- }
767
- ),
768
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", pl: 4, children: [
769
- /* @__PURE__ */ jsxs(Group, { gap: 6, align: "baseline", wrap: "wrap", children: [
770
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Last worked" }),
771
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 600, c: "var(--color-text)", children: formatRelativeTime(candidate.updated_at) }),
772
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "on" }),
773
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 600, c: "var(--color-text)", children: candidate.name })
774
- ] }),
775
- hasBody && /* @__PURE__ */ jsxs(Fragment, { children: [
776
- /* @__PURE__ */ jsx(Divider, { opacity: 0.3 }),
777
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
778
- ctx.current_state && /* @__PURE__ */ jsx(ContextRow, { label: "Current State", value: ctx.current_state }),
779
- ctx.next_steps && /* @__PURE__ */ jsx(ContextRow, { label: "Next Steps", value: ctx.next_steps })
780
- ] })
781
- ] })
782
- ] })
783
- ]
784
- }
785
- );
786
- }
787
- function QuickActionsRow({
788
- onCreateTask,
789
- onAddMilestone,
790
- onLogBlocker,
791
- onBack,
792
- backLabel = "Projects"
793
- }) {
794
- return /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", justify: "space-between", children: [
795
- /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
796
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "compact-sm", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 14 }), onClick: onCreateTask, children: "Create Task" }),
797
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "compact-sm", leftSection: /* @__PURE__ */ jsx(IconFlag, { size: 14 }), onClick: onAddMilestone, children: "Add Milestone" }),
798
- /* @__PURE__ */ jsx(
799
- Button,
800
- {
801
- variant: "light",
802
- size: "compact-sm",
803
- color: "orange",
804
- leftSection: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 14 }),
805
- onClick: onLogBlocker,
806
- children: "Log Blocker"
807
- }
808
- )
809
- ] }),
810
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "compact-sm", leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 14 }), onClick: onBack, children: backLabel })
811
- ] });
812
- }
813
- function LinkedRecordChips({ dealId, clientCompanyId }) {
814
- if (!dealId && !clientCompanyId) return null;
815
- return /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
816
- dealId && /* @__PURE__ */ jsx(Anchor, { href: `/crm/deals/${dealId}`, underline: "never", children: /* @__PURE__ */ jsx(
817
- Badge,
818
- {
819
- variant: "light",
820
- color: "blue",
821
- leftSection: /* @__PURE__ */ jsx(IconCurrencyDollar, { size: 12 }),
822
- style: { cursor: "pointer" },
823
- children: "Deal"
824
- }
825
- ) }),
826
- clientCompanyId && /* @__PURE__ */ jsx(Anchor, { href: `/crm/companies/${clientCompanyId}`, underline: "never", children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: "teal", leftSection: /* @__PURE__ */ jsx(IconBuilding, { size: 12 }), style: { cursor: "pointer" }, children: "Company" }) })
827
- ] });
828
- }
829
- function ProjectActivityTimeline({ projectId }) {
830
- const { data: activities, isLoading, error } = useProjectActivities(projectId);
831
- if (isLoading) {
832
- return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsx(Loader, { size: "sm" }) });
833
- }
834
- if (error) {
835
- return /* @__PURE__ */ jsx(Alert, { color: "red", title: "Failed to load activity", children: "Could not fetch project activity. Please refresh." });
836
- }
837
- if (!activities?.length) {
838
- return /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "No activity recorded for this project yet." });
839
- }
840
- return /* @__PURE__ */ jsx(Timeline, { bulletSize: 18, lineWidth: 2, children: activities.map((activity) => /* @__PURE__ */ jsxs(
841
- Timeline.Item,
842
- {
843
- title: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: activity.title }),
844
- children: [
845
- activity.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", mt: 2, children: activity.description }),
846
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", mt: 2, children: formatTimeAgo(activity.occurredAt) })
847
- ]
848
- },
849
- activity.id
850
- )) });
851
- }
852
- var NOTE_FILTER_OPTIONS = [
853
- { value: "call_note", label: "Call Notes" },
854
- { value: "status_update", label: "Status Updates" },
855
- { value: "issue", label: "Issues" },
856
- { value: "blocker", label: "Blockers" },
857
- { value: "agent_learning", label: "Agent Learning" }
858
- ];
859
- function isWithinLastWeek(dateStr) {
860
- if (!dateStr) return false;
861
- const date = new Date(dateStr);
862
- const cutoff = /* @__PURE__ */ new Date();
863
- cutoff.setDate(cutoff.getDate() - 7);
864
- return date >= cutoff;
865
- }
866
- function NotesTabContent({ notes, onAddNote }) {
867
- const [activeFilter, setActiveFilter] = useState(null);
868
- const allNotes = notes ?? [];
869
- const thisWeek = allNotes.filter((n) => isWithinLastWeek(n.occurred_at));
870
- const weeklyByType = NOTE_FILTER_OPTIONS.map(({ value, label }) => ({
871
- value,
872
- label,
873
- count: thisWeek.filter((n) => n.type === value).length
874
- })).filter((entry) => entry.count > 0);
875
- const rollupText = weeklyByType.length > 0 ? weeklyByType.map((e) => `${e.count} ${e.label.toLowerCase()}`).join(", ") + " this week" : null;
876
- const visibleNotes = activeFilter ? allNotes.filter((n) => n.type === activeFilter) : allNotes;
877
- return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
878
- rollupText && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: rollupText }),
879
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", wrap: "wrap", children: [
880
- /* @__PURE__ */ jsx(Chip.Group, { value: activeFilter ?? "", onChange: (val) => setActiveFilter(val || null), children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
881
- /* @__PURE__ */ jsx(Chip, { value: "", size: "xs", variant: "light", children: "All" }),
882
- NOTE_FILTER_OPTIONS.map(({ value, label }) => /* @__PURE__ */ jsx(Chip, { value, size: "xs", variant: "light", children: label }, value))
883
- ] }) }),
884
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: onAddNote, children: "Add Note" })
885
- ] }),
886
- !visibleNotes.length ? /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: activeFilter ? `No ${formatStatusLabel(activeFilter)} notes yet.` : "No notes yet." }) : /* @__PURE__ */ jsx(Stack, { gap: "sm", children: visibleNotes.map((note) => /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
887
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
888
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: noteTypeColors[note.type ?? ""] ?? "gray", size: "sm", children: formatStatusLabel(note.type ?? "note") }),
889
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatRelativeTime(note.occurred_at) })
890
- ] }),
891
- note.summary && /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: note.summary }),
892
- /* @__PURE__ */ jsx(Text, { size: "sm", children: note.content })
893
- ] }) }, note.id)) })
894
- ] });
895
- }
896
- function isResumeContext2(val) {
897
- return !!val && typeof val === "object" && !Array.isArray(val);
898
- }
899
- function extractContext(task) {
900
- if (isResumeContext2(task.resume_context)) return task.resume_context;
901
- return {};
902
- }
903
- function useMergeResumeContext() {
904
- const { apiRequest } = useElevasisServices();
905
- const queryClient = useQueryClient();
906
- return useMutation({
907
- mutationFn: async ({ taskId, patch }) => {
908
- const res = await apiRequest(`/project-tasks/${taskId}/resume-context`, {
909
- method: "PATCH",
910
- body: JSON.stringify(patch)
911
- });
912
- return res.task;
913
- },
914
- onSuccess: (_data, variables) => {
915
- queryClient.invalidateQueries({ queryKey: projectKeys.tasks(variables.projectId) });
916
- },
917
- onError: (error) => {
918
- showApiErrorNotification(error);
919
- }
920
- });
921
- }
922
- function InlineResumeContextEditor({ task, projectId }) {
923
- const [expanded, setExpanded] = useState(false);
924
- const ctx = extractContext(task);
925
- const [currentState, setCurrentState] = useState(ctx.current_state ?? "");
926
- const [nextSteps, setNextSteps] = useState(ctx.next_steps ?? "");
927
- const [filesModified, setFilesModified] = useState(typeof ctx.files_modified === "string" ? ctx.files_modified : "");
928
- const { mutate: mergeContext, isPending } = useMergeResumeContext();
929
- const save = useCallback(
930
- (patch) => {
931
- const nonEmpty = Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== void 0));
932
- if (Object.keys(nonEmpty).length === 0) return;
933
- mergeContext({ taskId: task.id, projectId, patch: nonEmpty });
934
- },
935
- [mergeContext, task.id, projectId]
936
- );
937
- return /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
938
- /* @__PURE__ */ jsxs(Group, { gap: "xs", style: { cursor: "pointer", userSelect: "none" }, onClick: () => setExpanded((v) => !v), children: [
939
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "xs", tabIndex: -1, children: expanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: 12 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 12 }) }),
940
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Resume context" }),
941
- isPending && /* @__PURE__ */ jsx(Loader, { size: 10 })
942
- ] }),
943
- /* @__PURE__ */ jsx(Collapse, { in: expanded, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", pl: "md", pt: 4, children: [
944
- /* @__PURE__ */ jsx(
945
- Textarea,
946
- {
947
- label: "Current state",
948
- placeholder: "What is the task's current state?",
949
- size: "xs",
950
- minRows: 2,
951
- autosize: true,
952
- value: currentState,
953
- onChange: (e) => setCurrentState(e.target.value),
954
- onBlur: () => {
955
- if (currentState !== (ctx.current_state ?? "")) save({ current_state: currentState });
956
- }
957
- }
958
- ),
959
- /* @__PURE__ */ jsx(
960
- Textarea,
961
- {
962
- label: "Next steps",
963
- placeholder: "What should happen next?",
964
- size: "xs",
965
- minRows: 2,
966
- autosize: true,
967
- value: nextSteps,
968
- onChange: (e) => setNextSteps(e.target.value),
969
- onBlur: () => {
970
- if (nextSteps !== (ctx.next_steps ?? "")) save({ next_steps: nextSteps });
971
- }
972
- }
973
- ),
974
- /* @__PURE__ */ jsx(
975
- Textarea,
976
- {
977
- label: "Files modified (one per line)",
978
- placeholder: "src/foo.ts\nsrc/bar.ts",
979
- size: "xs",
980
- minRows: 2,
981
- autosize: true,
982
- value: filesModified,
983
- onChange: (e) => setFilesModified(e.target.value),
984
- onBlur: () => {
985
- const original = typeof ctx.files_modified === "string" ? ctx.files_modified : "";
986
- if (filesModified !== original) save({ files_modified: filesModified });
987
- }
988
- }
989
- )
990
- ] }) })
991
- ] });
992
- }
993
- var noteTypeOptions = [
994
- { value: "call_note", label: "Call Note" },
995
- { value: "status_update", label: "Status Update" },
996
- { value: "issue", label: "Issue" },
997
- { value: "blocker", label: "Blocker" },
998
- { value: "agent_learning", label: "Agent Learning" }
999
- ];
1000
- function parseChecklist2(milestone) {
1001
- const raw = milestone.checklist;
1002
- if (!Array.isArray(raw)) return [];
1003
- return raw;
1004
- }
1005
- function sortMilestonesForTimeline(milestones) {
1006
- return [...milestones].sort((a, b) => {
1007
- if (a.sequence !== b.sequence) return a.sequence - b.sequence;
1008
- const aCreated = a.created_at ? new Date(a.created_at).getTime() : 0;
1009
- const bCreated = b.created_at ? new Date(b.created_at).getTime() : 0;
1010
- if (aCreated !== bCreated) return aCreated - bCreated;
1011
- return a.name.localeCompare(b.name);
1012
- });
1013
- }
1014
- var bulletSize = 14;
1015
- function renderMilestoneBullet(status) {
1016
- const normalizedStatus = status ?? "upcoming";
1017
- const isCompleted = normalizedStatus === "completed";
1018
- const isInProgress = normalizedStatus === "in_progress";
1019
- if (isCompleted) {
1020
- return /* @__PURE__ */ jsx(
1021
- "div",
1022
- {
1023
- style: {
1024
- width: bulletSize,
1025
- height: bulletSize,
1026
- borderRadius: "50%",
1027
- backgroundColor: "var(--color-primary)",
1028
- boxShadow: "0 0 0 4px var(--color-surface)"
1029
- }
1030
- }
1031
- );
1032
- }
1033
- const fillColor = isInProgress ? "var(--color-primary)" : "color-mix(in srgb, var(--color-primary) 50%, transparent)";
1034
- const borderColor = isInProgress ? "var(--color-primary)" : "color-mix(in srgb, var(--color-primary) 20%, transparent)";
1035
- return /* @__PURE__ */ jsx(
1036
- "div",
1037
- {
1038
- style: {
1039
- width: bulletSize,
1040
- height: bulletSize,
1041
- borderRadius: "50%",
1042
- backgroundColor: fillColor,
1043
- border: `2px solid ${borderColor}`,
1044
- boxSizing: "border-box",
1045
- zIndex: 2,
1046
- boxShadow: "0 0 0 4px var(--color-surface)"
1047
- }
1048
- }
1049
- );
1050
- }
1051
- function MilestoneChecklist({ milestone }) {
1052
- const items = parseChecklist2(milestone);
1053
- const { mutate: updateMilestone } = useUpdateMilestone();
1054
- function saveChecklist(updated) {
1055
- const allComplete = updated.length > 0 && updated.every((item) => item.completed);
1056
- const wasComplete = milestone.status === "completed";
1057
- const updates = {
1058
- checklist: updated
1059
- };
1060
- if (allComplete && !wasComplete) {
1061
- updates.status = "completed";
1062
- updates.completed_at = (/* @__PURE__ */ new Date()).toISOString();
1063
- } else if (!allComplete && wasComplete) {
1064
- updates.status = "in_progress";
1065
- updates.completed_at = null;
1066
- }
1067
- updateMilestone({ id: milestone.id, updates });
1068
- }
1069
- return /* @__PURE__ */ jsx(
1070
- Checklist,
1071
- {
1072
- items,
1073
- onToggle: (itemId) => {
1074
- const updated = items.map((item) => item.id === itemId ? { ...item, completed: !item.completed } : item);
1075
- saveChecklist(updated);
1076
- },
1077
- onAdd: (label) => {
1078
- const newItem = { id: crypto.randomUUID(), label, completed: false };
1079
- saveChecklist([...items, newItem]);
1080
- },
1081
- onRemove: (itemId) => {
1082
- saveChecklist(items.filter((item) => item.id !== itemId));
1083
- }
1084
- }
1085
- );
1086
- }
1087
- function AddNoteModal({ opened, onClose, projectId }) {
1088
- const [noteType, setNoteType] = useState("call_note");
1089
- const [content, setContent] = useState("");
1090
- const [summary, setSummary] = useState("");
1091
- const { mutate: createNote, isPending } = useCreateNote();
1092
- function handleSubmit() {
1093
- if (!content.trim() || !noteType) return;
1094
- createNote(
1095
- {
1096
- project_id: projectId,
1097
- type: noteType,
1098
- content: content.trim(),
1099
- summary: summary.trim() || null
1100
- },
1101
- {
1102
- onSuccess: () => {
1103
- setContent("");
1104
- setSummary("");
1105
- setNoteType("call_note");
1106
- onClose();
1107
- }
1108
- }
1109
- );
1110
- }
1111
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose, size: "md", loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1112
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
1113
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Add Note" }),
1114
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, disabled: isPending, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
1115
- ] }),
1116
- /* @__PURE__ */ jsx(Select, { label: "Type", data: noteTypeOptions, value: noteType, onChange: setNoteType, required: true }),
1117
- /* @__PURE__ */ jsx(
1118
- TextInput,
1119
- {
1120
- label: "Summary",
1121
- placeholder: "Brief summary (optional)",
1122
- value: summary,
1123
- onChange: (event) => setSummary(event.target.value)
1124
- }
1125
- ),
1126
- /* @__PURE__ */ jsx(
1127
- Textarea,
1128
- {
1129
- label: "Content",
1130
- placeholder: "Note content...",
1131
- minRows: 4,
1132
- value: content,
1133
- onChange: (event) => setContent(event.target.value),
1134
- required: true
1135
- }
1136
- ),
1137
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1138
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: onClose, disabled: isPending, children: "Cancel" }),
1139
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: isPending, disabled: !content.trim() || !noteType, children: "Add Note" })
1140
- ] })
1141
- ] }) });
1142
- }
1143
- function LogBlockerModal({ opened, onClose, projectId }) {
1144
- const [content, setContent] = useState("");
1145
- const [summary, setSummary] = useState("");
1146
- const { mutate: createNote, isPending } = useCreateNote();
1147
- function handleSubmit() {
1148
- if (!content.trim()) return;
1149
- createNote(
1150
- {
1151
- project_id: projectId,
1152
- type: "blocker",
1153
- content: content.trim(),
1154
- summary: summary.trim() || null
1155
- },
1156
- {
1157
- onSuccess: () => {
1158
- setContent("");
1159
- setSummary("");
1160
- onClose();
1161
- }
1162
- }
1163
- );
1164
- }
1165
- return /* @__PURE__ */ jsx(Modal, { opened, onClose, size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1166
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Log Blocker" }),
1167
- /* @__PURE__ */ jsx(
1168
- TextInput,
1169
- {
1170
- label: "Summary",
1171
- placeholder: "Brief blocker summary (optional)",
1172
- value: summary,
1173
- onChange: (e) => setSummary(e.target.value)
1174
- }
1175
- ),
1176
- /* @__PURE__ */ jsx(
1177
- Textarea,
1178
- {
1179
- label: "Description",
1180
- placeholder: "Describe the blocker...",
1181
- minRows: 4,
1182
- value: content,
1183
- onChange: (e) => setContent(e.target.value),
1184
- required: true
1185
- }
1186
- ),
1187
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1188
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: onClose, disabled: isPending, children: "Cancel" }),
1189
- /* @__PURE__ */ jsx(Button, { color: "orange", onClick: handleSubmit, loading: isPending, disabled: !content.trim(), children: "Log Blocker" })
1190
- ] })
1191
- ] }) });
1192
- }
1193
- function ProjectDetailPage({ projectId, onBack, backLabel = "Projects" }) {
1194
- useProjectRealtime(projectId);
1195
- const { data: project, isLoading, error } = useProject(projectId);
1196
- const { data: notes } = useProjectNotes({ projectId });
1197
- const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject();
1198
- const [addNoteOpen, setAddNoteOpen] = useState(false);
1199
- const [deleteModalOpen, setDeleteModalOpen] = useState(false);
1200
- const [logBlockerOpen, { open: openLogBlocker, close: closeLogBlocker }] = useDisclosure(false);
1201
- const [createTaskOpen, { open: openCreateTask, close: closeCreateTask }] = useDisclosure(false);
1202
- const [addMilestoneOpen, { open: openAddMilestone, close: closeAddMilestone }] = useDisclosure(false);
1203
- if (isLoading) {
1204
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Loader, {}) }) });
1205
- }
1206
- if (error) {
1207
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "red", children: "Error loading project" }) }) });
1208
- }
1209
- if (!project) {
1210
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "Project not found" }) }) });
1211
- }
1212
- const milestones = sortMilestonesForTimeline(project.milestones ?? []);
1213
- const tasks = project.tasks ?? [];
1214
- const totalMilestones = milestones.length;
1215
- const completedMilestones = milestones.filter((milestone) => milestone.status === "completed").length;
1216
- const totalTasks = tasks.length;
1217
- const approvedTasks = tasks.filter((task) => task.status === "approved").length;
1218
- const milestoneProgress = calculateProgress(completedMilestones, totalMilestones);
1219
- const taskProgress = calculateProgress(approvedTasks, totalTasks);
1220
- const companyName = project.company?.name ?? null;
1221
- const activeMilestoneIndex = milestones.reduce(
1222
- (lastIndex, milestone, index) => milestone.status !== "upcoming" ? index : lastIndex,
1223
- -1
1224
- );
1225
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
1226
- /* @__PURE__ */ jsx(
1227
- PageTitleCaption,
1228
- {
1229
- title: project.name,
1230
- caption: [companyName, formatStatusLabel(project.status || "unknown")].filter(Boolean).join(" - ")
1231
- }
1232
- ),
1233
- /* @__PURE__ */ jsx(
1234
- QuickActionsRow,
1235
- {
1236
- onCreateTask: openCreateTask,
1237
- onAddMilestone: openAddMilestone,
1238
- onLogBlocker: openLogBlocker,
1239
- onBack,
1240
- backLabel
1241
- }
1242
- ),
1243
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
1244
- /* @__PURE__ */ jsx(
1245
- StatCard,
1246
- {
1247
- variant: "hero",
1248
- icon: IconHeartbeat,
1249
- value: formatStatusLabel(project.status || "unknown"),
1250
- label: "Status",
1251
- valueColor: `var(--mantine-color-${projectStatusColors[project.status || ""] || "gray"}-6)`
1252
- }
1253
- ),
1254
- /* @__PURE__ */ jsx(
1255
- StatCard,
1256
- {
1257
- variant: "hero",
1258
- icon: IconFlag,
1259
- value: `${completedMilestones}/${totalMilestones}`,
1260
- label: "Milestones",
1261
- children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1262
- milestoneProgress,
1263
- "% complete"
1264
- ] })
1265
- }
1266
- ),
1267
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconChecklist, value: `${approvedTasks}/${totalTasks}`, label: "Tasks", children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1268
- taskProgress,
1269
- "% complete"
1270
- ] }) }),
1271
- /* @__PURE__ */ jsx(
1272
- StatCard,
1273
- {
1274
- variant: "hero",
1275
- icon: IconCalendar,
1276
- value: project.start_date ? formatDate(project.start_date) : "-",
1277
- label: "Timeline",
1278
- children: project.target_end_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1279
- "to ",
1280
- formatDate(project.target_end_date)
1281
- ] })
1282
- }
1283
- )
1284
- ] }),
1285
- /* @__PURE__ */ jsxs(
1286
- Paper,
1287
- {
1288
- p: "md",
1289
- style: {
1290
- border: "1px solid var(--color-border)",
1291
- borderRadius: "var(--mantine-radius-default)"
1292
- },
1293
- children: [
1294
- /* @__PURE__ */ jsx(Stack, { gap: "md", children: /* @__PURE__ */ jsx(
1295
- CardHeader,
1296
- {
1297
- icon: /* @__PURE__ */ jsx(IconFileDescription, { size: 18 }),
1298
- title: "Project Details",
1299
- rightSection: /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "wrap", justify: "flex-end", children: [
1300
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", children: formatStatusLabel(project.status || "unknown") }),
1301
- /* @__PURE__ */ jsx(LinkedRecordChips, { dealId: project.deal_id, clientCompanyId: project.client_company_id }),
1302
- /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", c: "var(--color-text-subtle)", children: project.id }),
1303
- /* @__PURE__ */ jsx(CopyButton, { value: `/project work ${project.id}`, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, children: /* @__PURE__ */ jsx(
1304
- ActionIcon,
1305
- {
1306
- onClick: copy,
1307
- variant: "subtle",
1308
- size: "xs",
1309
- color: copied ? "green" : "var(--color-text-subtle)",
1310
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1311
- }
1312
- ) }) })
1313
- ] })
1314
- }
1315
- ) }),
1316
- /* @__PURE__ */ jsx(Space, { my: "xs" }),
1317
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1318
- project.description && /* @__PURE__ */ jsxs("div", { children: [
1319
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Description" }),
1320
- /* @__PURE__ */ jsx(Text, { size: "sm", children: project.description })
1321
- ] }),
1322
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
1323
- /* @__PURE__ */ jsxs("div", { children: [
1324
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Start Date" }),
1325
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.start_date) })
1326
- ] }),
1327
- /* @__PURE__ */ jsxs("div", { children: [
1328
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Target End Date" }),
1329
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.target_end_date) })
1330
- ] }),
1331
- /* @__PURE__ */ jsxs("div", { children: [
1332
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Actual End Date" }),
1333
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.actual_end_date) })
1334
- ] }),
1335
- /* @__PURE__ */ jsxs("div", { children: [
1336
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Contract Value" }),
1337
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: project.contract_value != null ? `$${project.contract_value.toLocaleString()}${project.metadata && project.metadata.rate_type === "hourly" ? "/hr" : ""}` : "-" })
1338
- ] })
1339
- ] }),
1340
- project.company && /* @__PURE__ */ jsxs("div", { children: [
1341
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Company" }),
1342
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: project.company.name }),
1343
- project.company.domain && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: project.company.domain })
1344
- ] })
1345
- ] }),
1346
- /* @__PURE__ */ jsx(Space, { my: "xs" }),
1347
- /* @__PURE__ */ jsx(ResumeContextPreview, { tasks }),
1348
- /* @__PURE__ */ jsx(Space, { my: "md" }),
1349
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "status", children: [
1350
- /* @__PURE__ */ jsxs(Tabs.List, { children: [
1351
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "status", children: "Status" }),
1352
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "notes", children: "Notes" }),
1353
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", children: "Activity" })
1354
- ] }),
1355
- /* @__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(
1356
- Timeline,
1357
- {
1358
- active: activeMilestoneIndex,
1359
- bulletSize: 20,
1360
- lineWidth: 3,
1361
- color: "var(--color-primary)",
1362
- styles: {
1363
- itemBullet: {
1364
- borderColor: "transparent",
1365
- backgroundColor: "transparent",
1366
- padding: 0,
1367
- zIndex: 3,
1368
- overflow: "visible",
1369
- display: "flex",
1370
- alignItems: "center",
1371
- justifyContent: "center"
1372
- },
1373
- item: {
1374
- "--_item-border-color": "color-mix(in srgb, var(--color-primary) 22%, transparent)",
1375
- paddingBottom: "var(--mantine-spacing-xl)"
1376
- },
1377
- itemTitle: {
1378
- color: "var(--color-text)"
1379
- },
1380
- itemBody: {
1381
- marginTop: 0
1382
- }
1383
- },
1384
- children: milestones.map((milestone) => {
1385
- const milestoneTasks = tasks.filter((task) => task.milestone_id === milestone.id);
1386
- const checklistItems = parseChecklist2(milestone);
1387
- const completedChecklistItems = checklistItems.filter((item) => item.completed).length;
1388
- return /* @__PURE__ */ jsx(
1389
- Timeline.Item,
1390
- {
1391
- bullet: renderMilestoneBullet(milestone.status),
1392
- title: /* @__PURE__ */ jsx("div", {}),
1393
- children: /* @__PURE__ */ jsx(
1394
- Paper,
1395
- {
1396
- p: "lg",
1397
- radius: "md",
1398
- style: {
1399
- background: "transparent",
1400
- border: "none",
1401
- borderTop: "1px solid var(--color-border)",
1402
- borderRight: "1px solid var(--color-border)",
1403
- borderBottom: "1px solid var(--color-border)",
1404
- boxShadow: "none",
1405
- position: "relative",
1406
- overflow: "hidden"
1407
- },
1408
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1409
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "wrap", children: [
1410
- /* @__PURE__ */ jsxs(Stack, { gap: 6, style: { flex: 1, minWidth: 260 }, children: [
1411
- /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
1412
- /* @__PURE__ */ jsx(Text, { fw: 700, size: "lg", c: "var(--color-text)", children: milestone.name }),
1413
- /* @__PURE__ */ jsx(
1414
- Badge,
1415
- {
1416
- variant: "light",
1417
- color: milestoneStatusColors[milestone.status || ""] || "gray",
1418
- size: "sm",
1419
- styles: {
1420
- root: {
1421
- letterSpacing: "0.02em"
1422
- }
1423
- },
1424
- children: formatStatusLabel(milestone.status || "unknown")
1425
- }
1426
- )
1427
- ] }),
1428
- milestone.description && /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--color-text-dimmed)", maw: 900, children: milestone.description })
1429
- ] }),
1430
- /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
1431
- milestone.due_date && /* @__PURE__ */ jsxs(
1432
- Badge,
1433
- {
1434
- variant: "outline",
1435
- size: "md",
1436
- styles: {
1437
- root: {
1438
- borderColor: "var(--border-primary-subtle)",
1439
- backgroundColor: "var(--surface-primary-faint)"
1440
- }
1441
- },
1442
- children: [
1443
- "Due ",
1444
- formatDate(milestone.due_date)
1445
- ]
1446
- }
1447
- ),
1448
- /* @__PURE__ */ jsxs(
1449
- Badge,
1450
- {
1451
- variant: "outline",
1452
- size: "md",
1453
- styles: {
1454
- root: {
1455
- borderColor: "var(--border-primary-soft)",
1456
- backgroundColor: "var(--color-surface)"
1457
- }
1458
- },
1459
- children: [
1460
- completedChecklistItems,
1461
- "/",
1462
- checklistItems.length,
1463
- " complete"
1464
- ]
1465
- }
1466
- ),
1467
- milestoneTasks.length > 0 && /* @__PURE__ */ jsxs(
1468
- Badge,
1469
- {
1470
- variant: "outline",
1471
- size: "md",
1472
- styles: {
1473
- root: {
1474
- borderColor: "var(--border-primary-soft)",
1475
- backgroundColor: "var(--color-surface)"
1476
- }
1477
- },
1478
- children: [
1479
- milestoneTasks.length,
1480
- " tasks"
1481
- ]
1482
- }
1483
- )
1484
- ] })
1485
- ] }),
1486
- /* @__PURE__ */ jsx(MilestoneChecklist, { milestone }),
1487
- milestoneTasks.length > 0 && /* @__PURE__ */ jsx(Stack, { gap: "sm", children: milestoneTasks.map((task) => /* @__PURE__ */ jsx(
1488
- Card,
1489
- {
1490
- p: "sm",
1491
- radius: "md",
1492
- style: {
1493
- border: "1px solid var(--border-primary-faint)",
1494
- backgroundColor: "color-mix(in srgb, var(--color-surface-hover) 70%, var(--color-surface))",
1495
- boxShadow: "var(--standard-box-shadow)"
1496
- },
1497
- children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1498
- /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "space-between", align: "flex-start", wrap: "wrap", children: [
1499
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, c: "var(--color-text)", children: task.name }),
1500
- /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
1501
- /* @__PURE__ */ jsx(
1502
- Badge,
1503
- {
1504
- variant: "light",
1505
- color: taskTypeColors[task.type || ""] || "gray",
1506
- size: "xs",
1507
- children: formatStatusLabel(task.type || "other")
1508
- }
1509
- ),
1510
- /* @__PURE__ */ jsx(
1511
- Badge,
1512
- {
1513
- variant: "light",
1514
- color: taskStatusColors[task.status || ""] || "gray",
1515
- size: "xs",
1516
- children: formatStatusLabel(task.status || "pending")
1517
- }
1518
- )
1519
- ] })
1520
- ] }),
1521
- /* @__PURE__ */ jsx(InlineResumeContextEditor, { task, projectId })
1522
- ] })
1523
- },
1524
- task.id
1525
- )) })
1526
- ] })
1527
- }
1528
- )
1529
- },
1530
- milestone.id
1531
- );
1532
- })
1533
- }
1534
- ) }),
1535
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "notes", pt: "md", children: /* @__PURE__ */ jsx(NotesTabContent, { notes, onAddNote: () => setAddNoteOpen(true) }) }),
1536
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(ProjectActivityTimeline, { projectId }) })
1537
- ] })
1538
- ]
1539
- }
1540
- ),
1541
- /* @__PURE__ */ jsx(AddNoteModal, { opened: addNoteOpen, onClose: () => setAddNoteOpen(false), projectId }),
1542
- /* @__PURE__ */ jsx(LogBlockerModal, { opened: logBlockerOpen, onClose: closeLogBlocker, projectId }),
1543
- /* @__PURE__ */ jsx(
1544
- CreateDeliveryEntityModal,
1545
- {
1546
- opened: createTaskOpen,
1547
- onClose: closeCreateTask,
1548
- projectId,
1549
- entityType: "task"
1550
- }
1551
- ),
1552
- /* @__PURE__ */ jsx(
1553
- CreateDeliveryEntityModal,
1554
- {
1555
- opened: addMilestoneOpen,
1556
- onClose: closeAddMilestone,
1557
- projectId,
1558
- entityType: "milestone"
1559
- }
1560
- ),
1561
- /* @__PURE__ */ jsx(CustomModal, { opened: deleteModalOpen, onClose: () => setDeleteModalOpen(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1562
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
1563
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Delete project" }),
1564
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: () => setDeleteModalOpen(false), children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
1565
- ] }),
1566
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1567
- "Are you sure you want to delete ",
1568
- /* @__PURE__ */ jsx("strong", { children: project.name }),
1569
- "? This cannot be undone."
1570
- ] }),
1571
- /* @__PURE__ */ jsx(Divider, {}),
1572
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1573
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setDeleteModalOpen(false), disabled: isDeleting, children: "Cancel" }),
1574
- /* @__PURE__ */ jsx(
1575
- Button,
1576
- {
1577
- color: "red",
1578
- loading: isDeleting,
1579
- onClick: () => deleteProject(project.id, {
1580
- onSuccess: () => {
1581
- setDeleteModalOpen(false);
1582
- onBack();
1583
- }
1584
- }),
1585
- children: "Delete project"
1586
- }
1587
- )
1588
- ] })
1589
- ] }) })
1590
- ] }) }) });
1591
- }
1592
-
1593
- export { AllTasksPage, Checklist, CreateDeliveryEntityModal, DELIVERY_COMMUNICATION_ITEMS, DELIVERY_PROJECT_ITEMS, DELIVERY_WORK_ITEMS, HealthStatusCard, MilestoneTimeline, ProjectDetailPage, ProjectsListPage, ProjectsSidebar, ProjectsSidebarMiddle, ProjectsSidebarTop, TaskCard, UpcomingMilestonesPage, deliveryManifest };