@contractspec/example.crm-pipeline 3.7.6 → 3.7.10
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/.turbo/turbo-build.log +45 -42
- package/AGENTS.md +51 -33
- package/CHANGELOG.md +36 -0
- package/README.md +67 -148
- package/dist/browser/docs/crm-pipeline.docblock.js +1 -1
- package/dist/browser/docs/index.js +1 -1
- package/dist/browser/events/contact.event.js +1 -1
- package/dist/browser/events/deal.event.js +1 -1
- package/dist/browser/events/index.js +3 -3
- package/dist/browser/events/task.event.js +1 -1
- package/dist/browser/handlers/crm.handlers.js +13 -2
- package/dist/browser/handlers/index.js +13 -2
- package/dist/browser/index.js +680 -447
- package/dist/browser/ui/CrmDashboard.js +574 -352
- package/dist/browser/ui/CrmDealCard.js +5 -5
- package/dist/browser/ui/CrmPipelineBoard.js +13 -13
- package/dist/browser/ui/hooks/index.js +21 -10
- package/dist/browser/ui/hooks/useDealList.js +20 -9
- package/dist/browser/ui/hooks/useDealMutations.js +1 -1
- package/dist/browser/ui/index.js +683 -450
- package/dist/browser/ui/modals/CreateDealModal.js +12 -12
- package/dist/browser/ui/modals/DealActionsModal.js +21 -21
- package/dist/browser/ui/modals/index.js +33 -33
- package/dist/browser/ui/renderers/index.js +140 -118
- package/dist/browser/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/browser/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/browser/ui/tables/DealListTab.js +390 -0
- package/dist/deal/index.d.ts +2 -2
- package/dist/docs/crm-pipeline.docblock.js +1 -1
- package/dist/docs/index.js +1 -1
- package/dist/events/contact.event.js +1 -1
- package/dist/events/deal.event.js +1 -1
- package/dist/events/index.js +3 -3
- package/dist/events/task.event.js +1 -1
- package/dist/handlers/crm.handlers.d.ts +2 -0
- package/dist/handlers/crm.handlers.js +13 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +13 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +680 -447
- package/dist/node/docs/crm-pipeline.docblock.js +1 -1
- package/dist/node/docs/index.js +1 -1
- package/dist/node/events/contact.event.js +1 -1
- package/dist/node/events/deal.event.js +1 -1
- package/dist/node/events/index.js +3 -3
- package/dist/node/events/task.event.js +1 -1
- package/dist/node/handlers/crm.handlers.js +13 -2
- package/dist/node/handlers/index.js +13 -2
- package/dist/node/index.js +680 -447
- package/dist/node/ui/CrmDashboard.js +574 -352
- package/dist/node/ui/CrmDealCard.js +5 -5
- package/dist/node/ui/CrmPipelineBoard.js +13 -13
- package/dist/node/ui/hooks/index.js +21 -10
- package/dist/node/ui/hooks/useDealList.js +20 -9
- package/dist/node/ui/hooks/useDealMutations.js +1 -1
- package/dist/node/ui/index.js +683 -450
- package/dist/node/ui/modals/CreateDealModal.js +12 -12
- package/dist/node/ui/modals/DealActionsModal.js +21 -21
- package/dist/node/ui/modals/index.js +33 -33
- package/dist/node/ui/renderers/index.js +140 -118
- package/dist/node/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/node/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/node/ui/tables/DealListTab.js +390 -0
- package/dist/operations/index.d.ts +1 -1
- package/dist/ui/CrmDashboard.js +574 -352
- package/dist/ui/CrmDealCard.js +5 -5
- package/dist/ui/CrmPipelineBoard.js +13 -13
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +21 -10
- package/dist/ui/hooks/useDealList.d.ts +8 -2
- package/dist/ui/hooks/useDealList.js +20 -9
- package/dist/ui/hooks/useDealMutations.d.ts +9 -0
- package/dist/ui/hooks/useDealMutations.js +1 -1
- package/dist/ui/index.d.ts +3 -3
- package/dist/ui/index.js +683 -450
- package/dist/ui/modals/CreateDealModal.js +12 -12
- package/dist/ui/modals/DealActionsModal.js +21 -21
- package/dist/ui/modals/index.js +33 -33
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +140 -118
- package/dist/ui/renderers/pipeline.markdown.js +13 -2
- package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
- package/dist/ui/renderers/pipeline.renderer.js +108 -97
- package/dist/ui/tables/DealListTab.d.ts +20 -0
- package/dist/ui/tables/DealListTab.js +391 -0
- package/dist/ui/tables/DealListTab.smoke.test.d.ts +1 -0
- package/package.json +29 -14
- package/src/crm-pipeline.feature.ts +86 -86
- package/src/deal/deal.enum.ts +8 -8
- package/src/deal/deal.operation.ts +255 -255
- package/src/deal/deal.schema.ts +92 -92
- package/src/deal/deal.test-spec.ts +48 -48
- package/src/deal/index.ts +17 -19
- package/src/docs/crm-pipeline.docblock.ts +44 -44
- package/src/entities/company.entity.ts +52 -52
- package/src/entities/contact.entity.ts +67 -67
- package/src/entities/deal.entity.ts +134 -134
- package/src/entities/index.ts +27 -27
- package/src/entities/task.entity.ts +105 -105
- package/src/events/contact.event.ts +22 -22
- package/src/events/deal.event.ts +77 -77
- package/src/events/task.event.ts +19 -19
- package/src/example.ts +32 -32
- package/src/handlers/crm.handlers.ts +375 -357
- package/src/handlers/deal.handlers.ts +179 -179
- package/src/handlers/index.ts +18 -19
- package/src/handlers/mock-data.ts +167 -167
- package/src/index.ts +11 -11
- package/src/operations/index.ts +16 -16
- package/src/presentations/dashboard.presentation.ts +45 -45
- package/src/presentations/pipeline.presentation.ts +90 -90
- package/src/seeders/index.ts +26 -26
- package/src/shared/overlay-types.ts +23 -23
- package/src/ui/CrmDashboard.tsx +210 -279
- package/src/ui/CrmDealCard.tsx +64 -64
- package/src/ui/CrmPipelineBoard.tsx +105 -105
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useDealList.ts +113 -85
- package/src/ui/hooks/useDealMutations.ts +151 -150
- package/src/ui/index.ts +5 -10
- package/src/ui/modals/CreateDealModal.tsx +217 -217
- package/src/ui/modals/DealActionsModal.tsx +390 -390
- package/src/ui/overlays/demo-overlays.ts +43 -43
- package/src/ui/renderers/index.ts +4 -3
- package/src/ui/renderers/pipeline.markdown.ts +165 -165
- package/src/ui/renderers/pipeline.renderer.tsx +17 -16
- package/src/ui/tables/DealListTab.smoke.test.tsx +149 -0
- package/src/ui/tables/DealListTab.tsx +276 -0
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
package/dist/browser/ui/index.js
CHANGED
|
@@ -22,6 +22,13 @@ function rowToDeal(row) {
|
|
|
22
22
|
updatedAt: new Date(row.updatedAt)
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
var DEAL_SORT_COLUMNS = {
|
|
26
|
+
name: "name",
|
|
27
|
+
value: "value",
|
|
28
|
+
status: "status",
|
|
29
|
+
expectedCloseDate: "expectedCloseDate",
|
|
30
|
+
updatedAt: "updatedAt"
|
|
31
|
+
};
|
|
25
32
|
function createCrmHandlers(db) {
|
|
26
33
|
async function listDeals(input) {
|
|
27
34
|
const {
|
|
@@ -32,7 +39,9 @@ function createCrmHandlers(db) {
|
|
|
32
39
|
ownerId,
|
|
33
40
|
search,
|
|
34
41
|
limit = 20,
|
|
35
|
-
offset = 0
|
|
42
|
+
offset = 0,
|
|
43
|
+
sortBy = "value",
|
|
44
|
+
sortDirection = "desc"
|
|
36
45
|
} = input;
|
|
37
46
|
let whereClause = "WHERE projectId = ?";
|
|
38
47
|
const params = [projectId];
|
|
@@ -60,7 +69,9 @@ function createCrmHandlers(db) {
|
|
|
60
69
|
const total = countResult[0]?.count ?? 0;
|
|
61
70
|
const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
|
|
62
71
|
const totalValue = valueResult[0]?.total ?? 0;
|
|
63
|
-
const
|
|
72
|
+
const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
|
|
73
|
+
const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
|
|
74
|
+
const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
|
|
64
75
|
return {
|
|
65
76
|
deals: dealRows.map(rowToDeal),
|
|
66
77
|
total,
|
|
@@ -440,186 +451,6 @@ async function mockGetDealsByStageHandler(input) {
|
|
|
440
451
|
async function mockGetPipelineStagesHandler(input) {
|
|
441
452
|
return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
|
|
442
453
|
}
|
|
443
|
-
// src/ui/hooks/useDealList.ts
|
|
444
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
445
|
-
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
446
|
-
"use client";
|
|
447
|
-
function useDealList(options = {}) {
|
|
448
|
-
const { handlers, projectId } = useTemplateRuntime();
|
|
449
|
-
const { crm: crm2 } = handlers;
|
|
450
|
-
const [data, setData] = useState(null);
|
|
451
|
-
const [dealsByStage, setDealsByStage] = useState({});
|
|
452
|
-
const [stages, setStages] = useState([]);
|
|
453
|
-
const [loading, setLoading] = useState(true);
|
|
454
|
-
const [error, setError] = useState(null);
|
|
455
|
-
const [page, setPage] = useState(1);
|
|
456
|
-
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
457
|
-
const fetchData = useCallback(async () => {
|
|
458
|
-
setLoading(true);
|
|
459
|
-
setError(null);
|
|
460
|
-
try {
|
|
461
|
-
const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
|
|
462
|
-
crm2.listDeals({
|
|
463
|
-
projectId,
|
|
464
|
-
pipelineId,
|
|
465
|
-
stageId: options.stageId,
|
|
466
|
-
status: options.status === "all" ? undefined : options.status,
|
|
467
|
-
search: options.search,
|
|
468
|
-
limit: options.limit ?? 50,
|
|
469
|
-
offset: (page - 1) * (options.limit ?? 50)
|
|
470
|
-
}),
|
|
471
|
-
crm2.getDealsByStage({ projectId, pipelineId }),
|
|
472
|
-
crm2.getPipelineStages({ pipelineId })
|
|
473
|
-
]);
|
|
474
|
-
setData(dealsResult);
|
|
475
|
-
setDealsByStage(stageDealsResult);
|
|
476
|
-
setStages(stagesResult);
|
|
477
|
-
} catch (err) {
|
|
478
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
479
|
-
} finally {
|
|
480
|
-
setLoading(false);
|
|
481
|
-
}
|
|
482
|
-
}, [
|
|
483
|
-
crm2,
|
|
484
|
-
projectId,
|
|
485
|
-
pipelineId,
|
|
486
|
-
options.stageId,
|
|
487
|
-
options.status,
|
|
488
|
-
options.search,
|
|
489
|
-
options.limit,
|
|
490
|
-
page
|
|
491
|
-
]);
|
|
492
|
-
useEffect(() => {
|
|
493
|
-
fetchData();
|
|
494
|
-
}, [fetchData]);
|
|
495
|
-
const stats = useMemo(() => {
|
|
496
|
-
if (!data)
|
|
497
|
-
return null;
|
|
498
|
-
const open = data.deals.filter((d) => d.status === "OPEN");
|
|
499
|
-
const won = data.deals.filter((d) => d.status === "WON");
|
|
500
|
-
const lost = data.deals.filter((d) => d.status === "LOST");
|
|
501
|
-
return {
|
|
502
|
-
total: data.total,
|
|
503
|
-
totalValue: data.totalValue,
|
|
504
|
-
openCount: open.length,
|
|
505
|
-
openValue: open.reduce((sum, d) => sum + d.value, 0),
|
|
506
|
-
wonCount: won.length,
|
|
507
|
-
wonValue: won.reduce((sum, d) => sum + d.value, 0),
|
|
508
|
-
lostCount: lost.length
|
|
509
|
-
};
|
|
510
|
-
}, [data]);
|
|
511
|
-
return {
|
|
512
|
-
data,
|
|
513
|
-
dealsByStage,
|
|
514
|
-
stages,
|
|
515
|
-
loading,
|
|
516
|
-
error,
|
|
517
|
-
stats,
|
|
518
|
-
page,
|
|
519
|
-
refetch: fetchData,
|
|
520
|
-
nextPage: () => setPage((p) => p + 1),
|
|
521
|
-
prevPage: () => page > 1 && setPage((p) => p - 1)
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// src/ui/hooks/useDealMutations.ts
|
|
526
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
527
|
-
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
528
|
-
function useDealMutations(options = {}) {
|
|
529
|
-
const { handlers, projectId } = useTemplateRuntime2();
|
|
530
|
-
const { crm: crm2 } = handlers;
|
|
531
|
-
const [createState, setCreateState] = useState2({
|
|
532
|
-
loading: false,
|
|
533
|
-
error: null,
|
|
534
|
-
data: null
|
|
535
|
-
});
|
|
536
|
-
const [moveState, setMoveState] = useState2({
|
|
537
|
-
loading: false,
|
|
538
|
-
error: null,
|
|
539
|
-
data: null
|
|
540
|
-
});
|
|
541
|
-
const [winState, setWinState] = useState2({
|
|
542
|
-
loading: false,
|
|
543
|
-
error: null,
|
|
544
|
-
data: null
|
|
545
|
-
});
|
|
546
|
-
const [loseState, setLoseState] = useState2({
|
|
547
|
-
loading: false,
|
|
548
|
-
error: null,
|
|
549
|
-
data: null
|
|
550
|
-
});
|
|
551
|
-
const createDeal = useCallback2(async (input) => {
|
|
552
|
-
setCreateState({ loading: true, error: null, data: null });
|
|
553
|
-
try {
|
|
554
|
-
const result = await crm2.createDeal(input, {
|
|
555
|
-
projectId,
|
|
556
|
-
ownerId: "user-1"
|
|
557
|
-
});
|
|
558
|
-
setCreateState({ loading: false, error: null, data: result });
|
|
559
|
-
options.onSuccess?.();
|
|
560
|
-
return result;
|
|
561
|
-
} catch (err) {
|
|
562
|
-
const error = err instanceof Error ? err : new Error("Failed to create deal");
|
|
563
|
-
setCreateState({ loading: false, error, data: null });
|
|
564
|
-
options.onError?.(error);
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
}, [crm2, projectId, options]);
|
|
568
|
-
const moveDeal = useCallback2(async (input) => {
|
|
569
|
-
setMoveState({ loading: true, error: null, data: null });
|
|
570
|
-
try {
|
|
571
|
-
const result = await crm2.moveDeal(input);
|
|
572
|
-
setMoveState({ loading: false, error: null, data: result });
|
|
573
|
-
options.onSuccess?.();
|
|
574
|
-
return result;
|
|
575
|
-
} catch (err) {
|
|
576
|
-
const error = err instanceof Error ? err : new Error("Failed to move deal");
|
|
577
|
-
setMoveState({ loading: false, error, data: null });
|
|
578
|
-
options.onError?.(error);
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
}, [crm2, options]);
|
|
582
|
-
const winDeal = useCallback2(async (input) => {
|
|
583
|
-
setWinState({ loading: true, error: null, data: null });
|
|
584
|
-
try {
|
|
585
|
-
const result = await crm2.winDeal(input);
|
|
586
|
-
setWinState({ loading: false, error: null, data: result });
|
|
587
|
-
options.onSuccess?.();
|
|
588
|
-
return result;
|
|
589
|
-
} catch (err) {
|
|
590
|
-
const error = err instanceof Error ? err : new Error("Failed to mark deal as won");
|
|
591
|
-
setWinState({ loading: false, error, data: null });
|
|
592
|
-
options.onError?.(error);
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
}, [crm2, options]);
|
|
596
|
-
const loseDeal = useCallback2(async (input) => {
|
|
597
|
-
setLoseState({ loading: true, error: null, data: null });
|
|
598
|
-
try {
|
|
599
|
-
const result = await crm2.loseDeal(input);
|
|
600
|
-
setLoseState({ loading: false, error: null, data: result });
|
|
601
|
-
options.onSuccess?.();
|
|
602
|
-
return result;
|
|
603
|
-
} catch (err) {
|
|
604
|
-
const error = err instanceof Error ? err : new Error("Failed to mark deal as lost");
|
|
605
|
-
setLoseState({ loading: false, error, data: null });
|
|
606
|
-
options.onError?.(error);
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
}, [crm2, options]);
|
|
610
|
-
return {
|
|
611
|
-
createDeal,
|
|
612
|
-
moveDeal,
|
|
613
|
-
winDeal,
|
|
614
|
-
loseDeal,
|
|
615
|
-
createState,
|
|
616
|
-
moveState,
|
|
617
|
-
winState,
|
|
618
|
-
loseState,
|
|
619
|
-
isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
|
|
623
454
|
// src/ui/CrmDealCard.tsx
|
|
624
455
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
625
456
|
"use client";
|
|
@@ -635,7 +466,7 @@ function CrmDealCard({ deal, onClick }) {
|
|
|
635
466
|
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
636
467
|
return /* @__PURE__ */ jsxDEV("div", {
|
|
637
468
|
onClick,
|
|
638
|
-
className: "
|
|
469
|
+
className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
639
470
|
role: "button",
|
|
640
471
|
tabIndex: 0,
|
|
641
472
|
onKeyDown: (e) => {
|
|
@@ -644,22 +475,22 @@ function CrmDealCard({ deal, onClick }) {
|
|
|
644
475
|
},
|
|
645
476
|
children: [
|
|
646
477
|
/* @__PURE__ */ jsxDEV("h4", {
|
|
647
|
-
className: "leading-snug
|
|
478
|
+
className: "font-medium leading-snug",
|
|
648
479
|
children: deal.name
|
|
649
480
|
}, undefined, false, undefined, this),
|
|
650
481
|
/* @__PURE__ */ jsxDEV("div", {
|
|
651
|
-
className: "
|
|
482
|
+
className: "mt-2 font-semibold text-lg text-primary",
|
|
652
483
|
children: formatCurrency(deal.value, deal.currency)
|
|
653
484
|
}, undefined, false, undefined, this),
|
|
654
485
|
/* @__PURE__ */ jsxDEV("div", {
|
|
655
|
-
className: "
|
|
486
|
+
className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
|
|
656
487
|
children: [
|
|
657
488
|
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
658
489
|
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
659
490
|
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
660
491
|
}, undefined, false, undefined, this),
|
|
661
492
|
/* @__PURE__ */ jsxDEV("span", {
|
|
662
|
-
className: `rounded px-1.5 py-0.5 text-xs
|
|
493
|
+
className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
663
494
|
children: deal.status
|
|
664
495
|
}, undefined, false, undefined, this)
|
|
665
496
|
]
|
|
@@ -669,7 +500,7 @@ function CrmDealCard({ deal, onClick }) {
|
|
|
669
500
|
}
|
|
670
501
|
|
|
671
502
|
// src/ui/CrmPipelineBoard.tsx
|
|
672
|
-
import { useState
|
|
503
|
+
import { useState } from "react";
|
|
673
504
|
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
674
505
|
"use client";
|
|
675
506
|
function formatCurrency2(value) {
|
|
@@ -685,7 +516,7 @@ function CrmPipelineBoard({
|
|
|
685
516
|
onDealClick,
|
|
686
517
|
onDealMove
|
|
687
518
|
}) {
|
|
688
|
-
const [quickMoveOpen, setQuickMoveOpen] =
|
|
519
|
+
const [quickMoveOpen, setQuickMoveOpen] = useState(null);
|
|
689
520
|
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
690
521
|
const handleQuickMove = (dealId, toStageId) => {
|
|
691
522
|
onDealMove?.(dealId, toStageId);
|
|
@@ -697,10 +528,10 @@ function CrmPipelineBoard({
|
|
|
697
528
|
const deals = dealsByStage[stage.id] ?? [];
|
|
698
529
|
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
699
530
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
700
|
-
className: "
|
|
531
|
+
className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
|
|
701
532
|
children: [
|
|
702
533
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
703
|
-
className: "
|
|
534
|
+
className: "flex items-center justify-between border-border border-b px-3 py-2",
|
|
704
535
|
children: [
|
|
705
536
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
706
537
|
children: [
|
|
@@ -719,7 +550,7 @@ function CrmPipelineBoard({
|
|
|
719
550
|
]
|
|
720
551
|
}, undefined, true, undefined, this),
|
|
721
552
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
722
|
-
className: "
|
|
553
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
|
|
723
554
|
children: deals.length
|
|
724
555
|
}, undefined, false, undefined, this)
|
|
725
556
|
]
|
|
@@ -727,7 +558,7 @@ function CrmPipelineBoard({
|
|
|
727
558
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
728
559
|
className: "flex flex-1 flex-col gap-2 p-2",
|
|
729
560
|
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
730
|
-
className: "
|
|
561
|
+
className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
|
|
731
562
|
children: "No deals"
|
|
732
563
|
}, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
|
|
733
564
|
className: "group relative",
|
|
@@ -745,15 +576,15 @@ function CrmPipelineBoard({
|
|
|
745
576
|
e.stopPropagation();
|
|
746
577
|
setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
|
|
747
578
|
},
|
|
748
|
-
className: "
|
|
579
|
+
className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
|
|
749
580
|
title: "Quick move",
|
|
750
581
|
children: "➡️"
|
|
751
582
|
}, undefined, false, undefined, this),
|
|
752
583
|
quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
753
|
-
className: "
|
|
584
|
+
className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
|
|
754
585
|
children: [
|
|
755
586
|
/* @__PURE__ */ jsxDEV2("p", {
|
|
756
|
-
className: "
|
|
587
|
+
className: "px-3 py-1 font-medium text-muted-foreground text-xs",
|
|
757
588
|
children: "Move to:"
|
|
758
589
|
}, undefined, false, undefined, this),
|
|
759
590
|
sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
@@ -762,7 +593,7 @@ function CrmPipelineBoard({
|
|
|
762
593
|
e.stopPropagation();
|
|
763
594
|
handleQuickMove(deal.id, s.id);
|
|
764
595
|
},
|
|
765
|
-
className: "
|
|
596
|
+
className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
|
|
766
597
|
children: s.name
|
|
767
598
|
}, s.id, false, undefined, this))
|
|
768
599
|
]
|
|
@@ -778,9 +609,200 @@ function CrmPipelineBoard({
|
|
|
778
609
|
}, undefined, false, undefined, this);
|
|
779
610
|
}
|
|
780
611
|
|
|
612
|
+
// src/ui/hooks/useDealList.ts
|
|
613
|
+
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
614
|
+
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
615
|
+
"use client";
|
|
616
|
+
function useDealList(options = {}) {
|
|
617
|
+
const { handlers, projectId } = useTemplateRuntime();
|
|
618
|
+
const { crm: crm2 } = handlers;
|
|
619
|
+
const [data, setData] = useState2(null);
|
|
620
|
+
const [dealsByStage, setDealsByStage] = useState2({});
|
|
621
|
+
const [stages, setStages] = useState2([]);
|
|
622
|
+
const [loading, setLoading] = useState2(true);
|
|
623
|
+
const [error, setError] = useState2(null);
|
|
624
|
+
const [internalPage, setInternalPage] = useState2(0);
|
|
625
|
+
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
626
|
+
const pageIndex = options.pageIndex ?? internalPage;
|
|
627
|
+
const pageSize = options.pageSize ?? options.limit ?? 50;
|
|
628
|
+
const [sort] = options.sorting ?? [];
|
|
629
|
+
const sortBy = sort?.id;
|
|
630
|
+
const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
|
|
631
|
+
const fetchData = useCallback(async () => {
|
|
632
|
+
setLoading(true);
|
|
633
|
+
setError(null);
|
|
634
|
+
try {
|
|
635
|
+
const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
|
|
636
|
+
crm2.listDeals({
|
|
637
|
+
projectId,
|
|
638
|
+
pipelineId,
|
|
639
|
+
stageId: options.stageId,
|
|
640
|
+
status: options.status === "all" ? undefined : options.status,
|
|
641
|
+
search: options.search,
|
|
642
|
+
limit: pageSize,
|
|
643
|
+
offset: pageIndex * pageSize,
|
|
644
|
+
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
645
|
+
sortDirection
|
|
646
|
+
}),
|
|
647
|
+
crm2.getDealsByStage({ projectId, pipelineId }),
|
|
648
|
+
crm2.getPipelineStages({ pipelineId })
|
|
649
|
+
]);
|
|
650
|
+
setData(dealsResult);
|
|
651
|
+
setDealsByStage(stageDealsResult);
|
|
652
|
+
setStages(stagesResult);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
655
|
+
} finally {
|
|
656
|
+
setLoading(false);
|
|
657
|
+
}
|
|
658
|
+
}, [
|
|
659
|
+
crm2,
|
|
660
|
+
projectId,
|
|
661
|
+
pipelineId,
|
|
662
|
+
options.stageId,
|
|
663
|
+
options.status,
|
|
664
|
+
options.search,
|
|
665
|
+
pageIndex,
|
|
666
|
+
pageSize,
|
|
667
|
+
sortBy,
|
|
668
|
+
sortDirection
|
|
669
|
+
]);
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
fetchData();
|
|
672
|
+
}, [fetchData]);
|
|
673
|
+
const stats = useMemo(() => {
|
|
674
|
+
if (!data)
|
|
675
|
+
return null;
|
|
676
|
+
const open = data.deals.filter((d) => d.status === "OPEN");
|
|
677
|
+
const won = data.deals.filter((d) => d.status === "WON");
|
|
678
|
+
const lost = data.deals.filter((d) => d.status === "LOST");
|
|
679
|
+
return {
|
|
680
|
+
total: data.total,
|
|
681
|
+
totalValue: data.totalValue,
|
|
682
|
+
openCount: open.length,
|
|
683
|
+
openValue: open.reduce((sum, d) => sum + d.value, 0),
|
|
684
|
+
wonCount: won.length,
|
|
685
|
+
wonValue: won.reduce((sum, d) => sum + d.value, 0),
|
|
686
|
+
lostCount: lost.length
|
|
687
|
+
};
|
|
688
|
+
}, [data]);
|
|
689
|
+
return {
|
|
690
|
+
data,
|
|
691
|
+
dealsByStage,
|
|
692
|
+
stages,
|
|
693
|
+
loading,
|
|
694
|
+
error,
|
|
695
|
+
stats,
|
|
696
|
+
page: pageIndex + 1,
|
|
697
|
+
pageIndex,
|
|
698
|
+
pageSize,
|
|
699
|
+
refetch: fetchData,
|
|
700
|
+
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
701
|
+
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/ui/hooks/useDealMutations.ts
|
|
706
|
+
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
707
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
708
|
+
function useDealMutations(options = {}) {
|
|
709
|
+
const { handlers, projectId } = useTemplateRuntime2();
|
|
710
|
+
const { crm: crm2 } = handlers;
|
|
711
|
+
const [createState, setCreateState] = useState3({
|
|
712
|
+
loading: false,
|
|
713
|
+
error: null,
|
|
714
|
+
data: null
|
|
715
|
+
});
|
|
716
|
+
const [moveState, setMoveState] = useState3({
|
|
717
|
+
loading: false,
|
|
718
|
+
error: null,
|
|
719
|
+
data: null
|
|
720
|
+
});
|
|
721
|
+
const [winState, setWinState] = useState3({
|
|
722
|
+
loading: false,
|
|
723
|
+
error: null,
|
|
724
|
+
data: null
|
|
725
|
+
});
|
|
726
|
+
const [loseState, setLoseState] = useState3({
|
|
727
|
+
loading: false,
|
|
728
|
+
error: null,
|
|
729
|
+
data: null
|
|
730
|
+
});
|
|
731
|
+
const createDeal = useCallback2(async (input) => {
|
|
732
|
+
setCreateState({ loading: true, error: null, data: null });
|
|
733
|
+
try {
|
|
734
|
+
const result = await crm2.createDeal(input, {
|
|
735
|
+
projectId,
|
|
736
|
+
ownerId: "user-1"
|
|
737
|
+
});
|
|
738
|
+
setCreateState({ loading: false, error: null, data: result });
|
|
739
|
+
options.onSuccess?.();
|
|
740
|
+
return result;
|
|
741
|
+
} catch (err) {
|
|
742
|
+
const error = err instanceof Error ? err : new Error("Failed to create deal");
|
|
743
|
+
setCreateState({ loading: false, error, data: null });
|
|
744
|
+
options.onError?.(error);
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
}, [crm2, projectId, options]);
|
|
748
|
+
const moveDeal = useCallback2(async (input) => {
|
|
749
|
+
setMoveState({ loading: true, error: null, data: null });
|
|
750
|
+
try {
|
|
751
|
+
const result = await crm2.moveDeal(input);
|
|
752
|
+
setMoveState({ loading: false, error: null, data: result });
|
|
753
|
+
options.onSuccess?.();
|
|
754
|
+
return result;
|
|
755
|
+
} catch (err) {
|
|
756
|
+
const error = err instanceof Error ? err : new Error("Failed to move deal");
|
|
757
|
+
setMoveState({ loading: false, error, data: null });
|
|
758
|
+
options.onError?.(error);
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
}, [crm2, options]);
|
|
762
|
+
const winDeal = useCallback2(async (input) => {
|
|
763
|
+
setWinState({ loading: true, error: null, data: null });
|
|
764
|
+
try {
|
|
765
|
+
const result = await crm2.winDeal(input);
|
|
766
|
+
setWinState({ loading: false, error: null, data: result });
|
|
767
|
+
options.onSuccess?.();
|
|
768
|
+
return result;
|
|
769
|
+
} catch (err) {
|
|
770
|
+
const error = err instanceof Error ? err : new Error("Failed to mark deal as won");
|
|
771
|
+
setWinState({ loading: false, error, data: null });
|
|
772
|
+
options.onError?.(error);
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}, [crm2, options]);
|
|
776
|
+
const loseDeal = useCallback2(async (input) => {
|
|
777
|
+
setLoseState({ loading: true, error: null, data: null });
|
|
778
|
+
try {
|
|
779
|
+
const result = await crm2.loseDeal(input);
|
|
780
|
+
setLoseState({ loading: false, error: null, data: result });
|
|
781
|
+
options.onSuccess?.();
|
|
782
|
+
return result;
|
|
783
|
+
} catch (err) {
|
|
784
|
+
const error = err instanceof Error ? err : new Error("Failed to mark deal as lost");
|
|
785
|
+
setLoseState({ loading: false, error, data: null });
|
|
786
|
+
options.onError?.(error);
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}, [crm2, options]);
|
|
790
|
+
return {
|
|
791
|
+
createDeal,
|
|
792
|
+
moveDeal,
|
|
793
|
+
winDeal,
|
|
794
|
+
loseDeal,
|
|
795
|
+
createState,
|
|
796
|
+
moveState,
|
|
797
|
+
winState,
|
|
798
|
+
loseState,
|
|
799
|
+
isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
781
803
|
// src/ui/modals/CreateDealModal.tsx
|
|
782
|
-
import { useState as useState4 } from "react";
|
|
783
804
|
import { Button, Input } from "@contractspec/lib.design-system";
|
|
805
|
+
import { useState as useState4 } from "react";
|
|
784
806
|
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
785
807
|
"use client";
|
|
786
808
|
var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
|
|
@@ -839,7 +861,7 @@ function CreateDealModal({
|
|
|
839
861
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
840
862
|
children: [
|
|
841
863
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
842
|
-
className: "bg-background/80
|
|
864
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
843
865
|
onClick: onClose,
|
|
844
866
|
role: "button",
|
|
845
867
|
tabIndex: 0,
|
|
@@ -850,10 +872,10 @@ function CreateDealModal({
|
|
|
850
872
|
"aria-label": "Close modal"
|
|
851
873
|
}, undefined, false, undefined, this),
|
|
852
874
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
853
|
-
className: "
|
|
875
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
854
876
|
children: [
|
|
855
877
|
/* @__PURE__ */ jsxDEV3("h2", {
|
|
856
|
-
className: "mb-4 text-xl
|
|
878
|
+
className: "mb-4 font-semibold text-xl",
|
|
857
879
|
children: "Create New Deal"
|
|
858
880
|
}, undefined, false, undefined, this),
|
|
859
881
|
/* @__PURE__ */ jsxDEV3("form", {
|
|
@@ -864,7 +886,7 @@ function CreateDealModal({
|
|
|
864
886
|
children: [
|
|
865
887
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
866
888
|
htmlFor: "deal-name",
|
|
867
|
-
className: "
|
|
889
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
868
890
|
children: "Deal Name *"
|
|
869
891
|
}, undefined, false, undefined, this),
|
|
870
892
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -884,7 +906,7 @@ function CreateDealModal({
|
|
|
884
906
|
children: [
|
|
885
907
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
886
908
|
htmlFor: "deal-value",
|
|
887
|
-
className: "
|
|
909
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
888
910
|
children: "Value *"
|
|
889
911
|
}, undefined, false, undefined, this),
|
|
890
912
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -904,7 +926,7 @@ function CreateDealModal({
|
|
|
904
926
|
children: [
|
|
905
927
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
906
928
|
htmlFor: "deal-currency",
|
|
907
|
-
className: "
|
|
929
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
908
930
|
children: "Currency"
|
|
909
931
|
}, undefined, false, undefined, this),
|
|
910
932
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -912,7 +934,7 @@ function CreateDealModal({
|
|
|
912
934
|
value: currency,
|
|
913
935
|
onChange: (e) => setCurrency(e.target.value),
|
|
914
936
|
disabled: isLoading,
|
|
915
|
-
className: "
|
|
937
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
916
938
|
children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
|
|
917
939
|
value: c,
|
|
918
940
|
children: c
|
|
@@ -926,7 +948,7 @@ function CreateDealModal({
|
|
|
926
948
|
children: [
|
|
927
949
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
928
950
|
htmlFor: "deal-stage",
|
|
929
|
-
className: "
|
|
951
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
930
952
|
children: "Pipeline Stage *"
|
|
931
953
|
}, undefined, false, undefined, this),
|
|
932
954
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -934,7 +956,7 @@ function CreateDealModal({
|
|
|
934
956
|
value: stageId,
|
|
935
957
|
onChange: (e) => setStageId(e.target.value),
|
|
936
958
|
disabled: isLoading,
|
|
937
|
-
className: "
|
|
959
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
938
960
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
|
|
939
961
|
value: stage.id,
|
|
940
962
|
children: stage.name
|
|
@@ -946,7 +968,7 @@ function CreateDealModal({
|
|
|
946
968
|
children: [
|
|
947
969
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
948
970
|
htmlFor: "deal-close-date",
|
|
949
|
-
className: "
|
|
971
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
950
972
|
children: "Expected Close Date"
|
|
951
973
|
}, undefined, false, undefined, this),
|
|
952
974
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -959,7 +981,7 @@ function CreateDealModal({
|
|
|
959
981
|
]
|
|
960
982
|
}, undefined, true, undefined, this),
|
|
961
983
|
error && /* @__PURE__ */ jsxDEV3("div", {
|
|
962
|
-
className: "bg-destructive/10
|
|
984
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
963
985
|
children: error
|
|
964
986
|
}, undefined, false, undefined, this),
|
|
965
987
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
@@ -988,8 +1010,8 @@ function CreateDealModal({
|
|
|
988
1010
|
}
|
|
989
1011
|
|
|
990
1012
|
// src/ui/modals/DealActionsModal.tsx
|
|
991
|
-
import { useState as useState5 } from "react";
|
|
992
1013
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
1014
|
+
import { useState as useState5 } from "react";
|
|
993
1015
|
import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
|
|
994
1016
|
"use client";
|
|
995
1017
|
function formatCurrency3(value, currency) {
|
|
@@ -1090,7 +1112,7 @@ function DealActionsModal({
|
|
|
1090
1112
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
1091
1113
|
children: [
|
|
1092
1114
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
1093
|
-
className: "bg-background/80
|
|
1115
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
1094
1116
|
onClick: handleClose,
|
|
1095
1117
|
role: "button",
|
|
1096
1118
|
tabIndex: 0,
|
|
@@ -1101,21 +1123,21 @@ function DealActionsModal({
|
|
|
1101
1123
|
"aria-label": "Close modal"
|
|
1102
1124
|
}, undefined, false, undefined, this),
|
|
1103
1125
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
1104
|
-
className: "
|
|
1126
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
1105
1127
|
children: [
|
|
1106
1128
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
1107
|
-
className: "
|
|
1129
|
+
className: "mb-4 border-border border-b pb-4",
|
|
1108
1130
|
children: [
|
|
1109
1131
|
/* @__PURE__ */ jsxDEV4("h2", {
|
|
1110
|
-
className: "text-xl
|
|
1132
|
+
className: "font-semibold text-xl",
|
|
1111
1133
|
children: deal.name
|
|
1112
1134
|
}, undefined, false, undefined, this),
|
|
1113
1135
|
/* @__PURE__ */ jsxDEV4("p", {
|
|
1114
|
-
className: "
|
|
1136
|
+
className: "font-medium text-lg text-primary",
|
|
1115
1137
|
children: formatCurrency3(deal.value, deal.currency)
|
|
1116
1138
|
}, undefined, false, undefined, this),
|
|
1117
1139
|
/* @__PURE__ */ jsxDEV4("span", {
|
|
1118
|
-
className: `mt-2 inline-flex rounded-full px-2 py-0.5 text-xs
|
|
1140
|
+
className: `mt-2 inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
1119
1141
|
children: deal.status
|
|
1120
1142
|
}, undefined, false, undefined, this)
|
|
1121
1143
|
]
|
|
@@ -1167,7 +1189,7 @@ function DealActionsModal({
|
|
|
1167
1189
|
]
|
|
1168
1190
|
}, undefined, true, undefined, this),
|
|
1169
1191
|
deal.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
|
|
1170
|
-
className: "
|
|
1192
|
+
className: "py-4 text-center text-muted-foreground",
|
|
1171
1193
|
children: [
|
|
1172
1194
|
"This deal is already ",
|
|
1173
1195
|
deal.status.toLowerCase(),
|
|
@@ -1192,14 +1214,14 @@ function DealActionsModal({
|
|
|
1192
1214
|
children: [
|
|
1193
1215
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
1194
1216
|
htmlFor: "won-source",
|
|
1195
|
-
className: "
|
|
1217
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
1196
1218
|
children: "How did you win this deal?"
|
|
1197
1219
|
}, undefined, false, undefined, this),
|
|
1198
1220
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
1199
1221
|
id: "won-source",
|
|
1200
1222
|
value: wonSource,
|
|
1201
1223
|
onChange: (e) => setWonSource(e.target.value),
|
|
1202
|
-
className: "
|
|
1224
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1203
1225
|
children: [
|
|
1204
1226
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
1205
1227
|
value: "",
|
|
@@ -1233,7 +1255,7 @@ function DealActionsModal({
|
|
|
1233
1255
|
children: [
|
|
1234
1256
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
1235
1257
|
htmlFor: "win-notes",
|
|
1236
|
-
className: "
|
|
1258
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
1237
1259
|
children: "Notes (optional)"
|
|
1238
1260
|
}, undefined, false, undefined, this),
|
|
1239
1261
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -1242,12 +1264,12 @@ function DealActionsModal({
|
|
|
1242
1264
|
onChange: (e) => setNotes(e.target.value),
|
|
1243
1265
|
placeholder: "Any additional notes about the win...",
|
|
1244
1266
|
rows: 3,
|
|
1245
|
-
className: "
|
|
1267
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1246
1268
|
}, undefined, false, undefined, this)
|
|
1247
1269
|
]
|
|
1248
1270
|
}, undefined, true, undefined, this),
|
|
1249
1271
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
1250
|
-
className: "bg-destructive/10
|
|
1272
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
1251
1273
|
children: error
|
|
1252
1274
|
}, undefined, false, undefined, this),
|
|
1253
1275
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -1275,14 +1297,14 @@ function DealActionsModal({
|
|
|
1275
1297
|
children: [
|
|
1276
1298
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
1277
1299
|
htmlFor: "lost-reason",
|
|
1278
|
-
className: "
|
|
1300
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
1279
1301
|
children: "Why was this deal lost? *"
|
|
1280
1302
|
}, undefined, false, undefined, this),
|
|
1281
1303
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
1282
1304
|
id: "lost-reason",
|
|
1283
1305
|
value: lostReason,
|
|
1284
1306
|
onChange: (e) => setLostReason(e.target.value),
|
|
1285
|
-
className: "
|
|
1307
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1286
1308
|
children: [
|
|
1287
1309
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
1288
1310
|
value: "",
|
|
@@ -1324,7 +1346,7 @@ function DealActionsModal({
|
|
|
1324
1346
|
children: [
|
|
1325
1347
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
1326
1348
|
htmlFor: "lose-notes",
|
|
1327
|
-
className: "
|
|
1349
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
1328
1350
|
children: "Notes (optional)"
|
|
1329
1351
|
}, undefined, false, undefined, this),
|
|
1330
1352
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -1333,12 +1355,12 @@ function DealActionsModal({
|
|
|
1333
1355
|
onChange: (e) => setNotes(e.target.value),
|
|
1334
1356
|
placeholder: "Any additional details...",
|
|
1335
1357
|
rows: 3,
|
|
1336
|
-
className: "
|
|
1358
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1337
1359
|
}, undefined, false, undefined, this)
|
|
1338
1360
|
]
|
|
1339
1361
|
}, undefined, true, undefined, this),
|
|
1340
1362
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
1341
|
-
className: "bg-destructive/10
|
|
1363
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
1342
1364
|
children: error
|
|
1343
1365
|
}, undefined, false, undefined, this),
|
|
1344
1366
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -1367,14 +1389,14 @@ function DealActionsModal({
|
|
|
1367
1389
|
children: [
|
|
1368
1390
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
1369
1391
|
htmlFor: "move-stage",
|
|
1370
|
-
className: "
|
|
1392
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
1371
1393
|
children: "Move to Stage"
|
|
1372
1394
|
}, undefined, false, undefined, this),
|
|
1373
1395
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
1374
1396
|
id: "move-stage",
|
|
1375
1397
|
value: selectedStageId,
|
|
1376
1398
|
onChange: (e) => setSelectedStageId(e.target.value),
|
|
1377
|
-
className: "
|
|
1399
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1378
1400
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
|
|
1379
1401
|
value: stage.id,
|
|
1380
1402
|
children: [
|
|
@@ -1386,7 +1408,7 @@ function DealActionsModal({
|
|
|
1386
1408
|
]
|
|
1387
1409
|
}, undefined, true, undefined, this),
|
|
1388
1410
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
1389
|
-
className: "bg-destructive/10
|
|
1411
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
1390
1412
|
children: error
|
|
1391
1413
|
}, undefined, false, undefined, this),
|
|
1392
1414
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -1413,12 +1435,305 @@ function DealActionsModal({
|
|
|
1413
1435
|
}, undefined, true, undefined, this);
|
|
1414
1436
|
}
|
|
1415
1437
|
|
|
1416
|
-
// src/ui/
|
|
1417
|
-
import { useCallback as useCallback3, useState as useState6 } from "react";
|
|
1438
|
+
// src/ui/tables/DealListTab.tsx
|
|
1418
1439
|
import {
|
|
1419
1440
|
Button as Button3,
|
|
1441
|
+
DataTable,
|
|
1442
|
+
LoaderBlock
|
|
1443
|
+
} from "@contractspec/lib.design-system";
|
|
1444
|
+
import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
|
|
1445
|
+
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
1446
|
+
import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
|
|
1447
|
+
import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
|
|
1448
|
+
import * as React from "react";
|
|
1449
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
1450
|
+
"use client";
|
|
1451
|
+
function formatCurrency4(value, currency = "USD") {
|
|
1452
|
+
return new Intl.NumberFormat("en-US", {
|
|
1453
|
+
style: "currency",
|
|
1454
|
+
currency,
|
|
1455
|
+
minimumFractionDigits: 0,
|
|
1456
|
+
maximumFractionDigits: 0
|
|
1457
|
+
}).format(value);
|
|
1458
|
+
}
|
|
1459
|
+
function statusVariant(status) {
|
|
1460
|
+
switch (status) {
|
|
1461
|
+
case "WON":
|
|
1462
|
+
return "default";
|
|
1463
|
+
case "LOST":
|
|
1464
|
+
return "destructive";
|
|
1465
|
+
case "STALE":
|
|
1466
|
+
return "outline";
|
|
1467
|
+
default:
|
|
1468
|
+
return "secondary";
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function DealListDataTable({
|
|
1472
|
+
deals,
|
|
1473
|
+
totalItems,
|
|
1474
|
+
pageIndex,
|
|
1475
|
+
pageSize,
|
|
1476
|
+
sorting,
|
|
1477
|
+
loading,
|
|
1478
|
+
onSortingChange,
|
|
1479
|
+
onPaginationChange,
|
|
1480
|
+
onDealClick
|
|
1481
|
+
}) {
|
|
1482
|
+
const controller = useContractTable({
|
|
1483
|
+
data: deals,
|
|
1484
|
+
columns: [
|
|
1485
|
+
{
|
|
1486
|
+
id: "deal",
|
|
1487
|
+
header: "Deal",
|
|
1488
|
+
label: "Deal",
|
|
1489
|
+
accessor: (deal) => deal.name,
|
|
1490
|
+
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1491
|
+
gap: "xs",
|
|
1492
|
+
children: [
|
|
1493
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1494
|
+
className: "font-medium text-sm",
|
|
1495
|
+
children: item.name
|
|
1496
|
+
}, undefined, false, undefined, this),
|
|
1497
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1498
|
+
className: "text-muted-foreground text-xs",
|
|
1499
|
+
children: item.companyId ?? "Unassigned company"
|
|
1500
|
+
}, undefined, false, undefined, this)
|
|
1501
|
+
]
|
|
1502
|
+
}, undefined, true, undefined, this),
|
|
1503
|
+
size: 240,
|
|
1504
|
+
minSize: 180,
|
|
1505
|
+
canSort: true,
|
|
1506
|
+
canPin: true,
|
|
1507
|
+
canResize: true
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
id: "value",
|
|
1511
|
+
header: "Value",
|
|
1512
|
+
label: "Value",
|
|
1513
|
+
accessorKey: "value",
|
|
1514
|
+
cell: ({ item }) => formatCurrency4(item.value, item.currency),
|
|
1515
|
+
align: "right",
|
|
1516
|
+
size: 140,
|
|
1517
|
+
canSort: true,
|
|
1518
|
+
canResize: true
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
id: "status",
|
|
1522
|
+
header: "Status",
|
|
1523
|
+
label: "Status",
|
|
1524
|
+
accessorKey: "status",
|
|
1525
|
+
cell: ({ value }) => /* @__PURE__ */ jsxDEV5(Badge, {
|
|
1526
|
+
variant: statusVariant(value),
|
|
1527
|
+
children: String(value)
|
|
1528
|
+
}, undefined, false, undefined, this),
|
|
1529
|
+
size: 130,
|
|
1530
|
+
canSort: true,
|
|
1531
|
+
canHide: true,
|
|
1532
|
+
canPin: true,
|
|
1533
|
+
canResize: true
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
id: "expectedCloseDate",
|
|
1537
|
+
header: "Expected Close",
|
|
1538
|
+
label: "Expected Close",
|
|
1539
|
+
accessor: (deal) => deal.expectedCloseDate?.toISOString() ?? "",
|
|
1540
|
+
cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
|
|
1541
|
+
size: 170,
|
|
1542
|
+
canSort: true,
|
|
1543
|
+
canHide: true,
|
|
1544
|
+
canResize: true
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
id: "updatedAt",
|
|
1548
|
+
header: "Updated",
|
|
1549
|
+
label: "Updated",
|
|
1550
|
+
accessor: (deal) => deal.updatedAt.toISOString(),
|
|
1551
|
+
cell: ({ item }) => item.updatedAt.toLocaleDateString(),
|
|
1552
|
+
size: 140,
|
|
1553
|
+
canSort: true,
|
|
1554
|
+
canHide: true,
|
|
1555
|
+
canResize: true
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
id: "actions",
|
|
1559
|
+
header: "Actions",
|
|
1560
|
+
label: "Actions",
|
|
1561
|
+
accessor: (deal) => deal.id,
|
|
1562
|
+
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(Button3, {
|
|
1563
|
+
variant: "ghost",
|
|
1564
|
+
size: "sm",
|
|
1565
|
+
onPress: () => onDealClick?.(item.id),
|
|
1566
|
+
children: "Actions"
|
|
1567
|
+
}, undefined, false, undefined, this),
|
|
1568
|
+
size: 120,
|
|
1569
|
+
canSort: false,
|
|
1570
|
+
canHide: false,
|
|
1571
|
+
canPin: false,
|
|
1572
|
+
canResize: false
|
|
1573
|
+
}
|
|
1574
|
+
],
|
|
1575
|
+
executionMode: "server",
|
|
1576
|
+
selectionMode: "multiple",
|
|
1577
|
+
totalItems,
|
|
1578
|
+
state: {
|
|
1579
|
+
sorting,
|
|
1580
|
+
pagination: {
|
|
1581
|
+
pageIndex,
|
|
1582
|
+
pageSize
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
onSortingChange,
|
|
1586
|
+
onPaginationChange,
|
|
1587
|
+
initialState: {
|
|
1588
|
+
columnVisibility: { updatedAt: false },
|
|
1589
|
+
columnPinning: { left: ["deal", "status"], right: [] }
|
|
1590
|
+
},
|
|
1591
|
+
renderExpandedContent: (deal) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1592
|
+
gap: "sm",
|
|
1593
|
+
className: "py-2",
|
|
1594
|
+
children: [
|
|
1595
|
+
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
1596
|
+
justify: "between",
|
|
1597
|
+
children: [
|
|
1598
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1599
|
+
className: "font-medium text-sm",
|
|
1600
|
+
children: "Owner"
|
|
1601
|
+
}, undefined, false, undefined, this),
|
|
1602
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1603
|
+
className: "text-muted-foreground text-sm",
|
|
1604
|
+
children: deal.ownerId
|
|
1605
|
+
}, undefined, false, undefined, this)
|
|
1606
|
+
]
|
|
1607
|
+
}, undefined, true, undefined, this),
|
|
1608
|
+
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
1609
|
+
justify: "between",
|
|
1610
|
+
children: [
|
|
1611
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1612
|
+
className: "font-medium text-sm",
|
|
1613
|
+
children: "Contact"
|
|
1614
|
+
}, undefined, false, undefined, this),
|
|
1615
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1616
|
+
className: "text-muted-foreground text-sm",
|
|
1617
|
+
children: deal.contactId ?? "No linked contact"
|
|
1618
|
+
}, undefined, false, undefined, this)
|
|
1619
|
+
]
|
|
1620
|
+
}, undefined, true, undefined, this),
|
|
1621
|
+
deal.wonSource ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1622
|
+
justify: "between",
|
|
1623
|
+
children: [
|
|
1624
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1625
|
+
className: "font-medium text-sm",
|
|
1626
|
+
children: "Won Source"
|
|
1627
|
+
}, undefined, false, undefined, this),
|
|
1628
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1629
|
+
className: "text-muted-foreground text-sm",
|
|
1630
|
+
children: deal.wonSource
|
|
1631
|
+
}, undefined, false, undefined, this)
|
|
1632
|
+
]
|
|
1633
|
+
}, undefined, true, undefined, this) : null,
|
|
1634
|
+
deal.lostReason ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1635
|
+
justify: "between",
|
|
1636
|
+
children: [
|
|
1637
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1638
|
+
className: "font-medium text-sm",
|
|
1639
|
+
children: "Lost Reason"
|
|
1640
|
+
}, undefined, false, undefined, this),
|
|
1641
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1642
|
+
className: "text-muted-foreground text-sm",
|
|
1643
|
+
children: deal.lostReason
|
|
1644
|
+
}, undefined, false, undefined, this)
|
|
1645
|
+
]
|
|
1646
|
+
}, undefined, true, undefined, this) : null,
|
|
1647
|
+
deal.notes ? /* @__PURE__ */ jsxDEV5(VStack, {
|
|
1648
|
+
gap: "xs",
|
|
1649
|
+
children: [
|
|
1650
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1651
|
+
className: "font-medium text-sm",
|
|
1652
|
+
children: "Notes"
|
|
1653
|
+
}, undefined, false, undefined, this),
|
|
1654
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1655
|
+
className: "text-muted-foreground text-sm",
|
|
1656
|
+
children: deal.notes
|
|
1657
|
+
}, undefined, false, undefined, this)
|
|
1658
|
+
]
|
|
1659
|
+
}, undefined, true, undefined, this) : null
|
|
1660
|
+
]
|
|
1661
|
+
}, undefined, true, undefined, this),
|
|
1662
|
+
getCanExpand: () => true
|
|
1663
|
+
});
|
|
1664
|
+
return /* @__PURE__ */ jsxDEV5(DataTable, {
|
|
1665
|
+
controller,
|
|
1666
|
+
title: "All Deals",
|
|
1667
|
+
description: "Server-mode table using the shared ContractSpec controller.",
|
|
1668
|
+
loading,
|
|
1669
|
+
toolbar: /* @__PURE__ */ jsxDEV5(HStack, {
|
|
1670
|
+
gap: "sm",
|
|
1671
|
+
className: "flex-wrap",
|
|
1672
|
+
children: [
|
|
1673
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1674
|
+
className: "text-muted-foreground text-sm",
|
|
1675
|
+
children: [
|
|
1676
|
+
"Selected ",
|
|
1677
|
+
controller.selectedRowIds.length
|
|
1678
|
+
]
|
|
1679
|
+
}, undefined, true, undefined, this),
|
|
1680
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
1681
|
+
className: "text-muted-foreground text-sm",
|
|
1682
|
+
children: [
|
|
1683
|
+
totalItems,
|
|
1684
|
+
" total deals"
|
|
1685
|
+
]
|
|
1686
|
+
}, undefined, true, undefined, this)
|
|
1687
|
+
]
|
|
1688
|
+
}, undefined, true, undefined, this),
|
|
1689
|
+
footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
|
|
1690
|
+
emptyState: /* @__PURE__ */ jsxDEV5("div", {
|
|
1691
|
+
className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
|
|
1692
|
+
children: "No deals found"
|
|
1693
|
+
}, undefined, false, undefined, this)
|
|
1694
|
+
}, undefined, false, undefined, this);
|
|
1695
|
+
}
|
|
1696
|
+
function DealListTab({
|
|
1697
|
+
onDealClick
|
|
1698
|
+
}) {
|
|
1699
|
+
const [sorting, setSorting] = React.useState([
|
|
1700
|
+
{ id: "value", desc: true }
|
|
1701
|
+
]);
|
|
1702
|
+
const [pagination, setPagination] = React.useState({
|
|
1703
|
+
pageIndex: 0,
|
|
1704
|
+
pageSize: 3
|
|
1705
|
+
});
|
|
1706
|
+
const { data, loading } = useDealList({
|
|
1707
|
+
pageIndex: pagination.pageIndex,
|
|
1708
|
+
pageSize: pagination.pageSize,
|
|
1709
|
+
sorting
|
|
1710
|
+
});
|
|
1711
|
+
if (loading && !data) {
|
|
1712
|
+
return /* @__PURE__ */ jsxDEV5(LoaderBlock, {
|
|
1713
|
+
label: "Loading deals..."
|
|
1714
|
+
}, undefined, false, undefined, this);
|
|
1715
|
+
}
|
|
1716
|
+
return /* @__PURE__ */ jsxDEV5(DealListDataTable, {
|
|
1717
|
+
deals: data?.deals ?? [],
|
|
1718
|
+
totalItems: data?.total ?? 0,
|
|
1719
|
+
pageIndex: pagination.pageIndex,
|
|
1720
|
+
pageSize: pagination.pageSize,
|
|
1721
|
+
sorting,
|
|
1722
|
+
loading,
|
|
1723
|
+
onSortingChange: (nextSorting) => {
|
|
1724
|
+
setSorting(nextSorting);
|
|
1725
|
+
setPagination((current) => ({ ...current, pageIndex: 0 }));
|
|
1726
|
+
},
|
|
1727
|
+
onPaginationChange: setPagination,
|
|
1728
|
+
onDealClick
|
|
1729
|
+
}, undefined, false, undefined, this);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/ui/CrmDashboard.tsx
|
|
1733
|
+
import {
|
|
1734
|
+
Button as Button4,
|
|
1420
1735
|
ErrorState,
|
|
1421
|
-
LoaderBlock,
|
|
1736
|
+
LoaderBlock as LoaderBlock2,
|
|
1422
1737
|
StatCard,
|
|
1423
1738
|
StatCardGroup
|
|
1424
1739
|
} from "@contractspec/lib.design-system";
|
|
@@ -1428,9 +1743,10 @@ import {
|
|
|
1428
1743
|
TabsList,
|
|
1429
1744
|
TabsTrigger
|
|
1430
1745
|
} from "@contractspec/lib.ui-kit-web/ui/tabs";
|
|
1431
|
-
import {
|
|
1746
|
+
import { useCallback as useCallback3, useState as useState7 } from "react";
|
|
1747
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
1432
1748
|
"use client";
|
|
1433
|
-
function
|
|
1749
|
+
function formatCurrency5(value, currency = "USD") {
|
|
1434
1750
|
return new Intl.NumberFormat("en-US", {
|
|
1435
1751
|
style: "currency",
|
|
1436
1752
|
currency,
|
|
@@ -1439,9 +1755,9 @@ function formatCurrency4(value, currency = "USD") {
|
|
|
1439
1755
|
}).format(value);
|
|
1440
1756
|
}
|
|
1441
1757
|
function CrmDashboard() {
|
|
1442
|
-
const [isCreateModalOpen, setIsCreateModalOpen] =
|
|
1443
|
-
const [selectedDeal, setSelectedDeal] =
|
|
1444
|
-
const [isDealActionsOpen, setIsDealActionsOpen] =
|
|
1758
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState7(false);
|
|
1759
|
+
const [selectedDeal, setSelectedDeal] = useState7(null);
|
|
1760
|
+
const [isDealActionsOpen, setIsDealActionsOpen] = useState7(false);
|
|
1445
1761
|
const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
|
|
1446
1762
|
const mutations = useDealMutations({
|
|
1447
1763
|
onSuccess: () => {
|
|
@@ -1459,32 +1775,32 @@ function CrmDashboard() {
|
|
|
1459
1775
|
await mutations.moveDeal({ dealId, stageId: toStageId });
|
|
1460
1776
|
}, [mutations]);
|
|
1461
1777
|
if (loading && !data) {
|
|
1462
|
-
return /* @__PURE__ */
|
|
1778
|
+
return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
|
|
1463
1779
|
label: "Loading CRM..."
|
|
1464
1780
|
}, undefined, false, undefined, this);
|
|
1465
1781
|
}
|
|
1466
1782
|
if (error) {
|
|
1467
|
-
return /* @__PURE__ */
|
|
1783
|
+
return /* @__PURE__ */ jsxDEV6(ErrorState, {
|
|
1468
1784
|
title: "Failed to load CRM",
|
|
1469
1785
|
description: error.message,
|
|
1470
1786
|
onRetry: refetch,
|
|
1471
1787
|
retryLabel: "Retry"
|
|
1472
1788
|
}, undefined, false, undefined, this);
|
|
1473
1789
|
}
|
|
1474
|
-
return /* @__PURE__ */
|
|
1790
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1475
1791
|
className: "space-y-6",
|
|
1476
1792
|
children: [
|
|
1477
|
-
/* @__PURE__ */
|
|
1793
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1478
1794
|
className: "flex items-center justify-between",
|
|
1479
1795
|
children: [
|
|
1480
|
-
/* @__PURE__ */
|
|
1481
|
-
className: "text-2xl
|
|
1796
|
+
/* @__PURE__ */ jsxDEV6("h2", {
|
|
1797
|
+
className: "font-bold text-2xl",
|
|
1482
1798
|
children: "CRM Pipeline"
|
|
1483
1799
|
}, undefined, false, undefined, this),
|
|
1484
|
-
/* @__PURE__ */
|
|
1800
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
1485
1801
|
onClick: () => setIsCreateModalOpen(true),
|
|
1486
1802
|
children: [
|
|
1487
|
-
/* @__PURE__ */
|
|
1803
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1488
1804
|
className: "mr-2",
|
|
1489
1805
|
children: "+"
|
|
1490
1806
|
}, undefined, false, undefined, this),
|
|
@@ -1493,60 +1809,60 @@ function CrmDashboard() {
|
|
|
1493
1809
|
}, undefined, true, undefined, this)
|
|
1494
1810
|
]
|
|
1495
1811
|
}, undefined, true, undefined, this),
|
|
1496
|
-
stats && /* @__PURE__ */
|
|
1812
|
+
stats && /* @__PURE__ */ jsxDEV6(StatCardGroup, {
|
|
1497
1813
|
children: [
|
|
1498
|
-
/* @__PURE__ */
|
|
1814
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1499
1815
|
label: "Total Pipeline",
|
|
1500
|
-
value:
|
|
1816
|
+
value: formatCurrency5(stats.totalValue),
|
|
1501
1817
|
hint: `${stats.total} deals`
|
|
1502
1818
|
}, undefined, false, undefined, this),
|
|
1503
|
-
/* @__PURE__ */
|
|
1819
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1504
1820
|
label: "Open Deals",
|
|
1505
|
-
value:
|
|
1821
|
+
value: formatCurrency5(stats.openValue),
|
|
1506
1822
|
hint: `${stats.openCount} active`
|
|
1507
1823
|
}, undefined, false, undefined, this),
|
|
1508
|
-
/* @__PURE__ */
|
|
1824
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1509
1825
|
label: "Won",
|
|
1510
|
-
value:
|
|
1826
|
+
value: formatCurrency5(stats.wonValue),
|
|
1511
1827
|
hint: `${stats.wonCount} closed`
|
|
1512
1828
|
}, undefined, false, undefined, this),
|
|
1513
|
-
/* @__PURE__ */
|
|
1829
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
1514
1830
|
label: "Lost",
|
|
1515
1831
|
value: stats.lostCount,
|
|
1516
1832
|
hint: "deals lost"
|
|
1517
1833
|
}, undefined, false, undefined, this)
|
|
1518
1834
|
]
|
|
1519
1835
|
}, undefined, true, undefined, this),
|
|
1520
|
-
/* @__PURE__ */
|
|
1836
|
+
/* @__PURE__ */ jsxDEV6(Tabs, {
|
|
1521
1837
|
defaultValue: "pipeline",
|
|
1522
1838
|
className: "w-full",
|
|
1523
1839
|
children: [
|
|
1524
|
-
/* @__PURE__ */
|
|
1840
|
+
/* @__PURE__ */ jsxDEV6(TabsList, {
|
|
1525
1841
|
children: [
|
|
1526
|
-
/* @__PURE__ */
|
|
1842
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1527
1843
|
value: "pipeline",
|
|
1528
1844
|
children: [
|
|
1529
|
-
/* @__PURE__ */
|
|
1845
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1530
1846
|
className: "mr-2",
|
|
1531
1847
|
children: "\uD83D\uDCCA"
|
|
1532
1848
|
}, undefined, false, undefined, this),
|
|
1533
1849
|
"Pipeline"
|
|
1534
1850
|
]
|
|
1535
1851
|
}, undefined, true, undefined, this),
|
|
1536
|
-
/* @__PURE__ */
|
|
1852
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1537
1853
|
value: "list",
|
|
1538
1854
|
children: [
|
|
1539
|
-
/* @__PURE__ */
|
|
1855
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1540
1856
|
className: "mr-2",
|
|
1541
1857
|
children: "\uD83D\uDCCB"
|
|
1542
1858
|
}, undefined, false, undefined, this),
|
|
1543
1859
|
"All Deals"
|
|
1544
1860
|
]
|
|
1545
1861
|
}, undefined, true, undefined, this),
|
|
1546
|
-
/* @__PURE__ */
|
|
1862
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
1547
1863
|
value: "metrics",
|
|
1548
1864
|
children: [
|
|
1549
|
-
/* @__PURE__ */
|
|
1865
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
1550
1866
|
className: "mr-2",
|
|
1551
1867
|
children: "\uD83D\uDCC8"
|
|
1552
1868
|
}, undefined, false, undefined, this),
|
|
@@ -1555,34 +1871,33 @@ function CrmDashboard() {
|
|
|
1555
1871
|
}, undefined, true, undefined, this)
|
|
1556
1872
|
]
|
|
1557
1873
|
}, undefined, true, undefined, this),
|
|
1558
|
-
/* @__PURE__ */
|
|
1874
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1559
1875
|
value: "pipeline",
|
|
1560
1876
|
className: "min-h-[400px]",
|
|
1561
|
-
children: /* @__PURE__ */
|
|
1877
|
+
children: /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
|
|
1562
1878
|
dealsByStage,
|
|
1563
1879
|
stages,
|
|
1564
1880
|
onDealClick: handleDealClick,
|
|
1565
1881
|
onDealMove: handleDealMove
|
|
1566
1882
|
}, undefined, false, undefined, this)
|
|
1567
1883
|
}, undefined, false, undefined, this),
|
|
1568
|
-
/* @__PURE__ */
|
|
1884
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1569
1885
|
value: "list",
|
|
1570
1886
|
className: "min-h-[400px]",
|
|
1571
|
-
children: /* @__PURE__ */
|
|
1572
|
-
data,
|
|
1887
|
+
children: /* @__PURE__ */ jsxDEV6(DealListTab, {
|
|
1573
1888
|
onDealClick: handleDealClick
|
|
1574
1889
|
}, undefined, false, undefined, this)
|
|
1575
1890
|
}, undefined, false, undefined, this),
|
|
1576
|
-
/* @__PURE__ */
|
|
1891
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
1577
1892
|
value: "metrics",
|
|
1578
1893
|
className: "min-h-[400px]",
|
|
1579
|
-
children: /* @__PURE__ */
|
|
1894
|
+
children: /* @__PURE__ */ jsxDEV6(MetricsTab, {
|
|
1580
1895
|
stats
|
|
1581
1896
|
}, undefined, false, undefined, this)
|
|
1582
1897
|
}, undefined, false, undefined, this)
|
|
1583
1898
|
]
|
|
1584
1899
|
}, undefined, true, undefined, this),
|
|
1585
|
-
/* @__PURE__ */
|
|
1900
|
+
/* @__PURE__ */ jsxDEV6(CreateDealModal, {
|
|
1586
1901
|
isOpen: isCreateModalOpen,
|
|
1587
1902
|
onClose: () => setIsCreateModalOpen(false),
|
|
1588
1903
|
onSubmit: async (input) => {
|
|
@@ -1591,7 +1906,7 @@ function CrmDashboard() {
|
|
|
1591
1906
|
stages,
|
|
1592
1907
|
isLoading: mutations.createState.loading
|
|
1593
1908
|
}, undefined, false, undefined, this),
|
|
1594
|
-
/* @__PURE__ */
|
|
1909
|
+
/* @__PURE__ */ jsxDEV6(DealActionsModal, {
|
|
1595
1910
|
isOpen: isDealActionsOpen,
|
|
1596
1911
|
deal: selectedDeal,
|
|
1597
1912
|
stages,
|
|
@@ -1614,113 +1929,31 @@ function CrmDashboard() {
|
|
|
1614
1929
|
]
|
|
1615
1930
|
}, undefined, true, undefined, this);
|
|
1616
1931
|
}
|
|
1617
|
-
function DealListTab({ data, onDealClick }) {
|
|
1618
|
-
if (!data?.deals.length) {
|
|
1619
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
1620
|
-
className: "text-muted-foreground flex h-64 items-center justify-center",
|
|
1621
|
-
children: "No deals found"
|
|
1622
|
-
}, undefined, false, undefined, this);
|
|
1623
|
-
}
|
|
1624
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
1625
|
-
className: "border-border rounded-lg border",
|
|
1626
|
-
children: /* @__PURE__ */ jsxDEV5("table", {
|
|
1627
|
-
className: "w-full",
|
|
1628
|
-
children: [
|
|
1629
|
-
/* @__PURE__ */ jsxDEV5("thead", {
|
|
1630
|
-
className: "border-border bg-muted/30 border-b",
|
|
1631
|
-
children: /* @__PURE__ */ jsxDEV5("tr", {
|
|
1632
|
-
children: [
|
|
1633
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1634
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1635
|
-
children: "Deal"
|
|
1636
|
-
}, undefined, false, undefined, this),
|
|
1637
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1638
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1639
|
-
children: "Value"
|
|
1640
|
-
}, undefined, false, undefined, this),
|
|
1641
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1642
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1643
|
-
children: "Status"
|
|
1644
|
-
}, undefined, false, undefined, this),
|
|
1645
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1646
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1647
|
-
children: "Expected Close"
|
|
1648
|
-
}, undefined, false, undefined, this),
|
|
1649
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
1650
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
1651
|
-
children: "Actions"
|
|
1652
|
-
}, undefined, false, undefined, this)
|
|
1653
|
-
]
|
|
1654
|
-
}, undefined, true, undefined, this)
|
|
1655
|
-
}, undefined, false, undefined, this),
|
|
1656
|
-
/* @__PURE__ */ jsxDEV5("tbody", {
|
|
1657
|
-
className: "divide-border divide-y",
|
|
1658
|
-
children: data.deals.map((deal) => /* @__PURE__ */ jsxDEV5("tr", {
|
|
1659
|
-
className: "hover:bg-muted/50",
|
|
1660
|
-
children: [
|
|
1661
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1662
|
-
className: "px-4 py-3",
|
|
1663
|
-
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
1664
|
-
className: "font-medium",
|
|
1665
|
-
children: deal.name
|
|
1666
|
-
}, undefined, false, undefined, this)
|
|
1667
|
-
}, undefined, false, undefined, this),
|
|
1668
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1669
|
-
className: "px-4 py-3 font-mono",
|
|
1670
|
-
children: formatCurrency4(deal.value, deal.currency)
|
|
1671
|
-
}, undefined, false, undefined, this),
|
|
1672
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1673
|
-
className: "px-4 py-3",
|
|
1674
|
-
children: /* @__PURE__ */ jsxDEV5("span", {
|
|
1675
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
1676
|
-
children: deal.status
|
|
1677
|
-
}, undefined, false, undefined, this)
|
|
1678
|
-
}, undefined, false, undefined, this),
|
|
1679
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1680
|
-
className: "text-muted-foreground px-4 py-3",
|
|
1681
|
-
children: deal.expectedCloseDate?.toLocaleDateString() ?? "-"
|
|
1682
|
-
}, undefined, false, undefined, this),
|
|
1683
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
1684
|
-
className: "px-4 py-3",
|
|
1685
|
-
children: /* @__PURE__ */ jsxDEV5(Button3, {
|
|
1686
|
-
variant: "ghost",
|
|
1687
|
-
size: "sm",
|
|
1688
|
-
onPress: () => onDealClick?.(deal.id),
|
|
1689
|
-
children: "Actions"
|
|
1690
|
-
}, undefined, false, undefined, this)
|
|
1691
|
-
}, undefined, false, undefined, this)
|
|
1692
|
-
]
|
|
1693
|
-
}, deal.id, true, undefined, this))
|
|
1694
|
-
}, undefined, false, undefined, this)
|
|
1695
|
-
]
|
|
1696
|
-
}, undefined, true, undefined, this)
|
|
1697
|
-
}, undefined, false, undefined, this);
|
|
1698
|
-
}
|
|
1699
1932
|
function MetricsTab({
|
|
1700
1933
|
stats
|
|
1701
1934
|
}) {
|
|
1702
1935
|
if (!stats)
|
|
1703
1936
|
return null;
|
|
1704
|
-
return /* @__PURE__ */
|
|
1937
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1705
1938
|
className: "space-y-6",
|
|
1706
|
-
children: /* @__PURE__ */
|
|
1707
|
-
className: "border-border bg-card
|
|
1939
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
1940
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
1708
1941
|
children: [
|
|
1709
|
-
/* @__PURE__ */
|
|
1710
|
-
className: "mb-4 text-lg
|
|
1942
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
1943
|
+
className: "mb-4 font-semibold text-lg",
|
|
1711
1944
|
children: "Pipeline Overview"
|
|
1712
1945
|
}, undefined, false, undefined, this),
|
|
1713
|
-
/* @__PURE__ */
|
|
1946
|
+
/* @__PURE__ */ jsxDEV6("dl", {
|
|
1714
1947
|
className: "grid gap-4 sm:grid-cols-3",
|
|
1715
1948
|
children: [
|
|
1716
|
-
/* @__PURE__ */
|
|
1949
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1717
1950
|
children: [
|
|
1718
|
-
/* @__PURE__ */
|
|
1951
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1719
1952
|
className: "text-muted-foreground text-sm",
|
|
1720
1953
|
children: "Win Rate"
|
|
1721
1954
|
}, undefined, false, undefined, this),
|
|
1722
|
-
/* @__PURE__ */
|
|
1723
|
-
className: "text-2xl
|
|
1955
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1956
|
+
className: "font-semibold text-2xl",
|
|
1724
1957
|
children: [
|
|
1725
1958
|
stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
|
|
1726
1959
|
"%"
|
|
@@ -1728,26 +1961,26 @@ function MetricsTab({
|
|
|
1728
1961
|
}, undefined, true, undefined, this)
|
|
1729
1962
|
]
|
|
1730
1963
|
}, undefined, true, undefined, this),
|
|
1731
|
-
/* @__PURE__ */
|
|
1964
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1732
1965
|
children: [
|
|
1733
|
-
/* @__PURE__ */
|
|
1966
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1734
1967
|
className: "text-muted-foreground text-sm",
|
|
1735
1968
|
children: "Avg Deal Size"
|
|
1736
1969
|
}, undefined, false, undefined, this),
|
|
1737
|
-
/* @__PURE__ */
|
|
1738
|
-
className: "text-2xl
|
|
1739
|
-
children:
|
|
1970
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1971
|
+
className: "font-semibold text-2xl",
|
|
1972
|
+
children: formatCurrency5(stats.total > 0 ? stats.totalValue / stats.total : 0)
|
|
1740
1973
|
}, undefined, false, undefined, this)
|
|
1741
1974
|
]
|
|
1742
1975
|
}, undefined, true, undefined, this),
|
|
1743
|
-
/* @__PURE__ */
|
|
1976
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
1744
1977
|
children: [
|
|
1745
|
-
/* @__PURE__ */
|
|
1978
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
1746
1979
|
className: "text-muted-foreground text-sm",
|
|
1747
1980
|
children: "Conversion"
|
|
1748
1981
|
}, undefined, false, undefined, this),
|
|
1749
|
-
/* @__PURE__ */
|
|
1750
|
-
className: "text-2xl
|
|
1982
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
1983
|
+
className: "font-semibold text-2xl",
|
|
1751
1984
|
children: [
|
|
1752
1985
|
stats.wonCount,
|
|
1753
1986
|
" / ",
|
|
@@ -1762,33 +1995,61 @@ function MetricsTab({
|
|
|
1762
1995
|
}, undefined, true, undefined, this)
|
|
1763
1996
|
}, undefined, false, undefined, this);
|
|
1764
1997
|
}
|
|
1998
|
+
|
|
1765
1999
|
// src/ui/hooks/index.ts
|
|
1766
2000
|
"use client";
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
},
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2001
|
+
// src/ui/overlays/demo-overlays.ts
|
|
2002
|
+
var crmDemoOverlay = {
|
|
2003
|
+
overlayId: "crm-pipeline.demo-user",
|
|
2004
|
+
version: "1.0.0",
|
|
2005
|
+
description: "Demo mode with sample data",
|
|
2006
|
+
appliesTo: {
|
|
2007
|
+
feature: "crm-pipeline",
|
|
2008
|
+
role: "demo"
|
|
2009
|
+
},
|
|
2010
|
+
modifications: [
|
|
2011
|
+
{
|
|
2012
|
+
type: "hideField",
|
|
2013
|
+
field: "importButton",
|
|
2014
|
+
reason: "Not available in demo"
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
type: "hideField",
|
|
2018
|
+
field: "exportButton",
|
|
2019
|
+
reason: "Not available in demo"
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
type: "addBadge",
|
|
2023
|
+
position: "header",
|
|
2024
|
+
label: "Demo Mode",
|
|
2025
|
+
variant: "warning"
|
|
1785
2026
|
}
|
|
1786
|
-
|
|
1787
|
-
}
|
|
2027
|
+
]
|
|
1788
2028
|
};
|
|
1789
|
-
|
|
2029
|
+
var crmSalesRepOverlay = {
|
|
2030
|
+
overlayId: "crm-pipeline.sales-rep",
|
|
2031
|
+
version: "1.0.0",
|
|
2032
|
+
description: "Sales rep focused view",
|
|
2033
|
+
appliesTo: {
|
|
2034
|
+
feature: "crm-pipeline",
|
|
2035
|
+
role: "sales-rep"
|
|
2036
|
+
},
|
|
2037
|
+
modifications: [
|
|
2038
|
+
{
|
|
2039
|
+
type: "hideField",
|
|
2040
|
+
field: "teamMetrics",
|
|
2041
|
+
reason: "Team metrics for managers only"
|
|
2042
|
+
},
|
|
2043
|
+
{ type: "hideField", field: "pipelineSettings", reason: "Admin only" },
|
|
2044
|
+
{ type: "renameLabel", field: "deals", newLabel: "My Deals" }
|
|
2045
|
+
]
|
|
2046
|
+
};
|
|
2047
|
+
var crmOverlays = [
|
|
2048
|
+
crmDemoOverlay,
|
|
2049
|
+
crmSalesRepOverlay
|
|
2050
|
+
];
|
|
1790
2051
|
// src/ui/renderers/pipeline.markdown.ts
|
|
1791
|
-
function
|
|
2052
|
+
function formatCurrency6(value, currency = "USD") {
|
|
1792
2053
|
return new Intl.NumberFormat("en-US", {
|
|
1793
2054
|
style: "currency",
|
|
1794
2055
|
currency,
|
|
@@ -1815,7 +2076,7 @@ var crmPipelineMarkdownRenderer = {
|
|
|
1815
2076
|
const lines = [
|
|
1816
2077
|
"# CRM Pipeline",
|
|
1817
2078
|
"",
|
|
1818
|
-
`**Total Value**: ${
|
|
2079
|
+
`**Total Value**: ${formatCurrency6(dealsResult.totalValue)}`,
|
|
1819
2080
|
`**Total Deals**: ${dealsResult.total}`,
|
|
1820
2081
|
""
|
|
1821
2082
|
];
|
|
@@ -1823,13 +2084,13 @@ var crmPipelineMarkdownRenderer = {
|
|
|
1823
2084
|
const stageDeals = dealsByStage[stage.id] ?? [];
|
|
1824
2085
|
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
1825
2086
|
lines.push(`## ${stage.name}`);
|
|
1826
|
-
lines.push(`_${stageDeals.length} deals · ${
|
|
2087
|
+
lines.push(`_${stageDeals.length} deals · ${formatCurrency6(stageValue)}_`);
|
|
1827
2088
|
lines.push("");
|
|
1828
2089
|
if (stageDeals.length === 0) {
|
|
1829
2090
|
lines.push("_No deals_");
|
|
1830
2091
|
} else {
|
|
1831
2092
|
for (const deal of stageDeals) {
|
|
1832
|
-
lines.push(`- **${deal.name}** - ${
|
|
2093
|
+
lines.push(`- **${deal.name}** - ${formatCurrency6(deal.value, deal.currency)}`);
|
|
1833
2094
|
}
|
|
1834
2095
|
}
|
|
1835
2096
|
lines.push("");
|
|
@@ -1869,9 +2130,9 @@ var crmDashboardMarkdownRenderer = {
|
|
|
1869
2130
|
"| Metric | Value |",
|
|
1870
2131
|
"|--------|-------|",
|
|
1871
2132
|
`| Total Deals | ${dealsResult.total} |`,
|
|
1872
|
-
`| Pipeline Value | ${
|
|
1873
|
-
`| Open Deals | ${openDeals.length} (${
|
|
1874
|
-
`| Won Deals | ${wonDeals.length} (${
|
|
2133
|
+
`| Pipeline Value | ${formatCurrency6(dealsResult.totalValue)} |`,
|
|
2134
|
+
`| Open Deals | ${openDeals.length} (${formatCurrency6(openValue)}) |`,
|
|
2135
|
+
`| Won Deals | ${wonDeals.length} (${formatCurrency6(wonValue)}) |`,
|
|
1875
2136
|
`| Lost Deals | ${lostDeals.length} |`,
|
|
1876
2137
|
"",
|
|
1877
2138
|
"## Pipeline Stages",
|
|
@@ -1882,7 +2143,7 @@ var crmDashboardMarkdownRenderer = {
|
|
|
1882
2143
|
for (const stage of stageList.sort((a, b) => a.position - b.position)) {
|
|
1883
2144
|
const stageDeals = openDeals.filter((d) => d.stageId === stage.id);
|
|
1884
2145
|
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
1885
|
-
lines.push(`| ${stage.name} | ${stageDeals.length} | ${
|
|
2146
|
+
lines.push(`| ${stage.name} | ${stageDeals.length} | ${formatCurrency6(stageValue)} |`);
|
|
1886
2147
|
}
|
|
1887
2148
|
lines.push("");
|
|
1888
2149
|
lines.push("## Recent Deals");
|
|
@@ -1895,7 +2156,7 @@ var crmDashboardMarkdownRenderer = {
|
|
|
1895
2156
|
lines.push("|------|-------|-------|--------|");
|
|
1896
2157
|
for (const deal of recentDeals) {
|
|
1897
2158
|
const stage = stageList.find((s) => s.id === deal.stageId);
|
|
1898
|
-
lines.push(`| ${deal.name} | ${
|
|
2159
|
+
lines.push(`| ${deal.name} | ${formatCurrency6(deal.value, deal.currency)} | ${stage?.name ?? "-"} | ${deal.status} |`);
|
|
1899
2160
|
}
|
|
1900
2161
|
}
|
|
1901
2162
|
return {
|
|
@@ -1905,56 +2166,28 @@ var crmDashboardMarkdownRenderer = {
|
|
|
1905
2166
|
};
|
|
1906
2167
|
}
|
|
1907
2168
|
};
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
},
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
{
|
|
1924
|
-
type: "hideField",
|
|
1925
|
-
field: "exportButton",
|
|
1926
|
-
reason: "Not available in demo"
|
|
1927
|
-
},
|
|
1928
|
-
{
|
|
1929
|
-
type: "addBadge",
|
|
1930
|
-
position: "header",
|
|
1931
|
-
label: "Demo Mode",
|
|
1932
|
-
variant: "warning"
|
|
2169
|
+
|
|
2170
|
+
// src/ui/renderers/pipeline.renderer.tsx
|
|
2171
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
2172
|
+
function CrmPipelineBoardWrapper() {
|
|
2173
|
+
const { dealsByStage, stages } = useDealList();
|
|
2174
|
+
return /* @__PURE__ */ jsxDEV7(CrmPipelineBoard, {
|
|
2175
|
+
dealsByStage,
|
|
2176
|
+
stages
|
|
2177
|
+
}, undefined, false, undefined, this);
|
|
2178
|
+
}
|
|
2179
|
+
var crmPipelineReactRenderer = {
|
|
2180
|
+
target: "react",
|
|
2181
|
+
render: async (desc, _ctx) => {
|
|
2182
|
+
if (desc.source.type !== "component") {
|
|
2183
|
+
throw new Error("Invalid source type");
|
|
1933
2184
|
}
|
|
1934
|
-
|
|
1935
|
-
};
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
description: "Sales rep focused view",
|
|
1940
|
-
appliesTo: {
|
|
1941
|
-
feature: "crm-pipeline",
|
|
1942
|
-
role: "sales-rep"
|
|
1943
|
-
},
|
|
1944
|
-
modifications: [
|
|
1945
|
-
{
|
|
1946
|
-
type: "hideField",
|
|
1947
|
-
field: "teamMetrics",
|
|
1948
|
-
reason: "Team metrics for managers only"
|
|
1949
|
-
},
|
|
1950
|
-
{ type: "hideField", field: "pipelineSettings", reason: "Admin only" },
|
|
1951
|
-
{ type: "renameLabel", field: "deals", newLabel: "My Deals" }
|
|
1952
|
-
]
|
|
2185
|
+
if (desc.source.componentKey !== "CrmPipelineView") {
|
|
2186
|
+
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
2187
|
+
}
|
|
2188
|
+
return /* @__PURE__ */ jsxDEV7(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
|
|
2189
|
+
}
|
|
1953
2190
|
};
|
|
1954
|
-
var crmOverlays = [
|
|
1955
|
-
crmDemoOverlay,
|
|
1956
|
-
crmSalesRepOverlay
|
|
1957
|
-
];
|
|
1958
2191
|
export {
|
|
1959
2192
|
useDealMutations,
|
|
1960
2193
|
useDealList,
|