@contractspec/example.crm-pipeline 3.7.5 → 3.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/AGENTS.md +51 -33
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +66 -148
  5. package/dist/browser/events/contact.event.js +1 -1
  6. package/dist/browser/events/deal.event.js +1 -1
  7. package/dist/browser/events/index.js +3 -3
  8. package/dist/browser/events/task.event.js +1 -1
  9. package/dist/browser/index.js +293 -293
  10. package/dist/browser/ui/CrmDashboard.js +221 -221
  11. package/dist/browser/ui/CrmDealCard.js +5 -5
  12. package/dist/browser/ui/CrmPipelineBoard.js +13 -13
  13. package/dist/browser/ui/hooks/index.js +2 -2
  14. package/dist/browser/ui/hooks/useDealList.js +1 -1
  15. package/dist/browser/ui/hooks/useDealMutations.js +1 -1
  16. package/dist/browser/ui/index.js +290 -290
  17. package/dist/browser/ui/modals/CreateDealModal.js +12 -12
  18. package/dist/browser/ui/modals/DealActionsModal.js +21 -21
  19. package/dist/browser/ui/modals/index.js +33 -33
  20. package/dist/browser/ui/renderers/index.js +116 -116
  21. package/dist/browser/ui/renderers/pipeline.renderer.js +97 -97
  22. package/dist/deal/index.d.ts +2 -2
  23. package/dist/events/contact.event.js +1 -1
  24. package/dist/events/deal.event.js +1 -1
  25. package/dist/events/index.js +3 -3
  26. package/dist/events/task.event.js +1 -1
  27. package/dist/handlers/index.d.ts +2 -2
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +293 -293
  30. package/dist/node/events/contact.event.js +1 -1
  31. package/dist/node/events/deal.event.js +1 -1
  32. package/dist/node/events/index.js +3 -3
  33. package/dist/node/events/task.event.js +1 -1
  34. package/dist/node/index.js +293 -293
  35. package/dist/node/ui/CrmDashboard.js +221 -221
  36. package/dist/node/ui/CrmDealCard.js +5 -5
  37. package/dist/node/ui/CrmPipelineBoard.js +13 -13
  38. package/dist/node/ui/hooks/index.js +2 -2
  39. package/dist/node/ui/hooks/useDealList.js +1 -1
  40. package/dist/node/ui/hooks/useDealMutations.js +1 -1
  41. package/dist/node/ui/index.js +290 -290
  42. package/dist/node/ui/modals/CreateDealModal.js +12 -12
  43. package/dist/node/ui/modals/DealActionsModal.js +21 -21
  44. package/dist/node/ui/modals/index.js +33 -33
  45. package/dist/node/ui/renderers/index.js +116 -116
  46. package/dist/node/ui/renderers/pipeline.renderer.js +97 -97
  47. package/dist/operations/index.d.ts +1 -1
  48. package/dist/ui/CrmDashboard.js +221 -221
  49. package/dist/ui/CrmDealCard.js +5 -5
  50. package/dist/ui/CrmPipelineBoard.js +13 -13
  51. package/dist/ui/hooks/index.d.ts +2 -2
  52. package/dist/ui/hooks/index.js +2 -2
  53. package/dist/ui/hooks/useDealList.js +1 -1
  54. package/dist/ui/hooks/useDealMutations.d.ts +9 -0
  55. package/dist/ui/hooks/useDealMutations.js +1 -1
  56. package/dist/ui/index.d.ts +3 -3
  57. package/dist/ui/index.js +290 -290
  58. package/dist/ui/modals/CreateDealModal.js +12 -12
  59. package/dist/ui/modals/DealActionsModal.js +21 -21
  60. package/dist/ui/modals/index.js +33 -33
  61. package/dist/ui/renderers/index.d.ts +1 -1
  62. package/dist/ui/renderers/index.js +116 -116
  63. package/dist/ui/renderers/pipeline.renderer.d.ts +1 -1
  64. package/dist/ui/renderers/pipeline.renderer.js +97 -97
  65. package/package.json +14 -14
  66. package/src/crm-pipeline.feature.ts +86 -86
  67. package/src/deal/deal.enum.ts +8 -8
  68. package/src/deal/deal.operation.ts +255 -255
  69. package/src/deal/deal.schema.ts +92 -92
  70. package/src/deal/deal.test-spec.ts +48 -48
  71. package/src/deal/index.ts +17 -19
  72. package/src/docs/crm-pipeline.docblock.ts +43 -43
  73. package/src/entities/company.entity.ts +52 -52
  74. package/src/entities/contact.entity.ts +67 -67
  75. package/src/entities/deal.entity.ts +134 -134
  76. package/src/entities/index.ts +27 -27
  77. package/src/entities/task.entity.ts +105 -105
  78. package/src/events/contact.event.ts +22 -22
  79. package/src/events/deal.event.ts +77 -77
  80. package/src/events/task.event.ts +19 -19
  81. package/src/example.ts +32 -32
  82. package/src/handlers/crm.handlers.ts +358 -357
  83. package/src/handlers/deal.handlers.ts +179 -179
  84. package/src/handlers/index.ts +18 -19
  85. package/src/handlers/mock-data.ts +167 -167
  86. package/src/index.ts +11 -11
  87. package/src/operations/index.ts +16 -16
  88. package/src/presentations/dashboard.presentation.ts +45 -45
  89. package/src/presentations/pipeline.presentation.ts +90 -90
  90. package/src/seeders/index.ts +26 -26
  91. package/src/shared/overlay-types.ts +23 -23
  92. package/src/ui/CrmDashboard.tsx +256 -256
  93. package/src/ui/CrmDealCard.tsx +64 -64
  94. package/src/ui/CrmPipelineBoard.tsx +105 -105
  95. package/src/ui/hooks/index.ts +3 -3
  96. package/src/ui/hooks/useDealList.ts +85 -85
  97. package/src/ui/hooks/useDealMutations.ts +151 -150
  98. package/src/ui/index.ts +5 -10
  99. package/src/ui/modals/CreateDealModal.tsx +217 -217
  100. package/src/ui/modals/DealActionsModal.tsx +390 -390
  101. package/src/ui/overlays/demo-overlays.ts +43 -43
  102. package/src/ui/renderers/index.ts +4 -3
  103. package/src/ui/renderers/pipeline.markdown.ts +165 -165
  104. package/src/ui/renderers/pipeline.renderer.tsx +17 -16
  105. package/tsconfig.json +7 -8
  106. package/tsdown.config.js +7 -3
@@ -440,88 +440,6 @@ async function mockGetDealsByStageHandler(input) {
440
440
  async function mockGetPipelineStagesHandler(input) {
441
441
  return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
442
442
  }
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
443
  // src/ui/CrmDealCard.tsx
526
444
  import { jsxDEV } from "react/jsx-dev-runtime";
527
445
  "use client";
@@ -537,7 +455,7 @@ function CrmDealCard({ deal, onClick }) {
537
455
  const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
538
456
  return /* @__PURE__ */ jsxDEV("div", {
539
457
  onClick,
540
- className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
458
+ className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
541
459
  role: "button",
542
460
  tabIndex: 0,
543
461
  onKeyDown: (e) => {
@@ -546,22 +464,22 @@ function CrmDealCard({ deal, onClick }) {
546
464
  },
547
465
  children: [
548
466
  /* @__PURE__ */ jsxDEV("h4", {
549
- className: "leading-snug font-medium",
467
+ className: "font-medium leading-snug",
550
468
  children: deal.name
551
469
  }, undefined, false, undefined, this),
552
470
  /* @__PURE__ */ jsxDEV("div", {
553
- className: "text-primary mt-2 text-lg font-semibold",
471
+ className: "mt-2 font-semibold text-lg text-primary",
554
472
  children: formatCurrency(deal.value, deal.currency)
555
473
  }, undefined, false, undefined, this),
556
474
  /* @__PURE__ */ jsxDEV("div", {
557
- className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
475
+ className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
558
476
  children: [
559
477
  daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
560
478
  className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
561
479
  children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
562
480
  }, undefined, false, undefined, this),
563
481
  /* @__PURE__ */ jsxDEV("span", {
564
- className: `rounded px-1.5 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"}`,
482
+ 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"}`,
565
483
  children: deal.status
566
484
  }, undefined, false, undefined, this)
567
485
  ]
@@ -571,7 +489,7 @@ function CrmDealCard({ deal, onClick }) {
571
489
  }
572
490
 
573
491
  // src/ui/CrmPipelineBoard.tsx
574
- import { useState as useState2 } from "react";
492
+ import { useState } from "react";
575
493
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
576
494
  "use client";
577
495
  function formatCurrency2(value) {
@@ -587,7 +505,7 @@ function CrmPipelineBoard({
587
505
  onDealClick,
588
506
  onDealMove
589
507
  }) {
590
- const [quickMoveOpen, setQuickMoveOpen] = useState2(null);
508
+ const [quickMoveOpen, setQuickMoveOpen] = useState(null);
591
509
  const sortedStages = [...stages].sort((a, b) => a.position - b.position);
592
510
  const handleQuickMove = (dealId, toStageId) => {
593
511
  onDealMove?.(dealId, toStageId);
@@ -599,10 +517,10 @@ function CrmPipelineBoard({
599
517
  const deals = dealsByStage[stage.id] ?? [];
600
518
  const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
601
519
  return /* @__PURE__ */ jsxDEV2("div", {
602
- className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
520
+ className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
603
521
  children: [
604
522
  /* @__PURE__ */ jsxDEV2("div", {
605
- className: "border-border flex items-center justify-between border-b px-3 py-2",
523
+ className: "flex items-center justify-between border-border border-b px-3 py-2",
606
524
  children: [
607
525
  /* @__PURE__ */ jsxDEV2("div", {
608
526
  children: [
@@ -621,7 +539,7 @@ function CrmPipelineBoard({
621
539
  ]
622
540
  }, undefined, true, undefined, this),
623
541
  /* @__PURE__ */ jsxDEV2("span", {
624
- className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
542
+ className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
625
543
  children: deals.length
626
544
  }, undefined, false, undefined, this)
627
545
  ]
@@ -629,7 +547,7 @@ function CrmPipelineBoard({
629
547
  /* @__PURE__ */ jsxDEV2("div", {
630
548
  className: "flex flex-1 flex-col gap-2 p-2",
631
549
  children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
632
- className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
550
+ className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
633
551
  children: "No deals"
634
552
  }, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
635
553
  className: "group relative",
@@ -647,15 +565,15 @@ function CrmPipelineBoard({
647
565
  e.stopPropagation();
648
566
  setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
649
567
  },
650
- className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
568
+ className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
651
569
  title: "Quick move",
652
570
  children: "➡️"
653
571
  }, undefined, false, undefined, this),
654
572
  quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
655
- className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
573
+ className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
656
574
  children: [
657
575
  /* @__PURE__ */ jsxDEV2("p", {
658
- className: "text-muted-foreground px-3 py-1 text-xs font-medium",
576
+ className: "px-3 py-1 font-medium text-muted-foreground text-xs",
659
577
  children: "Move to:"
660
578
  }, undefined, false, undefined, this),
661
579
  sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
@@ -664,7 +582,7 @@ function CrmPipelineBoard({
664
582
  e.stopPropagation();
665
583
  handleQuickMove(deal.id, s.id);
666
584
  },
667
- className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
585
+ className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
668
586
  children: s.name
669
587
  }, s.id, false, undefined, this))
670
588
  ]
@@ -680,27 +598,87 @@ function CrmPipelineBoard({
680
598
  }, undefined, false, undefined, this);
681
599
  }
682
600
 
683
- // src/ui/renderers/pipeline.renderer.tsx
684
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
685
- function CrmPipelineBoardWrapper() {
686
- const { dealsByStage, stages } = useDealList();
687
- return /* @__PURE__ */ jsxDEV3(CrmPipelineBoard, {
601
+ // src/ui/hooks/useDealList.ts
602
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
603
+ import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
604
+ "use client";
605
+ function useDealList(options = {}) {
606
+ const { handlers, projectId } = useTemplateRuntime();
607
+ const { crm: crm2 } = handlers;
608
+ const [data, setData] = useState2(null);
609
+ const [dealsByStage, setDealsByStage] = useState2({});
610
+ const [stages, setStages] = useState2([]);
611
+ const [loading, setLoading] = useState2(true);
612
+ const [error, setError] = useState2(null);
613
+ const [page, setPage] = useState2(1);
614
+ const pipelineId = options.pipelineId ?? "pipeline-1";
615
+ const fetchData = useCallback(async () => {
616
+ setLoading(true);
617
+ setError(null);
618
+ try {
619
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
620
+ crm2.listDeals({
621
+ projectId,
622
+ pipelineId,
623
+ stageId: options.stageId,
624
+ status: options.status === "all" ? undefined : options.status,
625
+ search: options.search,
626
+ limit: options.limit ?? 50,
627
+ offset: (page - 1) * (options.limit ?? 50)
628
+ }),
629
+ crm2.getDealsByStage({ projectId, pipelineId }),
630
+ crm2.getPipelineStages({ pipelineId })
631
+ ]);
632
+ setData(dealsResult);
633
+ setDealsByStage(stageDealsResult);
634
+ setStages(stagesResult);
635
+ } catch (err) {
636
+ setError(err instanceof Error ? err : new Error("Unknown error"));
637
+ } finally {
638
+ setLoading(false);
639
+ }
640
+ }, [
641
+ crm2,
642
+ projectId,
643
+ pipelineId,
644
+ options.stageId,
645
+ options.status,
646
+ options.search,
647
+ options.limit,
648
+ page
649
+ ]);
650
+ useEffect(() => {
651
+ fetchData();
652
+ }, [fetchData]);
653
+ const stats = useMemo(() => {
654
+ if (!data)
655
+ return null;
656
+ const open = data.deals.filter((d) => d.status === "OPEN");
657
+ const won = data.deals.filter((d) => d.status === "WON");
658
+ const lost = data.deals.filter((d) => d.status === "LOST");
659
+ return {
660
+ total: data.total,
661
+ totalValue: data.totalValue,
662
+ openCount: open.length,
663
+ openValue: open.reduce((sum, d) => sum + d.value, 0),
664
+ wonCount: won.length,
665
+ wonValue: won.reduce((sum, d) => sum + d.value, 0),
666
+ lostCount: lost.length
667
+ };
668
+ }, [data]);
669
+ return {
670
+ data,
688
671
  dealsByStage,
689
- stages
690
- }, undefined, false, undefined, this);
672
+ stages,
673
+ loading,
674
+ error,
675
+ stats,
676
+ page,
677
+ refetch: fetchData,
678
+ nextPage: () => setPage((p) => p + 1),
679
+ prevPage: () => page > 1 && setPage((p) => p - 1)
680
+ };
691
681
  }
692
- var crmPipelineReactRenderer = {
693
- target: "react",
694
- render: async (desc, _ctx) => {
695
- if (desc.source.type !== "component") {
696
- throw new Error("Invalid source type");
697
- }
698
- if (desc.source.componentKey !== "CrmPipelineView") {
699
- throw new Error(`Unknown component: ${desc.source.componentKey}`);
700
- }
701
- return /* @__PURE__ */ jsxDEV3(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
702
- }
703
- };
704
682
 
705
683
  // src/ui/renderers/pipeline.markdown.ts
706
684
  function formatCurrency3(value, currency = "USD") {
@@ -820,6 +798,28 @@ var crmDashboardMarkdownRenderer = {
820
798
  };
821
799
  }
822
800
  };
801
+
802
+ // src/ui/renderers/pipeline.renderer.tsx
803
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
804
+ function CrmPipelineBoardWrapper() {
805
+ const { dealsByStage, stages } = useDealList();
806
+ return /* @__PURE__ */ jsxDEV3(CrmPipelineBoard, {
807
+ dealsByStage,
808
+ stages
809
+ }, undefined, false, undefined, this);
810
+ }
811
+ var crmPipelineReactRenderer = {
812
+ target: "react",
813
+ render: async (desc, _ctx) => {
814
+ if (desc.source.type !== "component") {
815
+ throw new Error("Invalid source type");
816
+ }
817
+ if (desc.source.componentKey !== "CrmPipelineView") {
818
+ throw new Error(`Unknown component: ${desc.source.componentKey}`);
819
+ }
820
+ return /* @__PURE__ */ jsxDEV3(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
821
+ }
822
+ };
823
823
  export {
824
824
  crmPipelineReactRenderer,
825
825
  crmPipelineMarkdownRenderer,
@@ -1,85 +1,3 @@
1
- // src/ui/hooks/useDealList.ts
2
- import { useCallback, useEffect, useMemo, useState } from "react";
3
- import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
4
- "use client";
5
- function useDealList(options = {}) {
6
- const { handlers, projectId } = useTemplateRuntime();
7
- const { crm } = handlers;
8
- const [data, setData] = useState(null);
9
- const [dealsByStage, setDealsByStage] = useState({});
10
- const [stages, setStages] = useState([]);
11
- const [loading, setLoading] = useState(true);
12
- const [error, setError] = useState(null);
13
- const [page, setPage] = useState(1);
14
- const pipelineId = options.pipelineId ?? "pipeline-1";
15
- const fetchData = useCallback(async () => {
16
- setLoading(true);
17
- setError(null);
18
- try {
19
- const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
20
- crm.listDeals({
21
- projectId,
22
- pipelineId,
23
- stageId: options.stageId,
24
- status: options.status === "all" ? undefined : options.status,
25
- search: options.search,
26
- limit: options.limit ?? 50,
27
- offset: (page - 1) * (options.limit ?? 50)
28
- }),
29
- crm.getDealsByStage({ projectId, pipelineId }),
30
- crm.getPipelineStages({ pipelineId })
31
- ]);
32
- setData(dealsResult);
33
- setDealsByStage(stageDealsResult);
34
- setStages(stagesResult);
35
- } catch (err) {
36
- setError(err instanceof Error ? err : new Error("Unknown error"));
37
- } finally {
38
- setLoading(false);
39
- }
40
- }, [
41
- crm,
42
- projectId,
43
- pipelineId,
44
- options.stageId,
45
- options.status,
46
- options.search,
47
- options.limit,
48
- page
49
- ]);
50
- useEffect(() => {
51
- fetchData();
52
- }, [fetchData]);
53
- const stats = useMemo(() => {
54
- if (!data)
55
- return null;
56
- const open = data.deals.filter((d) => d.status === "OPEN");
57
- const won = data.deals.filter((d) => d.status === "WON");
58
- const lost = data.deals.filter((d) => d.status === "LOST");
59
- return {
60
- total: data.total,
61
- totalValue: data.totalValue,
62
- openCount: open.length,
63
- openValue: open.reduce((sum, d) => sum + d.value, 0),
64
- wonCount: won.length,
65
- wonValue: won.reduce((sum, d) => sum + d.value, 0),
66
- lostCount: lost.length
67
- };
68
- }, [data]);
69
- return {
70
- data,
71
- dealsByStage,
72
- stages,
73
- loading,
74
- error,
75
- stats,
76
- page,
77
- refetch: fetchData,
78
- nextPage: () => setPage((p) => p + 1),
79
- prevPage: () => page > 1 && setPage((p) => p - 1)
80
- };
81
- }
82
-
83
1
  // src/ui/CrmDealCard.tsx
84
2
  import { jsxDEV } from "react/jsx-dev-runtime";
85
3
  "use client";
@@ -95,7 +13,7 @@ function CrmDealCard({ deal, onClick }) {
95
13
  const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
96
14
  return /* @__PURE__ */ jsxDEV("div", {
97
15
  onClick,
98
- className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
16
+ className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
99
17
  role: "button",
100
18
  tabIndex: 0,
101
19
  onKeyDown: (e) => {
@@ -104,22 +22,22 @@ function CrmDealCard({ deal, onClick }) {
104
22
  },
105
23
  children: [
106
24
  /* @__PURE__ */ jsxDEV("h4", {
107
- className: "leading-snug font-medium",
25
+ className: "font-medium leading-snug",
108
26
  children: deal.name
109
27
  }, undefined, false, undefined, this),
110
28
  /* @__PURE__ */ jsxDEV("div", {
111
- className: "text-primary mt-2 text-lg font-semibold",
29
+ className: "mt-2 font-semibold text-lg text-primary",
112
30
  children: formatCurrency(deal.value, deal.currency)
113
31
  }, undefined, false, undefined, this),
114
32
  /* @__PURE__ */ jsxDEV("div", {
115
- className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
33
+ className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
116
34
  children: [
117
35
  daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
118
36
  className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
119
37
  children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
120
38
  }, undefined, false, undefined, this),
121
39
  /* @__PURE__ */ jsxDEV("span", {
122
- className: `rounded px-1.5 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"}`,
40
+ 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"}`,
123
41
  children: deal.status
124
42
  }, undefined, false, undefined, this)
125
43
  ]
@@ -129,7 +47,7 @@ function CrmDealCard({ deal, onClick }) {
129
47
  }
130
48
 
131
49
  // src/ui/CrmPipelineBoard.tsx
132
- import { useState as useState2 } from "react";
50
+ import { useState } from "react";
133
51
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
134
52
  "use client";
135
53
  function formatCurrency2(value) {
@@ -145,7 +63,7 @@ function CrmPipelineBoard({
145
63
  onDealClick,
146
64
  onDealMove
147
65
  }) {
148
- const [quickMoveOpen, setQuickMoveOpen] = useState2(null);
66
+ const [quickMoveOpen, setQuickMoveOpen] = useState(null);
149
67
  const sortedStages = [...stages].sort((a, b) => a.position - b.position);
150
68
  const handleQuickMove = (dealId, toStageId) => {
151
69
  onDealMove?.(dealId, toStageId);
@@ -157,10 +75,10 @@ function CrmPipelineBoard({
157
75
  const deals = dealsByStage[stage.id] ?? [];
158
76
  const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
159
77
  return /* @__PURE__ */ jsxDEV2("div", {
160
- className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
78
+ className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
161
79
  children: [
162
80
  /* @__PURE__ */ jsxDEV2("div", {
163
- className: "border-border flex items-center justify-between border-b px-3 py-2",
81
+ className: "flex items-center justify-between border-border border-b px-3 py-2",
164
82
  children: [
165
83
  /* @__PURE__ */ jsxDEV2("div", {
166
84
  children: [
@@ -179,7 +97,7 @@ function CrmPipelineBoard({
179
97
  ]
180
98
  }, undefined, true, undefined, this),
181
99
  /* @__PURE__ */ jsxDEV2("span", {
182
- className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
100
+ className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
183
101
  children: deals.length
184
102
  }, undefined, false, undefined, this)
185
103
  ]
@@ -187,7 +105,7 @@ function CrmPipelineBoard({
187
105
  /* @__PURE__ */ jsxDEV2("div", {
188
106
  className: "flex flex-1 flex-col gap-2 p-2",
189
107
  children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
190
- className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
108
+ className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
191
109
  children: "No deals"
192
110
  }, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
193
111
  className: "group relative",
@@ -205,15 +123,15 @@ function CrmPipelineBoard({
205
123
  e.stopPropagation();
206
124
  setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
207
125
  },
208
- className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
126
+ className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
209
127
  title: "Quick move",
210
128
  children: "➡️"
211
129
  }, undefined, false, undefined, this),
212
130
  quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
213
- className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
131
+ className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
214
132
  children: [
215
133
  /* @__PURE__ */ jsxDEV2("p", {
216
- className: "text-muted-foreground px-3 py-1 text-xs font-medium",
134
+ className: "px-3 py-1 font-medium text-muted-foreground text-xs",
217
135
  children: "Move to:"
218
136
  }, undefined, false, undefined, this),
219
137
  sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
@@ -222,7 +140,7 @@ function CrmPipelineBoard({
222
140
  e.stopPropagation();
223
141
  handleQuickMove(deal.id, s.id);
224
142
  },
225
- className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
143
+ className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
226
144
  children: s.name
227
145
  }, s.id, false, undefined, this))
228
146
  ]
@@ -238,6 +156,88 @@ function CrmPipelineBoard({
238
156
  }, undefined, false, undefined, this);
239
157
  }
240
158
 
159
+ // src/ui/hooks/useDealList.ts
160
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
161
+ import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
162
+ "use client";
163
+ function useDealList(options = {}) {
164
+ const { handlers, projectId } = useTemplateRuntime();
165
+ const { crm } = handlers;
166
+ const [data, setData] = useState2(null);
167
+ const [dealsByStage, setDealsByStage] = useState2({});
168
+ const [stages, setStages] = useState2([]);
169
+ const [loading, setLoading] = useState2(true);
170
+ const [error, setError] = useState2(null);
171
+ const [page, setPage] = useState2(1);
172
+ const pipelineId = options.pipelineId ?? "pipeline-1";
173
+ const fetchData = useCallback(async () => {
174
+ setLoading(true);
175
+ setError(null);
176
+ try {
177
+ const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
178
+ crm.listDeals({
179
+ projectId,
180
+ pipelineId,
181
+ stageId: options.stageId,
182
+ status: options.status === "all" ? undefined : options.status,
183
+ search: options.search,
184
+ limit: options.limit ?? 50,
185
+ offset: (page - 1) * (options.limit ?? 50)
186
+ }),
187
+ crm.getDealsByStage({ projectId, pipelineId }),
188
+ crm.getPipelineStages({ pipelineId })
189
+ ]);
190
+ setData(dealsResult);
191
+ setDealsByStage(stageDealsResult);
192
+ setStages(stagesResult);
193
+ } catch (err) {
194
+ setError(err instanceof Error ? err : new Error("Unknown error"));
195
+ } finally {
196
+ setLoading(false);
197
+ }
198
+ }, [
199
+ crm,
200
+ projectId,
201
+ pipelineId,
202
+ options.stageId,
203
+ options.status,
204
+ options.search,
205
+ options.limit,
206
+ page
207
+ ]);
208
+ useEffect(() => {
209
+ fetchData();
210
+ }, [fetchData]);
211
+ const stats = useMemo(() => {
212
+ if (!data)
213
+ return null;
214
+ const open = data.deals.filter((d) => d.status === "OPEN");
215
+ const won = data.deals.filter((d) => d.status === "WON");
216
+ const lost = data.deals.filter((d) => d.status === "LOST");
217
+ return {
218
+ total: data.total,
219
+ totalValue: data.totalValue,
220
+ openCount: open.length,
221
+ openValue: open.reduce((sum, d) => sum + d.value, 0),
222
+ wonCount: won.length,
223
+ wonValue: won.reduce((sum, d) => sum + d.value, 0),
224
+ lostCount: lost.length
225
+ };
226
+ }, [data]);
227
+ return {
228
+ data,
229
+ dealsByStage,
230
+ stages,
231
+ loading,
232
+ error,
233
+ stats,
234
+ page,
235
+ refetch: fetchData,
236
+ nextPage: () => setPage((p) => p + 1),
237
+ prevPage: () => page > 1 && setPage((p) => p - 1)
238
+ };
239
+ }
240
+
241
241
  // src/ui/renderers/pipeline.renderer.tsx
242
242
  import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
243
243
  function CrmPipelineBoardWrapper() {
@@ -2,5 +2,5 @@
2
2
  * Deal domain - Deal management in CRM pipeline.
3
3
  */
4
4
  export { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
5
- export { DealModel, CreateDealInputModel, MoveDealInputModel, DealMovedPayloadModel, WinDealInputModel, DealWonPayloadModel, LoseDealInputModel, DealLostPayloadModel, ListDealsInputModel, ListDealsOutputModel, } from './deal.schema';
6
- export { CreateDealContract, MoveDealContract, WinDealContract, LoseDealContract, ListDealsContract, } from './deal.operation';
5
+ export { CreateDealContract, ListDealsContract, LoseDealContract, MoveDealContract, WinDealContract, } from './deal.operation';
6
+ export { CreateDealInputModel, DealLostPayloadModel, DealModel, DealMovedPayloadModel, DealWonPayloadModel, ListDealsInputModel, ListDealsOutputModel, LoseDealInputModel, MoveDealInputModel, WinDealInputModel, } from './deal.schema';
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/events/contact.event.ts
3
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
4
3
  import { defineEvent } from "@contractspec/lib.contracts-spec";
4
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
5
5
  var ContactCreatedPayload = defineSchemaModel({
6
6
  name: "ContactCreatedPayload",
7
7
  description: "Payload when a contact is created",
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/events/deal.event.ts
3
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
4
3
  import { defineEvent } from "@contractspec/lib.contracts-spec";
4
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
5
5
  var DealCreatedPayload = defineSchemaModel({
6
6
  name: "DealCreatedPayload",
7
7
  description: "Payload when a deal is created",
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/events/contact.event.ts
3
- import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
4
3
  import { defineEvent } from "@contractspec/lib.contracts-spec";
4
+ import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
5
5
  var ContactCreatedPayload = defineSchemaModel({
6
6
  name: "ContactCreatedPayload",
7
7
  description: "Payload when a contact is created",
@@ -29,8 +29,8 @@ var ContactCreatedEvent = defineEvent({
29
29
  });
30
30
 
31
31
  // src/events/deal.event.ts
32
- import { ScalarTypeEnum as ScalarTypeEnum2, defineSchemaModel as defineSchemaModel2 } from "@contractspec/lib.schema";
33
32
  import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
33
+ import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
34
34
  var DealCreatedPayload = defineSchemaModel2({
35
35
  name: "DealCreatedPayload",
36
36
  description: "Payload when a deal is created",
@@ -125,8 +125,8 @@ var DealLostEvent = defineEvent2({
125
125
  });
126
126
 
127
127
  // src/events/task.event.ts
128
- import { ScalarTypeEnum as ScalarTypeEnum3, defineSchemaModel as defineSchemaModel3 } from "@contractspec/lib.schema";
129
128
  import { defineEvent as defineEvent3 } from "@contractspec/lib.contracts-spec";
129
+ import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
130
130
  var TaskCompletedPayload = defineSchemaModel3({
131
131
  name: "TaskCompletedPayload",
132
132
  description: "Payload when a task is completed",