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