@elevasis/ui 2.22.0 → 2.24.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.
- package/dist/app/index.d.ts +2915 -0
- package/dist/app/index.js +5 -4
- package/dist/{chunk-3HEUGBOT.js → chunk-2WZ635SS.js} +2 -2
- package/dist/{chunk-GJ7EIABJ.js → chunk-4NWNS7TX.js} +1 -1
- package/dist/{chunk-LKVBEE63.js → chunk-FUEXGRFR.js} +2 -2
- package/dist/{chunk-6IA2OMAE.js → chunk-HC2KV6BU.js} +9 -0
- package/dist/{chunk-IBUYJXA3.js → chunk-KCJ6VATY.js} +1133 -578
- package/dist/{chunk-WWJ6S2HQ.js → chunk-KLFIJDTD.js} +1 -1
- package/dist/{chunk-COG4ABRI.js → chunk-M2HWJY6O.js} +704 -375
- package/dist/{chunk-LVJGPE6H.js → chunk-MTR6AN2C.js} +3 -12
- package/dist/chunk-OWHQ65EQ.js +211 -0
- package/dist/{chunk-XOTJNW4Q.js → chunk-QIW6OCEI.js} +18 -1
- package/dist/{chunk-B4FHWKEF.js → chunk-QULLZ5PE.js} +193 -10
- package/dist/{chunk-QZJM3RYI.js → chunk-SNHGSCKH.js} +1 -1
- package/dist/{chunk-LVUCBY7X.js → chunk-UDJE54WN.js} +85 -3
- package/dist/{chunk-IOTLB6ND.js → chunk-VGNAV3TH.js} +406 -195
- package/dist/{chunk-BSZRKBAW.js → chunk-YBZT7MJR.js} +3 -3
- package/dist/{chunk-SQ5JGELM.js → chunk-ZDKQNQ4X.js} +19 -1
- package/dist/components/index.d.ts +500 -466
- package/dist/components/index.js +75 -32
- package/dist/components/navigation/index.js +2 -2
- package/dist/features/auth/index.d.ts +472 -389
- package/dist/features/crm/index.d.ts +468 -391
- package/dist/features/crm/index.js +8 -8
- package/dist/features/dashboard/index.js +8 -8
- package/dist/features/delivery/index.d.ts +466 -383
- package/dist/features/delivery/index.js +8 -8
- package/dist/features/lead-gen/index.d.ts +213 -65
- package/dist/features/lead-gen/index.js +9 -8
- package/dist/features/monitoring/index.js +9 -9
- package/dist/features/monitoring/requests/index.js +7 -7
- package/dist/features/operations/index.js +11 -10
- package/dist/features/settings/index.d.ts +472 -389
- package/dist/features/settings/index.js +9 -9
- package/dist/hooks/delivery/index.d.ts +466 -383
- package/dist/hooks/index.d.ts +967 -744
- package/dist/hooks/index.js +7 -7
- package/dist/hooks/published.d.ts +967 -744
- package/dist/hooks/published.js +7 -7
- package/dist/index.d.ts +1360 -1069
- package/dist/index.js +8 -8
- package/dist/initialization/index.d.ts +472 -389
- package/dist/organization/index.d.ts +11 -1
- package/dist/organization/index.js +2 -2
- package/dist/profile/index.d.ts +472 -389
- package/dist/provider/index.d.ts +3132 -169
- package/dist/provider/index.js +6 -6
- package/dist/provider/published.d.ts +3098 -168
- package/dist/provider/published.js +3 -3
- package/dist/supabase/index.d.ts +577 -413
- package/dist/test-utils/index.d.ts +21 -1
- package/dist/test-utils/index.js +13 -4
- package/dist/theme/index.js +2 -2
- package/dist/types/index.d.ts +472 -389
- package/package.json +2 -2
- package/src/test-utils/README.md +2 -0
- /package/dist/{chunk-ZBCTB5CA.js → chunk-EIOJNUPL.js} +0 -0
|
@@ -4,16 +4,18 @@ import { SubshellNavItem } from './chunk-CEWTOKE7.js';
|
|
|
4
4
|
import { SubshellSidebarSection } from './chunk-IIMU5YAJ.js';
|
|
5
5
|
import { FilterBar } from './chunk-PDHTXPSF.js';
|
|
6
6
|
import { CustomModal } from './chunk-KVJ3LFH2.js';
|
|
7
|
-
import { useDealTasksDue, useDealsLookup, useCreateDealTask, useDealsSummary, useDeleteDeal, usePaginationState, useDeals, useTableSort, sortData, useTableSelection, useDealDetail, useCompany } from './chunk-
|
|
7
|
+
import { useExecuteAction, useDealTasksDue, useDealsLookup, useCreateDealTask, useDealsSummary, useDeleteDeal, usePaginationState, useDeals, useTableSort, sortData, useTableSelection, useDealDetail, useCompany } from './chunk-QULLZ5PE.js';
|
|
8
|
+
import { useCrmActions, deriveActions } from './chunk-UDJE54WN.js';
|
|
8
9
|
import { SubshellContentContainer } from './chunk-TKAYX2SP.js';
|
|
9
10
|
import { CenteredErrorState, CardHeader, PageTitleCaption, EmptyState, ActivityTimeline } from './chunk-XUYBOO32.js';
|
|
10
11
|
import { useRouterContext } from './chunk-Q7DJKLEN.js';
|
|
11
12
|
import { PAGE_SIZE_DEFAULT, formatTimeAgo } from './chunk-SGXXJE52.js';
|
|
12
13
|
import { useElevasisServices } from './chunk-IRW7JMQ4.js';
|
|
13
|
-
import {
|
|
14
|
-
import { IconLayoutGrid, IconColumns, IconFileInvoice, IconAddressBook, IconTrophy, IconClockExclamation, IconUser, IconPlus, IconChecklist,
|
|
14
|
+
import { Button, Modal, Stack, NumberInput, Switch, Select, Textarea, TextInput, Group, Alert, Text, Box, Badge, Center, Loader, UnstyledButton, Title, Paper, Table, SimpleGrid, Checkbox, Pagination, ActionIcon, Tabs, Card, Code, Divider, CopyButton, Tooltip } from '@mantine/core';
|
|
15
|
+
import { IconLayoutGrid, IconColumns, IconFileInvoice, IconAddressBook, IconAlertCircle, IconTrophy, IconClockExclamation, IconUser, IconPlus, IconChecklist, IconHistory, IconSearch, IconTargetArrow, IconAlertTriangle, IconArrowLeft, IconFileText, IconTrash, IconX, IconBuilding, IconCheckbox, IconCalendar, IconMail, IconPhone, IconArrowRight, IconNote, IconCheck, IconCopy } from '@tabler/icons-react';
|
|
15
16
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
16
17
|
import { useState, useMemo, useEffect } from 'react';
|
|
18
|
+
import { useForm } from '@mantine/form';
|
|
17
19
|
import { useQuery } from '@tanstack/react-query';
|
|
18
20
|
import { useNavigate } from '@tanstack/react-router';
|
|
19
21
|
|
|
@@ -43,6 +45,282 @@ var SAVED_VIEW_PRESETS = [
|
|
|
43
45
|
urlFilters: { stage: "closed_won" }
|
|
44
46
|
}
|
|
45
47
|
];
|
|
48
|
+
function ActionButton({ action, dealId }) {
|
|
49
|
+
const executeAction = useExecuteAction({ dealId });
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
Button,
|
|
52
|
+
{
|
|
53
|
+
variant: "light",
|
|
54
|
+
size: "sm",
|
|
55
|
+
loading: executeAction.isPending,
|
|
56
|
+
onClick: () => executeAction.mutate({ key: action.key }),
|
|
57
|
+
children: action.label
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function getType(schema) {
|
|
62
|
+
const zodSchema = schema;
|
|
63
|
+
return zodSchema._def?.type ?? zodSchema._def?.typeName ?? "";
|
|
64
|
+
}
|
|
65
|
+
function unwrapField(schema) {
|
|
66
|
+
const type = getType(schema);
|
|
67
|
+
if (type === "optional" || type === "ZodOptional") {
|
|
68
|
+
const inner = schema._def?.innerType;
|
|
69
|
+
if (!inner) return { schema, required: false, nullable: false, defaultValue: void 0 };
|
|
70
|
+
return { ...unwrapField(inner), required: false };
|
|
71
|
+
}
|
|
72
|
+
if (type === "nullable" || type === "ZodNullable") {
|
|
73
|
+
const inner = schema._def?.innerType;
|
|
74
|
+
if (!inner) return { schema, required: true, nullable: true, defaultValue: null };
|
|
75
|
+
return { ...unwrapField(inner), nullable: true };
|
|
76
|
+
}
|
|
77
|
+
if (type === "default" || type === "ZodDefault") {
|
|
78
|
+
const def = schema._def;
|
|
79
|
+
const inner = def?.innerType;
|
|
80
|
+
const defaultValue = typeof def?.defaultValue === "function" ? def.defaultValue() : def?.defaultValue;
|
|
81
|
+
if (!inner) return { schema, required: false, nullable: false, defaultValue };
|
|
82
|
+
return { ...unwrapField(inner), required: false, defaultValue };
|
|
83
|
+
}
|
|
84
|
+
return { schema, required: true, nullable: false, defaultValue: void 0 };
|
|
85
|
+
}
|
|
86
|
+
function getObjectShape(schema) {
|
|
87
|
+
const zodSchema = schema;
|
|
88
|
+
const type = getType(schema);
|
|
89
|
+
if (type !== "object" && type !== "ZodObject") {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const shape = zodSchema.shape;
|
|
93
|
+
if (!shape || typeof shape !== "object" || Array.isArray(shape)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return shape;
|
|
97
|
+
}
|
|
98
|
+
function getEnumValues(schema) {
|
|
99
|
+
const zodSchema = schema;
|
|
100
|
+
const values = zodSchema.options ?? zodSchema._def?.values ?? Object.values(zodSchema._def?.entries ?? {});
|
|
101
|
+
if (!Array.isArray(values) || values.some((value) => typeof value !== "string")) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return values;
|
|
105
|
+
}
|
|
106
|
+
function labelForField(name) {
|
|
107
|
+
return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (s) => s.toUpperCase());
|
|
108
|
+
}
|
|
109
|
+
function isLongTextField(name) {
|
|
110
|
+
return ["body", "message", "reply", "replyBody", "emailBody"].includes(name);
|
|
111
|
+
}
|
|
112
|
+
function initialValueFor(field, defaultValue) {
|
|
113
|
+
if (defaultValue !== void 0) {
|
|
114
|
+
if (field.kind === "date" && defaultValue instanceof Date) {
|
|
115
|
+
return defaultValue.toISOString().slice(0, 10);
|
|
116
|
+
}
|
|
117
|
+
return defaultValue;
|
|
118
|
+
}
|
|
119
|
+
switch (field.kind) {
|
|
120
|
+
case "boolean":
|
|
121
|
+
return false;
|
|
122
|
+
case "number":
|
|
123
|
+
return field.required ? 0 : "";
|
|
124
|
+
case "enum":
|
|
125
|
+
return field.required ? field.enumValues?.[0] ?? "" : "";
|
|
126
|
+
case "date":
|
|
127
|
+
case "dateString":
|
|
128
|
+
case "string":
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function describeSchema(schema) {
|
|
133
|
+
if (!schema) {
|
|
134
|
+
return { supported: false, reason: "This action does not define a payload schema." };
|
|
135
|
+
}
|
|
136
|
+
const shape = getObjectShape(schema);
|
|
137
|
+
if (!shape) {
|
|
138
|
+
return { supported: false, reason: "This action requires a payload schema that is not a flat object." };
|
|
139
|
+
}
|
|
140
|
+
const fields = [];
|
|
141
|
+
const initialValues = {};
|
|
142
|
+
for (const [name, rawFieldSchema] of Object.entries(shape)) {
|
|
143
|
+
const { schema: fieldSchema, required, nullable, defaultValue } = unwrapField(rawFieldSchema);
|
|
144
|
+
const type = getType(fieldSchema);
|
|
145
|
+
const zodField = fieldSchema;
|
|
146
|
+
const stringFormat = zodField._def?.format;
|
|
147
|
+
let descriptor = null;
|
|
148
|
+
if (type === "string" || type === "ZodString") {
|
|
149
|
+
descriptor = {
|
|
150
|
+
name,
|
|
151
|
+
label: labelForField(name),
|
|
152
|
+
kind: stringFormat === "date" ? "dateString" : "string",
|
|
153
|
+
schema: rawFieldSchema,
|
|
154
|
+
required,
|
|
155
|
+
nullable
|
|
156
|
+
};
|
|
157
|
+
} else if (type === "number" || type === "ZodNumber") {
|
|
158
|
+
descriptor = { name, label: labelForField(name), kind: "number", schema: rawFieldSchema, required, nullable };
|
|
159
|
+
} else if (type === "boolean" || type === "ZodBoolean") {
|
|
160
|
+
descriptor = { name, label: labelForField(name), kind: "boolean", schema: rawFieldSchema, required, nullable };
|
|
161
|
+
} else if (type === "enum" || type === "ZodEnum") {
|
|
162
|
+
const enumValues = getEnumValues(fieldSchema);
|
|
163
|
+
if (!enumValues) {
|
|
164
|
+
return { supported: false, reason: `Field "${name}" uses an enum shape that is not supported by this form.` };
|
|
165
|
+
}
|
|
166
|
+
descriptor = {
|
|
167
|
+
name,
|
|
168
|
+
label: labelForField(name),
|
|
169
|
+
kind: "enum",
|
|
170
|
+
schema: rawFieldSchema,
|
|
171
|
+
required,
|
|
172
|
+
nullable,
|
|
173
|
+
enumValues
|
|
174
|
+
};
|
|
175
|
+
} else if (type === "date" || type === "ZodDate") {
|
|
176
|
+
descriptor = { name, label: labelForField(name), kind: "date", schema: rawFieldSchema, required, nullable };
|
|
177
|
+
}
|
|
178
|
+
if (!descriptor) {
|
|
179
|
+
return {
|
|
180
|
+
supported: false,
|
|
181
|
+
reason: `Field "${name}" is not supported. CRM action forms currently support only flat string, number, boolean, enum, and date fields.`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const field = { ...descriptor, defaultValue: initialValueFor(descriptor, defaultValue) };
|
|
185
|
+
fields.push(field);
|
|
186
|
+
initialValues[name] = field.defaultValue;
|
|
187
|
+
}
|
|
188
|
+
return { supported: true, fields, initialValues };
|
|
189
|
+
}
|
|
190
|
+
function normalizeValue(field, value) {
|
|
191
|
+
const isEmpty = value === "" || value === void 0 || value === null;
|
|
192
|
+
if (isEmpty) {
|
|
193
|
+
if (!field.required) return void 0;
|
|
194
|
+
if (field.nullable) return null;
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
if (field.kind === "date") {
|
|
198
|
+
const acceptsString = (field.schema._def?.coerce ?? false) === true;
|
|
199
|
+
return acceptsString ? value : new Date(String(value));
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function buildPayload(fields, values) {
|
|
204
|
+
return fields.reduce((payload, field) => {
|
|
205
|
+
const value = normalizeValue(field, values[field.name]);
|
|
206
|
+
if (value !== void 0 || field.required) {
|
|
207
|
+
payload[field.name] = value;
|
|
208
|
+
}
|
|
209
|
+
return payload;
|
|
210
|
+
}, {});
|
|
211
|
+
}
|
|
212
|
+
function ActionFormButton({ action, dealId }) {
|
|
213
|
+
const [opened, setOpened] = useState(false);
|
|
214
|
+
const executeAction = useExecuteAction({ dealId });
|
|
215
|
+
const schemaDescription = describeSchema(action.payloadSchema);
|
|
216
|
+
const form = useForm({
|
|
217
|
+
initialValues: schemaDescription.supported ? schemaDescription.initialValues : {}
|
|
218
|
+
});
|
|
219
|
+
const handleClose = () => {
|
|
220
|
+
setOpened(false);
|
|
221
|
+
form.reset();
|
|
222
|
+
form.clearErrors();
|
|
223
|
+
};
|
|
224
|
+
const handleSubmit = form.onSubmit(async (values) => {
|
|
225
|
+
if (!schemaDescription.supported || !action.payloadSchema) return;
|
|
226
|
+
form.clearErrors();
|
|
227
|
+
const payload = buildPayload(schemaDescription.fields, values);
|
|
228
|
+
const parsed = action.payloadSchema.safeParse(payload);
|
|
229
|
+
if (!parsed.success) {
|
|
230
|
+
const formErrors = {};
|
|
231
|
+
for (const issue of parsed.error.issues) {
|
|
232
|
+
const fieldName = issue.path[0];
|
|
233
|
+
if (typeof fieldName === "string" && schemaDescription.fields.some((field) => field.name === fieldName)) {
|
|
234
|
+
formErrors[fieldName] = issue.message;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
form.setErrors(formErrors);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
await executeAction.mutateAsync({ key: action.key, payload: parsed.data });
|
|
241
|
+
handleClose();
|
|
242
|
+
});
|
|
243
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
244
|
+
/* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", onClick: () => setOpened(true), children: action.label }),
|
|
245
|
+
/* @__PURE__ */ jsx(Modal, { opened, onClose: handleClose, title: action.label, size: "xl", children: schemaDescription.supported ? /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
246
|
+
schemaDescription.fields.map((field) => {
|
|
247
|
+
if (field.kind === "number") {
|
|
248
|
+
return /* @__PURE__ */ jsx(
|
|
249
|
+
NumberInput,
|
|
250
|
+
{
|
|
251
|
+
label: field.label,
|
|
252
|
+
required: field.required,
|
|
253
|
+
disabled: executeAction.isPending,
|
|
254
|
+
error: form.errors[field.name],
|
|
255
|
+
...form.getInputProps(field.name)
|
|
256
|
+
},
|
|
257
|
+
field.name
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (field.kind === "boolean") {
|
|
261
|
+
return /* @__PURE__ */ jsx(
|
|
262
|
+
Switch,
|
|
263
|
+
{
|
|
264
|
+
label: field.label,
|
|
265
|
+
disabled: executeAction.isPending,
|
|
266
|
+
error: form.errors[field.name],
|
|
267
|
+
...form.getInputProps(field.name, { type: "checkbox" })
|
|
268
|
+
},
|
|
269
|
+
field.name
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (field.kind === "enum") {
|
|
273
|
+
return /* @__PURE__ */ jsx(
|
|
274
|
+
Select,
|
|
275
|
+
{
|
|
276
|
+
label: field.label,
|
|
277
|
+
required: field.required,
|
|
278
|
+
disabled: executeAction.isPending,
|
|
279
|
+
data: field.enumValues ?? [],
|
|
280
|
+
error: form.errors[field.name],
|
|
281
|
+
...form.getInputProps(field.name)
|
|
282
|
+
},
|
|
283
|
+
field.name
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (field.kind === "string" && isLongTextField(field.name)) {
|
|
287
|
+
return /* @__PURE__ */ jsx(
|
|
288
|
+
Textarea,
|
|
289
|
+
{
|
|
290
|
+
label: field.label,
|
|
291
|
+
required: field.required,
|
|
292
|
+
disabled: executeAction.isPending,
|
|
293
|
+
error: form.errors[field.name],
|
|
294
|
+
autosize: true,
|
|
295
|
+
minRows: 6,
|
|
296
|
+
...form.getInputProps(field.name)
|
|
297
|
+
},
|
|
298
|
+
field.name
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return /* @__PURE__ */ jsx(
|
|
302
|
+
TextInput,
|
|
303
|
+
{
|
|
304
|
+
label: field.label,
|
|
305
|
+
type: field.kind === "date" || field.kind === "dateString" ? "date" : "text",
|
|
306
|
+
required: field.required,
|
|
307
|
+
disabled: executeAction.isPending,
|
|
308
|
+
error: form.errors[field.name],
|
|
309
|
+
...form.getInputProps(field.name)
|
|
310
|
+
},
|
|
311
|
+
field.name
|
|
312
|
+
);
|
|
313
|
+
}),
|
|
314
|
+
/* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
|
|
315
|
+
/* @__PURE__ */ jsx(Button, { variant: "default", onClick: handleClose, disabled: executeAction.isPending, children: "Cancel" }),
|
|
316
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading: executeAction.isPending, children: action.label })
|
|
317
|
+
] })
|
|
318
|
+
] }) }) : /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
319
|
+
/* @__PURE__ */ jsx(Alert, { color: "yellow", variant: "light", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Unsupported action form", children: /* @__PURE__ */ jsx(Text, { size: "sm", children: schemaDescription.reason }) }),
|
|
320
|
+
/* @__PURE__ */ jsx(Button, { variant: "default", onClick: handleClose, disabled: executeAction.isPending, children: "Close" })
|
|
321
|
+
] }) })
|
|
322
|
+
] });
|
|
323
|
+
}
|
|
46
324
|
var KIND_ICONS = {
|
|
47
325
|
call: IconPhone,
|
|
48
326
|
email: IconMail,
|
|
@@ -63,7 +341,7 @@ function formatDueLabel(dueAt) {
|
|
|
63
341
|
return `in ${diffDays}d`;
|
|
64
342
|
}
|
|
65
343
|
function TaskRow({ task, onClick }) {
|
|
66
|
-
const
|
|
344
|
+
const KindIcon = KIND_ICONS[task.kind];
|
|
67
345
|
const dueLabel = formatDueLabel(task.dueAt);
|
|
68
346
|
const isOverdue = task.dueAt !== null && new Date(task.dueAt) < /* @__PURE__ */ new Date();
|
|
69
347
|
return /* @__PURE__ */ jsx(
|
|
@@ -84,7 +362,7 @@ function TaskRow({ task, onClick }) {
|
|
|
84
362
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
85
363
|
},
|
|
86
364
|
children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
|
|
87
|
-
/* @__PURE__ */ jsx(
|
|
365
|
+
/* @__PURE__ */ jsx(KindIcon, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
|
|
88
366
|
/* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1, minWidth: 0 }, children: task.title }),
|
|
89
367
|
dueLabel && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: isOverdue ? "red" : "gray", style: { flexShrink: 0 }, children: dueLabel })
|
|
90
368
|
] })
|
|
@@ -381,11 +659,35 @@ function useRecentCrmActivity(opts) {
|
|
|
381
659
|
error: query.error
|
|
382
660
|
};
|
|
383
661
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
662
|
+
|
|
663
|
+
// src/features/crm/pages/shared.ts
|
|
664
|
+
var DEAL_STAGE_COLORS = {
|
|
665
|
+
interested: "blue",
|
|
666
|
+
proposal: "yellow",
|
|
667
|
+
closing: "orange",
|
|
668
|
+
closed_won: "green",
|
|
669
|
+
closed_lost: "red",
|
|
670
|
+
nurturing: "grape"
|
|
671
|
+
};
|
|
672
|
+
var DEAL_STAGE_OPTIONS = [
|
|
673
|
+
{ value: "interested", label: "Interested" },
|
|
674
|
+
{ value: "proposal", label: "Proposal" },
|
|
675
|
+
{ value: "closing", label: "Closing" },
|
|
676
|
+
{ value: "closed_won", label: "Closed Won" },
|
|
677
|
+
{ value: "closed_lost", label: "Closed Lost" },
|
|
678
|
+
{ value: "nurturing", label: "Nurturing" }
|
|
679
|
+
];
|
|
680
|
+
function formatDealStageLabel(stage) {
|
|
681
|
+
if (!stage) return "Unknown";
|
|
682
|
+
return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
683
|
+
}
|
|
684
|
+
var BOOKING_PAGE_URL = "https://elevasis.io/inbound/book";
|
|
685
|
+
function buildBookingUrl(opts = {}) {
|
|
686
|
+
const params = new URLSearchParams();
|
|
687
|
+
if (opts.dealId) params.set("dealId", opts.dealId);
|
|
688
|
+
if (opts.contactId) params.set("contactId", opts.contactId);
|
|
689
|
+
return params.size > 0 ? `${BOOKING_PAGE_URL}?${params.toString()}` : BOOKING_PAGE_URL;
|
|
690
|
+
}
|
|
389
691
|
var STAGE_LABELS = {
|
|
390
692
|
interested: "Interested",
|
|
391
693
|
proposal: "Proposal",
|
|
@@ -403,143 +705,54 @@ function PipelineFunnelWidget({ onStageClick, getDealValue }) {
|
|
|
403
705
|
return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load pipeline data" }) });
|
|
404
706
|
}
|
|
405
707
|
const totalDeals = data.reduce((sum, s) => sum + s.count, 0);
|
|
406
|
-
const maxCount = Math.max(...data.map((s) => s.count), 1);
|
|
407
|
-
if (totalDeals === 0) {
|
|
408
|
-
return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
|
|
409
|
-
/* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), title: "Pipeline" }),
|
|
410
|
-
/* @__PURE__ */ jsx(Center, { h: 200, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No deals in the pipeline yet" }) })
|
|
411
|
-
] });
|
|
412
|
-
}
|
|
413
708
|
return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
|
|
414
709
|
/* @__PURE__ */ jsx(
|
|
415
710
|
CardHeader,
|
|
416
711
|
{
|
|
417
712
|
icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }),
|
|
418
713
|
title: "Pipeline",
|
|
419
|
-
subtitle: `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
|
|
714
|
+
subtitle: totalDeals === 0 ? "No deals in the pipeline yet" : `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
|
|
420
715
|
}
|
|
421
716
|
),
|
|
422
|
-
/* @__PURE__ */ jsx(
|
|
717
|
+
/* @__PURE__ */ jsx(Group, { gap: "lg", wrap: "nowrap", mt: "md", children: PIPELINE_FUNNEL_ORDER.map((stage) => {
|
|
423
718
|
const summary = data.find((s) => s.stage === stage);
|
|
424
719
|
const isEmpty = summary.count === 0;
|
|
425
|
-
const
|
|
426
|
-
|
|
720
|
+
const stageColor = DEAL_STAGE_COLORS[stage] ?? "gray";
|
|
721
|
+
const accent = `var(--mantine-color-${stageColor}-6)`;
|
|
722
|
+
return /* @__PURE__ */ jsxs(
|
|
427
723
|
Box,
|
|
428
724
|
{
|
|
429
725
|
onClick: () => onStageClick(stage),
|
|
430
726
|
style: {
|
|
727
|
+
flex: 1,
|
|
728
|
+
minWidth: 0,
|
|
431
729
|
cursor: "pointer",
|
|
432
|
-
|
|
433
|
-
padding: "6px 8px",
|
|
434
|
-
transition: `background-color var(--duration-fast) var(--easing)`,
|
|
435
|
-
marginBottom: 4
|
|
730
|
+
padding: "4px 0"
|
|
436
731
|
},
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
{
|
|
450
|
-
style: {
|
|
451
|
-
height: 8,
|
|
452
|
-
width: `${barWidth}%`,
|
|
453
|
-
borderRadius: 4,
|
|
454
|
-
backgroundColor: isEmpty ? "var(--color-border)" : "color-mix(in srgb, var(--color-primary) 70%, transparent)",
|
|
455
|
-
opacity: isEmpty ? 0.4 : 1,
|
|
456
|
-
transition: `width var(--duration-normal) var(--easing)`
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsxs(Group, { gap: 8, wrap: "nowrap", align: "center", mb: 6, children: [
|
|
734
|
+
/* @__PURE__ */ jsx(
|
|
735
|
+
Box,
|
|
736
|
+
{
|
|
737
|
+
style: {
|
|
738
|
+
width: 8,
|
|
739
|
+
height: 8,
|
|
740
|
+
borderRadius: "50%",
|
|
741
|
+
background: accent,
|
|
742
|
+
flexShrink: 0
|
|
743
|
+
}
|
|
457
744
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
745
|
+
),
|
|
746
|
+
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", truncate: true, children: STAGE_LABELS[stage] })
|
|
747
|
+
] }),
|
|
748
|
+
/* @__PURE__ */ jsx(Text, { size: "xl", fw: 600, c: isEmpty ? "dimmed" : void 0, lh: 1, children: summary.count })
|
|
749
|
+
]
|
|
461
750
|
},
|
|
462
751
|
stage
|
|
463
752
|
);
|
|
464
753
|
}) })
|
|
465
754
|
] });
|
|
466
755
|
}
|
|
467
|
-
var MAX_VISIBLE = 5;
|
|
468
|
-
function KindIcon({ kind }) {
|
|
469
|
-
const size = 16;
|
|
470
|
-
switch (kind) {
|
|
471
|
-
case "call":
|
|
472
|
-
return /* @__PURE__ */ jsx(IconPhone, { size });
|
|
473
|
-
case "email":
|
|
474
|
-
return /* @__PURE__ */ jsx(IconMail, { size });
|
|
475
|
-
case "meeting":
|
|
476
|
-
return /* @__PURE__ */ jsx(IconCalendar, { size });
|
|
477
|
-
default:
|
|
478
|
-
return /* @__PURE__ */ jsx(IconCheckbox, { size });
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
function formatDueDate(dueAt) {
|
|
482
|
-
if (!dueAt) return "No due date";
|
|
483
|
-
return new Date(dueAt).toLocaleDateString();
|
|
484
|
-
}
|
|
485
|
-
function TasksDueWidget({ onTaskClick, onSeeAll }) {
|
|
486
|
-
const { data: tasks, isLoading, error } = useDealTasksDue({ window: "today_and_overdue" });
|
|
487
|
-
if (isLoading) {
|
|
488
|
-
return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
|
|
489
|
-
}
|
|
490
|
-
if (error) {
|
|
491
|
-
return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load tasks" }) });
|
|
492
|
-
}
|
|
493
|
-
const totalCount = tasks?.length ?? 0;
|
|
494
|
-
const visibleTasks = (tasks ?? []).slice(0, MAX_VISIBLE);
|
|
495
|
-
const hasMore = totalCount > MAX_VISIBLE;
|
|
496
|
-
const seeAllLink = onSeeAll && hasMore ? /* @__PURE__ */ jsxs(Anchor, { size: "sm", onClick: onSeeAll, style: { cursor: "pointer" }, children: [
|
|
497
|
-
"See all (",
|
|
498
|
-
totalCount,
|
|
499
|
-
")"
|
|
500
|
-
] }) : void 0;
|
|
501
|
-
if (totalCount === 0) {
|
|
502
|
-
return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
|
|
503
|
-
/* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }), title: "Tasks Due" }),
|
|
504
|
-
/* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No tasks due today" }) })
|
|
505
|
-
] });
|
|
506
|
-
}
|
|
507
|
-
return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
|
|
508
|
-
/* @__PURE__ */ jsx(
|
|
509
|
-
CardHeader,
|
|
510
|
-
{
|
|
511
|
-
icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }),
|
|
512
|
-
title: "Tasks Due",
|
|
513
|
-
subtitle: `${totalCount} task${totalCount !== 1 ? "s" : ""}`,
|
|
514
|
-
rightSection: seeAllLink
|
|
515
|
-
}
|
|
516
|
-
),
|
|
517
|
-
/* @__PURE__ */ jsx(Stack, { gap: "xs", children: visibleTasks.map((task) => /* @__PURE__ */ jsx(
|
|
518
|
-
Box,
|
|
519
|
-
{
|
|
520
|
-
onClick: () => onTaskClick(task.dealId),
|
|
521
|
-
style: {
|
|
522
|
-
cursor: "pointer",
|
|
523
|
-
borderRadius: "var(--mantine-radius-sm)",
|
|
524
|
-
padding: "6px 8px",
|
|
525
|
-
transition: `background-color var(--duration-fast) var(--easing)`
|
|
526
|
-
},
|
|
527
|
-
onMouseEnter: (e) => {
|
|
528
|
-
e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
|
|
529
|
-
},
|
|
530
|
-
onMouseLeave: (e) => {
|
|
531
|
-
e.currentTarget.style.backgroundColor = "transparent";
|
|
532
|
-
},
|
|
533
|
-
children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
|
|
534
|
-
/* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(KindIcon, { kind: task.kind }) }),
|
|
535
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", style: { flex: 1, minWidth: 0 }, truncate: true, children: task.title }),
|
|
536
|
-
/* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "gray", style: { flexShrink: 0 }, children: formatDueDate(task.dueAt) })
|
|
537
|
-
] })
|
|
538
|
-
},
|
|
539
|
-
task.id
|
|
540
|
-
)) })
|
|
541
|
-
] });
|
|
542
|
-
}
|
|
543
756
|
function ActivityKindIcon({ kind }) {
|
|
544
757
|
const size = 16;
|
|
545
758
|
switch (kind) {
|
|
@@ -578,41 +791,28 @@ function ActivityFeedWidget({ onDealClick, limit }) {
|
|
|
578
791
|
/* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No recent activity" }) })
|
|
579
792
|
] });
|
|
580
793
|
}
|
|
581
|
-
return /* @__PURE__ */
|
|
794
|
+
return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
582
795
|
/* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
|
|
583
|
-
/* @__PURE__ */
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
{
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
},
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", align: "flex-start", children: [
|
|
602
|
-
/* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0, paddingTop: 2 }, children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }),
|
|
603
|
-
/* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
|
|
604
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, truncate: true, style: { flexShrink: 0, maxWidth: 140 }, children: name }),
|
|
605
|
-
/* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, style: { flex: 1, minWidth: 0 }, children: entry.description })
|
|
606
|
-
] }) }),
|
|
607
|
-
/* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { flexShrink: 0, whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) })
|
|
608
|
-
] })
|
|
609
|
-
},
|
|
610
|
-
entry.id
|
|
611
|
-
);
|
|
612
|
-
}) })
|
|
613
|
-
] });
|
|
796
|
+
/* @__PURE__ */ jsxs(Table, { highlightOnHover: true, children: [
|
|
797
|
+
/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
|
|
798
|
+
/* @__PURE__ */ jsx(Table.Th, { w: 40 }),
|
|
799
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
|
|
800
|
+
/* @__PURE__ */ jsx(Table.Th, { children: "Activity" }),
|
|
801
|
+
/* @__PURE__ */ jsx(Table.Th, { w: 120, children: "When" })
|
|
802
|
+
] }) }),
|
|
803
|
+
/* @__PURE__ */ jsx(Table.Tbody, { children: data.map((entry) => {
|
|
804
|
+
const name = entry.contactName ?? entry.companyName ?? "Unknown";
|
|
805
|
+
return /* @__PURE__ */ jsxs(Table.Tr, { style: { cursor: "pointer" }, onClick: () => onDealClick(entry.dealId), children: [
|
|
806
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { c: "dimmed", component: "span", children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }) }),
|
|
807
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, truncate: true, children: name }) }),
|
|
808
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, children: entry.description }) }),
|
|
809
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) }) })
|
|
810
|
+
] }, entry.id);
|
|
811
|
+
}) })
|
|
812
|
+
] })
|
|
813
|
+
] }) });
|
|
614
814
|
}
|
|
615
|
-
var
|
|
815
|
+
var currencyFormatter = new Intl.NumberFormat("en-US", {
|
|
616
816
|
style: "currency",
|
|
617
817
|
currency: "USD",
|
|
618
818
|
maximumFractionDigits: 0
|
|
@@ -629,7 +829,7 @@ function StatTile({ label, value }) {
|
|
|
629
829
|
function MetricsStrip() {
|
|
630
830
|
const { data } = useCrmQuickMetrics();
|
|
631
831
|
return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 2, sm: 4 }, children: [
|
|
632
|
-
/* @__PURE__ */ jsx(StatTile, { label: "Total Pipeline Value", value:
|
|
832
|
+
/* @__PURE__ */ jsx(StatTile, { label: "Total Pipeline Value", value: currencyFormatter.format(data.totalPipelineValue) }),
|
|
633
833
|
/* @__PURE__ */ jsx(StatTile, { label: "Win Rate", value: formatPercent(data.winRate) }),
|
|
634
834
|
/* @__PURE__ */ jsx(StatTile, { label: "Open Deals", value: String(data.openDeals) }),
|
|
635
835
|
/* @__PURE__ */ jsx(StatTile, { label: "Won This Period", value: String(data.wonDeals) })
|
|
@@ -653,13 +853,8 @@ function CrmOverview({
|
|
|
653
853
|
}
|
|
654
854
|
),
|
|
655
855
|
/* @__PURE__ */ jsx(MetricsStrip, {}),
|
|
656
|
-
/* @__PURE__ */
|
|
657
|
-
|
|
658
|
-
/* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
|
|
659
|
-
/* @__PURE__ */ jsx(TasksDueWidget, { onTaskClick: onDealClick }),
|
|
660
|
-
/* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
|
|
661
|
-
] })
|
|
662
|
-
] })
|
|
856
|
+
/* @__PURE__ */ jsx(PipelineFunnelWidget, { onStageClick, getDealValue }),
|
|
857
|
+
/* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
|
|
663
858
|
] });
|
|
664
859
|
}
|
|
665
860
|
var crmManifest = {
|
|
@@ -669,33 +864,11 @@ var crmManifest = {
|
|
|
669
864
|
icon: IconAddressBook,
|
|
670
865
|
sidebar: CrmSidebar
|
|
671
866
|
};
|
|
672
|
-
|
|
673
|
-
// src/features/crm/pages/shared.ts
|
|
674
|
-
var DEAL_STAGE_COLORS = {
|
|
675
|
-
interested: "blue",
|
|
676
|
-
proposal: "yellow",
|
|
677
|
-
closing: "orange",
|
|
678
|
-
closed_won: "green",
|
|
679
|
-
closed_lost: "red",
|
|
680
|
-
nurturing: "grape"
|
|
681
|
-
};
|
|
682
|
-
var DEAL_STAGE_OPTIONS = [
|
|
683
|
-
{ value: "interested", label: "Interested" },
|
|
684
|
-
{ value: "proposal", label: "Proposal" },
|
|
685
|
-
{ value: "closing", label: "Closing" },
|
|
686
|
-
{ value: "closed_won", label: "Closed Won" },
|
|
687
|
-
{ value: "closed_lost", label: "Closed Lost" },
|
|
688
|
-
{ value: "nurturing", label: "Nurturing" }
|
|
689
|
-
];
|
|
690
|
-
function formatDealStageLabel(stage) {
|
|
691
|
-
if (!stage) return "Unknown";
|
|
692
|
-
return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
693
|
-
}
|
|
694
867
|
var sortAccessors = {
|
|
695
868
|
company: (deal) => deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "",
|
|
696
869
|
contact: (deal) => [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "",
|
|
697
870
|
email: (deal) => deal.contact_email || "",
|
|
698
|
-
stage: (deal) => deal.
|
|
871
|
+
stage: (deal) => deal.stage_key || "",
|
|
699
872
|
updated: (deal) => deal.updated_at || ""
|
|
700
873
|
};
|
|
701
874
|
function DealsListPage() {
|
|
@@ -705,7 +878,12 @@ function DealsListPage() {
|
|
|
705
878
|
const [searchQuery, setSearchQuery] = useState("");
|
|
706
879
|
const [showBatchDelete, setShowBatchDelete] = useState(false);
|
|
707
880
|
const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [stageFilter, searchQuery]);
|
|
708
|
-
const {
|
|
881
|
+
const {
|
|
882
|
+
data: deals,
|
|
883
|
+
total,
|
|
884
|
+
isLoading,
|
|
885
|
+
error
|
|
886
|
+
} = useDeals({
|
|
709
887
|
stage: stageFilter || void 0,
|
|
710
888
|
search: searchQuery || void 0,
|
|
711
889
|
limit: PAGE_SIZE_DEFAULT,
|
|
@@ -800,7 +978,7 @@ function DealsListPage() {
|
|
|
800
978
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: companyName }) }),
|
|
801
979
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: contactName || "-" }) }),
|
|
802
980
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: deal.contact_email || "-" }) }),
|
|
803
|
-
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: DEAL_STAGE_COLORS[deal.
|
|
981
|
+
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: DEAL_STAGE_COLORS[deal.stage_key || ""] || "gray", size: "sm", children: formatDealStageLabel(deal.stage_key) }) }),
|
|
804
982
|
/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(deal.updated_at) }) })
|
|
805
983
|
]
|
|
806
984
|
},
|
|
@@ -852,11 +1030,31 @@ function DealsListPage() {
|
|
|
852
1030
|
)
|
|
853
1031
|
] });
|
|
854
1032
|
}
|
|
1033
|
+
function BookingLinkPanel({ dealId, contactId }) {
|
|
1034
|
+
const url = buildBookingUrl({ dealId, contactId });
|
|
1035
|
+
return /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
1036
|
+
/* @__PURE__ */ jsx(Title, { order: 4, children: "Booking Link" }),
|
|
1037
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
|
|
1038
|
+
/* @__PURE__ */ jsx(
|
|
1039
|
+
Text,
|
|
1040
|
+
{
|
|
1041
|
+
size: "sm",
|
|
1042
|
+
c: "var(--color-primary)",
|
|
1043
|
+
style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
1044
|
+
children: url
|
|
1045
|
+
}
|
|
1046
|
+
),
|
|
1047
|
+
/* @__PURE__ */ jsx(CopyButton, { value: url, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied" : "Copy", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { variant: "light", color: copied ? "teal" : void 0, onClick: copy, children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 16 }) : /* @__PURE__ */ jsx(IconCopy, { size: 16 }) }) }) })
|
|
1048
|
+
] })
|
|
1049
|
+
] }) });
|
|
1050
|
+
}
|
|
855
1051
|
function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
856
1052
|
const navigate = useNavigate();
|
|
857
1053
|
const deleteDeal = useDeleteDeal();
|
|
858
1054
|
const { data: deal, isLoading, error } = useDealDetail(dealId);
|
|
859
1055
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
|
1056
|
+
const crmActions = useCrmActions();
|
|
1057
|
+
const actions = useMemo(() => deal ? deriveActions(deal, crmActions) : [], [deal, crmActions]);
|
|
860
1058
|
useEffect(() => {
|
|
861
1059
|
if (deal) onDealLoaded?.(deal);
|
|
862
1060
|
}, [deal, onDealLoaded]);
|
|
@@ -889,9 +1087,21 @@ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
|
889
1087
|
children: "View Proposal"
|
|
890
1088
|
}
|
|
891
1089
|
),
|
|
1090
|
+
actions.map(
|
|
1091
|
+
(action) => action.payloadSchema ? /* @__PURE__ */ jsx(ActionFormButton, { action, dealId: deal.id }, action.key) : /* @__PURE__ */ jsx(ActionButton, { action, dealId: deal.id }, action.key)
|
|
1092
|
+
),
|
|
892
1093
|
renderActions?.(deal),
|
|
893
1094
|
/* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "red", onClick: () => setDeleteModalOpen(true), children: /* @__PURE__ */ jsx(IconTrash, { size: 16 }) })
|
|
894
|
-
] }) : /* @__PURE__ */ jsx(
|
|
1095
|
+
] }) : /* @__PURE__ */ jsx(
|
|
1096
|
+
Button,
|
|
1097
|
+
{
|
|
1098
|
+
variant: "light",
|
|
1099
|
+
size: "sm",
|
|
1100
|
+
leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
|
|
1101
|
+
onClick: () => navigate({ to: "/crm/deals" }),
|
|
1102
|
+
children: "Deals"
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
895
1105
|
if (isLoading) {
|
|
896
1106
|
return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
|
|
897
1107
|
/* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading deal details...", rightSection: headerActions }) }),
|
|
@@ -916,7 +1126,7 @@ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
|
916
1126
|
PageTitleCaption,
|
|
917
1127
|
{
|
|
918
1128
|
title,
|
|
919
|
-
caption: `${companyName || "Unknown"} - ${formatDealStageLabel(deal.
|
|
1129
|
+
caption: `${companyName || "Unknown"} - ${formatDealStageLabel(deal.stage_key)}`,
|
|
920
1130
|
rightSection: headerActions
|
|
921
1131
|
}
|
|
922
1132
|
) }),
|
|
@@ -931,7 +1141,7 @@ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
|
931
1141
|
/* @__PURE__ */ jsx(Title, { order: 4, children: "Deal" }),
|
|
932
1142
|
/* @__PURE__ */ jsxs(Group, { children: [
|
|
933
1143
|
/* @__PURE__ */ jsx(Text, { fw: 500, children: "Stage:" }),
|
|
934
|
-
/* @__PURE__ */ jsx(Badge, { color: DEAL_STAGE_COLORS[deal.
|
|
1144
|
+
/* @__PURE__ */ jsx(Badge, { color: DEAL_STAGE_COLORS[deal.stage_key || ""] || "gray", children: formatDealStageLabel(deal.stage_key) })
|
|
935
1145
|
] }),
|
|
936
1146
|
/* @__PURE__ */ jsxs(Group, { children: [
|
|
937
1147
|
/* @__PURE__ */ jsx(Text, { fw: 500, children: "Sent:" }),
|
|
@@ -974,6 +1184,7 @@ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
|
974
1184
|
] })
|
|
975
1185
|
] })
|
|
976
1186
|
] }) }),
|
|
1187
|
+
deal.contact_id && /* @__PURE__ */ jsx(BookingLinkPanel, { dealId: deal.id, contactId: deal.contact_id }),
|
|
977
1188
|
/* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
978
1189
|
/* @__PURE__ */ jsx(Title, { order: 4, children: "Company" }),
|
|
979
1190
|
/* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
|
|
@@ -1003,7 +1214,7 @@ function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
|
|
|
1003
1214
|
] })
|
|
1004
1215
|
] })
|
|
1005
1216
|
] }) }),
|
|
1006
|
-
["
|
|
1217
|
+
["closing", "closed_won"].includes(deal.stage_key || "") && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
1007
1218
|
/* @__PURE__ */ jsx(Title, { order: 4, children: "Payment" }),
|
|
1008
1219
|
/* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
|
|
1009
1220
|
/* @__PURE__ */ jsxs(Group, { children: [
|
|
@@ -1188,4 +1399,4 @@ function CompanyDetailPage({ companyId }) {
|
|
|
1188
1399
|
] }) });
|
|
1189
1400
|
}
|
|
1190
1401
|
|
|
1191
|
-
export { ActivityFeedWidget, CRM_ITEMS, CompanyDetailPage, CrmOverview, CrmSidebar, CrmSidebarMiddle, CrmSidebarTop, DEAL_STAGE_COLORS, DEAL_STAGE_OPTIONS, DealDetailPage, DealsListPage, MetricsStrip, MyTasksPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, QuickCreateActions, SAVED_VIEW_PRESETS, SavedViewsPanel,
|
|
1402
|
+
export { ActionButton, ActionFormButton, ActivityFeedWidget, CRM_ITEMS, CompanyDetailPage, CrmOverview, CrmSidebar, CrmSidebarMiddle, CrmSidebarTop, DEAL_STAGE_COLORS, DEAL_STAGE_OPTIONS, DealDetailPage, DealsListPage, MetricsStrip, MyTasksPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, QuickCreateActions, SAVED_VIEW_PRESETS, SavedViewsPanel, crmManifest, formatDealStageLabel, useCrmPipelineSummary, useCrmQuickMetrics, useRecentCrmActivity };
|