@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/index.js
CHANGED
|
@@ -551,7 +551,7 @@ var crmPipelineDocBlocks = [
|
|
|
551
551
|
- deal.created, stage.moved, task.completed, contact.updated.
|
|
552
552
|
|
|
553
553
|
## Presentations
|
|
554
|
-
- Pipelines/kanban, deal detail, contact/company profiles, task lists.
|
|
554
|
+
- Pipelines/kanban, deal detail, contact/company profiles, task lists, and a server-mode shared table for the deal list.
|
|
555
555
|
|
|
556
556
|
## Notes
|
|
557
557
|
- Stage definitions should be declarative; enforce via spec and regeneration.
|
|
@@ -910,8 +910,8 @@ var crmPipelineSchemaContribution = {
|
|
|
910
910
|
};
|
|
911
911
|
|
|
912
912
|
// src/events/contact.event.ts
|
|
913
|
-
import { ScalarTypeEnum as ScalarTypeEnum2, defineSchemaModel as defineSchemaModel2 } from "@contractspec/lib.schema";
|
|
914
913
|
import { defineEvent } from "@contractspec/lib.contracts-spec";
|
|
914
|
+
import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
|
|
915
915
|
var ContactCreatedPayload = defineSchemaModel2({
|
|
916
916
|
name: "ContactCreatedPayload",
|
|
917
917
|
description: "Payload when a contact is created",
|
|
@@ -939,8 +939,8 @@ var ContactCreatedEvent = defineEvent({
|
|
|
939
939
|
});
|
|
940
940
|
|
|
941
941
|
// src/events/deal.event.ts
|
|
942
|
-
import { ScalarTypeEnum as ScalarTypeEnum3, defineSchemaModel as defineSchemaModel3 } from "@contractspec/lib.schema";
|
|
943
942
|
import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
|
|
943
|
+
import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
|
|
944
944
|
var DealCreatedPayload = defineSchemaModel3({
|
|
945
945
|
name: "DealCreatedPayload",
|
|
946
946
|
description: "Payload when a deal is created",
|
|
@@ -1035,8 +1035,8 @@ var DealLostEvent = defineEvent2({
|
|
|
1035
1035
|
});
|
|
1036
1036
|
|
|
1037
1037
|
// src/events/task.event.ts
|
|
1038
|
-
import { ScalarTypeEnum as ScalarTypeEnum4, defineSchemaModel as defineSchemaModel4 } from "@contractspec/lib.schema";
|
|
1039
1038
|
import { defineEvent as defineEvent3 } from "@contractspec/lib.contracts-spec";
|
|
1039
|
+
import { defineSchemaModel as defineSchemaModel4, ScalarTypeEnum as ScalarTypeEnum4 } from "@contractspec/lib.schema";
|
|
1040
1040
|
var TaskCompletedPayload = defineSchemaModel4({
|
|
1041
1041
|
name: "TaskCompletedPayload",
|
|
1042
1042
|
description: "Payload when a task is completed",
|
|
@@ -1120,6 +1120,13 @@ function rowToDeal(row) {
|
|
|
1120
1120
|
updatedAt: new Date(row.updatedAt)
|
|
1121
1121
|
};
|
|
1122
1122
|
}
|
|
1123
|
+
var DEAL_SORT_COLUMNS = {
|
|
1124
|
+
name: "name",
|
|
1125
|
+
value: "value",
|
|
1126
|
+
status: "status",
|
|
1127
|
+
expectedCloseDate: "expectedCloseDate",
|
|
1128
|
+
updatedAt: "updatedAt"
|
|
1129
|
+
};
|
|
1123
1130
|
function createCrmHandlers(db) {
|
|
1124
1131
|
async function listDeals(input) {
|
|
1125
1132
|
const {
|
|
@@ -1130,7 +1137,9 @@ function createCrmHandlers(db) {
|
|
|
1130
1137
|
ownerId,
|
|
1131
1138
|
search,
|
|
1132
1139
|
limit = 20,
|
|
1133
|
-
offset = 0
|
|
1140
|
+
offset = 0,
|
|
1141
|
+
sortBy = "value",
|
|
1142
|
+
sortDirection = "desc"
|
|
1134
1143
|
} = input;
|
|
1135
1144
|
let whereClause = "WHERE projectId = ?";
|
|
1136
1145
|
const params = [projectId];
|
|
@@ -1158,7 +1167,9 @@ function createCrmHandlers(db) {
|
|
|
1158
1167
|
const total = countResult[0]?.count ?? 0;
|
|
1159
1168
|
const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
|
|
1160
1169
|
const totalValue = valueResult[0]?.total ?? 0;
|
|
1161
|
-
const
|
|
1170
|
+
const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
|
|
1171
|
+
const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
|
|
1172
|
+
const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
|
|
1162
1173
|
return {
|
|
1163
1174
|
deals: dealRows.map(rowToDeal),
|
|
1164
1175
|
total,
|
|
@@ -1690,20 +1701,183 @@ var DealCardPresentation = definePresentation2({
|
|
|
1690
1701
|
flags: ["crm.deals.enabled"]
|
|
1691
1702
|
}
|
|
1692
1703
|
});
|
|
1704
|
+
// src/ui/CrmDealCard.tsx
|
|
1705
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1706
|
+
"use client";
|
|
1707
|
+
function formatCurrency(value, currency) {
|
|
1708
|
+
return new Intl.NumberFormat("en-US", {
|
|
1709
|
+
style: "currency",
|
|
1710
|
+
currency,
|
|
1711
|
+
minimumFractionDigits: 0,
|
|
1712
|
+
maximumFractionDigits: 0
|
|
1713
|
+
}).format(value);
|
|
1714
|
+
}
|
|
1715
|
+
function CrmDealCard({ deal: deal3, onClick }) {
|
|
1716
|
+
const daysUntilClose = deal3.expectedCloseDate ? Math.ceil((deal3.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
1717
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
1718
|
+
onClick,
|
|
1719
|
+
className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
1720
|
+
role: "button",
|
|
1721
|
+
tabIndex: 0,
|
|
1722
|
+
onKeyDown: (e) => {
|
|
1723
|
+
if (e.key === "Enter" || e.key === " ")
|
|
1724
|
+
onClick?.();
|
|
1725
|
+
},
|
|
1726
|
+
children: [
|
|
1727
|
+
/* @__PURE__ */ jsxDEV("h4", {
|
|
1728
|
+
className: "font-medium leading-snug",
|
|
1729
|
+
children: deal3.name
|
|
1730
|
+
}, undefined, false, undefined, this),
|
|
1731
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1732
|
+
className: "mt-2 font-semibold text-lg text-primary",
|
|
1733
|
+
children: formatCurrency(deal3.value, deal3.currency)
|
|
1734
|
+
}, undefined, false, undefined, this),
|
|
1735
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
1736
|
+
className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
|
|
1737
|
+
children: [
|
|
1738
|
+
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
1739
|
+
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
1740
|
+
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
1741
|
+
}, undefined, false, undefined, this),
|
|
1742
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
1743
|
+
className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
|
|
1744
|
+
children: deal3.status
|
|
1745
|
+
}, undefined, false, undefined, this)
|
|
1746
|
+
]
|
|
1747
|
+
}, undefined, true, undefined, this)
|
|
1748
|
+
]
|
|
1749
|
+
}, undefined, true, undefined, this);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// src/ui/CrmPipelineBoard.tsx
|
|
1753
|
+
import { useState } from "react";
|
|
1754
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
1755
|
+
"use client";
|
|
1756
|
+
function formatCurrency2(value) {
|
|
1757
|
+
if (value >= 1e6)
|
|
1758
|
+
return `$${(value / 1e6).toFixed(1)}M`;
|
|
1759
|
+
if (value >= 1000)
|
|
1760
|
+
return `$${(value / 1000).toFixed(0)}K`;
|
|
1761
|
+
return `$${value}`;
|
|
1762
|
+
}
|
|
1763
|
+
function CrmPipelineBoard({
|
|
1764
|
+
dealsByStage,
|
|
1765
|
+
stages,
|
|
1766
|
+
onDealClick,
|
|
1767
|
+
onDealMove
|
|
1768
|
+
}) {
|
|
1769
|
+
const [quickMoveOpen, setQuickMoveOpen] = useState(null);
|
|
1770
|
+
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
1771
|
+
const handleQuickMove = (dealId, toStageId) => {
|
|
1772
|
+
onDealMove?.(dealId, toStageId);
|
|
1773
|
+
setQuickMoveOpen(null);
|
|
1774
|
+
};
|
|
1775
|
+
return /* @__PURE__ */ jsxDEV2("div", {
|
|
1776
|
+
className: "flex gap-4 overflow-x-auto pb-4",
|
|
1777
|
+
children: sortedStages.map((stage) => {
|
|
1778
|
+
const deals = dealsByStage[stage.id] ?? [];
|
|
1779
|
+
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
1780
|
+
return /* @__PURE__ */ jsxDEV2("div", {
|
|
1781
|
+
className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
|
|
1782
|
+
children: [
|
|
1783
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
1784
|
+
className: "flex items-center justify-between border-border border-b px-3 py-2",
|
|
1785
|
+
children: [
|
|
1786
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
1787
|
+
children: [
|
|
1788
|
+
/* @__PURE__ */ jsxDEV2("h3", {
|
|
1789
|
+
className: "font-medium",
|
|
1790
|
+
children: stage.name
|
|
1791
|
+
}, undefined, false, undefined, this),
|
|
1792
|
+
/* @__PURE__ */ jsxDEV2("p", {
|
|
1793
|
+
className: "text-muted-foreground text-xs",
|
|
1794
|
+
children: [
|
|
1795
|
+
deals.length,
|
|
1796
|
+
" deals \xB7 ",
|
|
1797
|
+
formatCurrency2(stageValue)
|
|
1798
|
+
]
|
|
1799
|
+
}, undefined, true, undefined, this)
|
|
1800
|
+
]
|
|
1801
|
+
}, undefined, true, undefined, this),
|
|
1802
|
+
/* @__PURE__ */ jsxDEV2("span", {
|
|
1803
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
|
|
1804
|
+
children: deals.length
|
|
1805
|
+
}, undefined, false, undefined, this)
|
|
1806
|
+
]
|
|
1807
|
+
}, undefined, true, undefined, this),
|
|
1808
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
1809
|
+
className: "flex flex-1 flex-col gap-2 p-2",
|
|
1810
|
+
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
1811
|
+
className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
|
|
1812
|
+
children: "No deals"
|
|
1813
|
+
}, undefined, false, undefined, this) : deals.map((deal3) => /* @__PURE__ */ jsxDEV2("div", {
|
|
1814
|
+
className: "group relative",
|
|
1815
|
+
children: [
|
|
1816
|
+
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
1817
|
+
deal: deal3,
|
|
1818
|
+
onClick: () => onDealClick?.(deal3.id)
|
|
1819
|
+
}, undefined, false, undefined, this),
|
|
1820
|
+
deal3.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
1821
|
+
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
1822
|
+
children: [
|
|
1823
|
+
/* @__PURE__ */ jsxDEV2("button", {
|
|
1824
|
+
type: "button",
|
|
1825
|
+
onClick: (e) => {
|
|
1826
|
+
e.stopPropagation();
|
|
1827
|
+
setQuickMoveOpen(quickMoveOpen === deal3.id ? null : deal3.id);
|
|
1828
|
+
},
|
|
1829
|
+
className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
|
|
1830
|
+
title: "Quick move",
|
|
1831
|
+
children: "\u27A1\uFE0F"
|
|
1832
|
+
}, undefined, false, undefined, this),
|
|
1833
|
+
quickMoveOpen === deal3.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
1834
|
+
className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
|
|
1835
|
+
children: [
|
|
1836
|
+
/* @__PURE__ */ jsxDEV2("p", {
|
|
1837
|
+
className: "px-3 py-1 font-medium text-muted-foreground text-xs",
|
|
1838
|
+
children: "Move to:"
|
|
1839
|
+
}, undefined, false, undefined, this),
|
|
1840
|
+
sortedStages.filter((s) => s.id !== deal3.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
1841
|
+
type: "button",
|
|
1842
|
+
onClick: (e) => {
|
|
1843
|
+
e.stopPropagation();
|
|
1844
|
+
handleQuickMove(deal3.id, s.id);
|
|
1845
|
+
},
|
|
1846
|
+
className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
|
|
1847
|
+
children: s.name
|
|
1848
|
+
}, s.id, false, undefined, this))
|
|
1849
|
+
]
|
|
1850
|
+
}, undefined, true, undefined, this)
|
|
1851
|
+
]
|
|
1852
|
+
}, undefined, true, undefined, this)
|
|
1853
|
+
]
|
|
1854
|
+
}, deal3.id, true, undefined, this))
|
|
1855
|
+
}, undefined, false, undefined, this)
|
|
1856
|
+
]
|
|
1857
|
+
}, stage.id, true, undefined, this);
|
|
1858
|
+
})
|
|
1859
|
+
}, undefined, false, undefined, this);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1693
1862
|
// src/ui/hooks/useDealList.ts
|
|
1694
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
1695
1863
|
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
1864
|
+
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
1696
1865
|
"use client";
|
|
1697
1866
|
function useDealList(options = {}) {
|
|
1698
1867
|
const { handlers, projectId } = useTemplateRuntime();
|
|
1699
1868
|
const { crm: crm2 } = handlers;
|
|
1700
|
-
const [data, setData] =
|
|
1701
|
-
const [dealsByStage, setDealsByStage] =
|
|
1702
|
-
const [stages, setStages] =
|
|
1703
|
-
const [loading, setLoading] =
|
|
1704
|
-
const [error, setError] =
|
|
1705
|
-
const [
|
|
1869
|
+
const [data, setData] = useState2(null);
|
|
1870
|
+
const [dealsByStage, setDealsByStage] = useState2({});
|
|
1871
|
+
const [stages, setStages] = useState2([]);
|
|
1872
|
+
const [loading, setLoading] = useState2(true);
|
|
1873
|
+
const [error, setError] = useState2(null);
|
|
1874
|
+
const [internalPage, setInternalPage] = useState2(0);
|
|
1706
1875
|
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
1876
|
+
const pageIndex = options.pageIndex ?? internalPage;
|
|
1877
|
+
const pageSize = options.pageSize ?? options.limit ?? 50;
|
|
1878
|
+
const [sort] = options.sorting ?? [];
|
|
1879
|
+
const sortBy = sort?.id;
|
|
1880
|
+
const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
|
|
1707
1881
|
const fetchData = useCallback(async () => {
|
|
1708
1882
|
setLoading(true);
|
|
1709
1883
|
setError(null);
|
|
@@ -1715,8 +1889,10 @@ function useDealList(options = {}) {
|
|
|
1715
1889
|
stageId: options.stageId,
|
|
1716
1890
|
status: options.status === "all" ? undefined : options.status,
|
|
1717
1891
|
search: options.search,
|
|
1718
|
-
limit:
|
|
1719
|
-
offset:
|
|
1892
|
+
limit: pageSize,
|
|
1893
|
+
offset: pageIndex * pageSize,
|
|
1894
|
+
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
1895
|
+
sortDirection
|
|
1720
1896
|
}),
|
|
1721
1897
|
crm2.getDealsByStage({ projectId, pipelineId }),
|
|
1722
1898
|
crm2.getPipelineStages({ pipelineId })
|
|
@@ -1736,8 +1912,10 @@ function useDealList(options = {}) {
|
|
|
1736
1912
|
options.stageId,
|
|
1737
1913
|
options.status,
|
|
1738
1914
|
options.search,
|
|
1739
|
-
|
|
1740
|
-
|
|
1915
|
+
pageIndex,
|
|
1916
|
+
pageSize,
|
|
1917
|
+
sortBy,
|
|
1918
|
+
sortDirection
|
|
1741
1919
|
]);
|
|
1742
1920
|
useEffect(() => {
|
|
1743
1921
|
fetchData();
|
|
@@ -1765,35 +1943,37 @@ function useDealList(options = {}) {
|
|
|
1765
1943
|
loading,
|
|
1766
1944
|
error,
|
|
1767
1945
|
stats,
|
|
1768
|
-
page,
|
|
1946
|
+
page: pageIndex + 1,
|
|
1947
|
+
pageIndex,
|
|
1948
|
+
pageSize,
|
|
1769
1949
|
refetch: fetchData,
|
|
1770
|
-
nextPage: () =>
|
|
1771
|
-
prevPage: () =>
|
|
1950
|
+
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
1951
|
+
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
1772
1952
|
};
|
|
1773
1953
|
}
|
|
1774
1954
|
|
|
1775
1955
|
// src/ui/hooks/useDealMutations.ts
|
|
1776
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1777
1956
|
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
1957
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
1778
1958
|
function useDealMutations(options = {}) {
|
|
1779
1959
|
const { handlers, projectId } = useTemplateRuntime2();
|
|
1780
1960
|
const { crm: crm2 } = handlers;
|
|
1781
|
-
const [createState, setCreateState] =
|
|
1961
|
+
const [createState, setCreateState] = useState3({
|
|
1782
1962
|
loading: false,
|
|
1783
1963
|
error: null,
|
|
1784
1964
|
data: null
|
|
1785
1965
|
});
|
|
1786
|
-
const [moveState, setMoveState] =
|
|
1966
|
+
const [moveState, setMoveState] = useState3({
|
|
1787
1967
|
loading: false,
|
|
1788
1968
|
error: null,
|
|
1789
1969
|
data: null
|
|
1790
1970
|
});
|
|
1791
|
-
const [winState, setWinState] =
|
|
1971
|
+
const [winState, setWinState] = useState3({
|
|
1792
1972
|
loading: false,
|
|
1793
1973
|
error: null,
|
|
1794
1974
|
data: null
|
|
1795
1975
|
});
|
|
1796
|
-
const [loseState, setLoseState] =
|
|
1976
|
+
const [loseState, setLoseState] = useState3({
|
|
1797
1977
|
loading: false,
|
|
1798
1978
|
error: null,
|
|
1799
1979
|
data: null
|
|
@@ -1857,180 +2037,22 @@ function useDealMutations(options = {}) {
|
|
|
1857
2037
|
return null;
|
|
1858
2038
|
}
|
|
1859
2039
|
}, [crm2, options]);
|
|
1860
|
-
return {
|
|
1861
|
-
createDeal,
|
|
1862
|
-
moveDeal,
|
|
1863
|
-
winDeal,
|
|
1864
|
-
loseDeal,
|
|
1865
|
-
createState,
|
|
1866
|
-
moveState,
|
|
1867
|
-
winState,
|
|
1868
|
-
loseState,
|
|
1869
|
-
isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
// src/ui/CrmDealCard.tsx
|
|
1874
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1875
|
-
"use client";
|
|
1876
|
-
function formatCurrency(value, currency) {
|
|
1877
|
-
return new Intl.NumberFormat("en-US", {
|
|
1878
|
-
style: "currency",
|
|
1879
|
-
currency,
|
|
1880
|
-
minimumFractionDigits: 0,
|
|
1881
|
-
maximumFractionDigits: 0
|
|
1882
|
-
}).format(value);
|
|
1883
|
-
}
|
|
1884
|
-
function CrmDealCard({ deal: deal3, onClick }) {
|
|
1885
|
-
const daysUntilClose = deal3.expectedCloseDate ? Math.ceil((deal3.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
1886
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
1887
|
-
onClick,
|
|
1888
|
-
className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
1889
|
-
role: "button",
|
|
1890
|
-
tabIndex: 0,
|
|
1891
|
-
onKeyDown: (e) => {
|
|
1892
|
-
if (e.key === "Enter" || e.key === " ")
|
|
1893
|
-
onClick?.();
|
|
1894
|
-
},
|
|
1895
|
-
children: [
|
|
1896
|
-
/* @__PURE__ */ jsxDEV("h4", {
|
|
1897
|
-
className: "leading-snug font-medium",
|
|
1898
|
-
children: deal3.name
|
|
1899
|
-
}, undefined, false, undefined, this),
|
|
1900
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
1901
|
-
className: "text-primary mt-2 text-lg font-semibold",
|
|
1902
|
-
children: formatCurrency(deal3.value, deal3.currency)
|
|
1903
|
-
}, undefined, false, undefined, this),
|
|
1904
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
1905
|
-
className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
|
|
1906
|
-
children: [
|
|
1907
|
-
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
1908
|
-
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
1909
|
-
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
1910
|
-
}, undefined, false, undefined, this),
|
|
1911
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
1912
|
-
className: `rounded px-1.5 py-0.5 text-xs font-medium ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
|
|
1913
|
-
children: deal3.status
|
|
1914
|
-
}, undefined, false, undefined, this)
|
|
1915
|
-
]
|
|
1916
|
-
}, undefined, true, undefined, this)
|
|
1917
|
-
]
|
|
1918
|
-
}, undefined, true, undefined, this);
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
// src/ui/CrmPipelineBoard.tsx
|
|
1922
|
-
import { useState as useState3 } from "react";
|
|
1923
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
1924
|
-
"use client";
|
|
1925
|
-
function formatCurrency2(value) {
|
|
1926
|
-
if (value >= 1e6)
|
|
1927
|
-
return `$${(value / 1e6).toFixed(1)}M`;
|
|
1928
|
-
if (value >= 1000)
|
|
1929
|
-
return `$${(value / 1000).toFixed(0)}K`;
|
|
1930
|
-
return `$${value}`;
|
|
1931
|
-
}
|
|
1932
|
-
function CrmPipelineBoard({
|
|
1933
|
-
dealsByStage,
|
|
1934
|
-
stages,
|
|
1935
|
-
onDealClick,
|
|
1936
|
-
onDealMove
|
|
1937
|
-
}) {
|
|
1938
|
-
const [quickMoveOpen, setQuickMoveOpen] = useState3(null);
|
|
1939
|
-
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
1940
|
-
const handleQuickMove = (dealId, toStageId) => {
|
|
1941
|
-
onDealMove?.(dealId, toStageId);
|
|
1942
|
-
setQuickMoveOpen(null);
|
|
1943
|
-
};
|
|
1944
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
1945
|
-
className: "flex gap-4 overflow-x-auto pb-4",
|
|
1946
|
-
children: sortedStages.map((stage) => {
|
|
1947
|
-
const deals = dealsByStage[stage.id] ?? [];
|
|
1948
|
-
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
1949
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
1950
|
-
className: "bg-muted/30 flex w-72 flex-shrink-0 flex-col rounded-lg",
|
|
1951
|
-
children: [
|
|
1952
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
1953
|
-
className: "border-border flex items-center justify-between border-b px-3 py-2",
|
|
1954
|
-
children: [
|
|
1955
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
1956
|
-
children: [
|
|
1957
|
-
/* @__PURE__ */ jsxDEV2("h3", {
|
|
1958
|
-
className: "font-medium",
|
|
1959
|
-
children: stage.name
|
|
1960
|
-
}, undefined, false, undefined, this),
|
|
1961
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
1962
|
-
className: "text-muted-foreground text-xs",
|
|
1963
|
-
children: [
|
|
1964
|
-
deals.length,
|
|
1965
|
-
" deals \xB7 ",
|
|
1966
|
-
formatCurrency2(stageValue)
|
|
1967
|
-
]
|
|
1968
|
-
}, undefined, true, undefined, this)
|
|
1969
|
-
]
|
|
1970
|
-
}, undefined, true, undefined, this),
|
|
1971
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
1972
|
-
className: "bg-muted flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium",
|
|
1973
|
-
children: deals.length
|
|
1974
|
-
}, undefined, false, undefined, this)
|
|
1975
|
-
]
|
|
1976
|
-
}, undefined, true, undefined, this),
|
|
1977
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
1978
|
-
className: "flex flex-1 flex-col gap-2 p-2",
|
|
1979
|
-
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
1980
|
-
className: "border-muted-foreground/20 text-muted-foreground flex h-24 items-center justify-center rounded-md border-2 border-dashed text-xs",
|
|
1981
|
-
children: "No deals"
|
|
1982
|
-
}, undefined, false, undefined, this) : deals.map((deal3) => /* @__PURE__ */ jsxDEV2("div", {
|
|
1983
|
-
className: "group relative",
|
|
1984
|
-
children: [
|
|
1985
|
-
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
1986
|
-
deal: deal3,
|
|
1987
|
-
onClick: () => onDealClick?.(deal3.id)
|
|
1988
|
-
}, undefined, false, undefined, this),
|
|
1989
|
-
deal3.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
1990
|
-
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
1991
|
-
children: [
|
|
1992
|
-
/* @__PURE__ */ jsxDEV2("button", {
|
|
1993
|
-
type: "button",
|
|
1994
|
-
onClick: (e) => {
|
|
1995
|
-
e.stopPropagation();
|
|
1996
|
-
setQuickMoveOpen(quickMoveOpen === deal3.id ? null : deal3.id);
|
|
1997
|
-
},
|
|
1998
|
-
className: "bg-background border-border hover:bg-muted flex h-6 w-6 items-center justify-center rounded border text-xs shadow-sm",
|
|
1999
|
-
title: "Quick move",
|
|
2000
|
-
children: "\u27A1\uFE0F"
|
|
2001
|
-
}, undefined, false, undefined, this),
|
|
2002
|
-
quickMoveOpen === deal3.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
2003
|
-
className: "bg-card border-border absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border py-1 shadow-lg",
|
|
2004
|
-
children: [
|
|
2005
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
2006
|
-
className: "text-muted-foreground px-3 py-1 text-xs font-medium",
|
|
2007
|
-
children: "Move to:"
|
|
2008
|
-
}, undefined, false, undefined, this),
|
|
2009
|
-
sortedStages.filter((s) => s.id !== deal3.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
2010
|
-
type: "button",
|
|
2011
|
-
onClick: (e) => {
|
|
2012
|
-
e.stopPropagation();
|
|
2013
|
-
handleQuickMove(deal3.id, s.id);
|
|
2014
|
-
},
|
|
2015
|
-
className: "hover:bg-muted w-full px-3 py-1.5 text-left text-sm",
|
|
2016
|
-
children: s.name
|
|
2017
|
-
}, s.id, false, undefined, this))
|
|
2018
|
-
]
|
|
2019
|
-
}, undefined, true, undefined, this)
|
|
2020
|
-
]
|
|
2021
|
-
}, undefined, true, undefined, this)
|
|
2022
|
-
]
|
|
2023
|
-
}, deal3.id, true, undefined, this))
|
|
2024
|
-
}, undefined, false, undefined, this)
|
|
2025
|
-
]
|
|
2026
|
-
}, stage.id, true, undefined, this);
|
|
2027
|
-
})
|
|
2028
|
-
}, undefined, false, undefined, this);
|
|
2040
|
+
return {
|
|
2041
|
+
createDeal,
|
|
2042
|
+
moveDeal,
|
|
2043
|
+
winDeal,
|
|
2044
|
+
loseDeal,
|
|
2045
|
+
createState,
|
|
2046
|
+
moveState,
|
|
2047
|
+
winState,
|
|
2048
|
+
loseState,
|
|
2049
|
+
isLoading: createState.loading || moveState.loading || winState.loading || loseState.loading
|
|
2050
|
+
};
|
|
2029
2051
|
}
|
|
2030
2052
|
|
|
2031
2053
|
// src/ui/modals/CreateDealModal.tsx
|
|
2032
|
-
import { useState as useState4 } from "react";
|
|
2033
2054
|
import { Button, Input } from "@contractspec/lib.design-system";
|
|
2055
|
+
import { useState as useState4 } from "react";
|
|
2034
2056
|
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
2035
2057
|
"use client";
|
|
2036
2058
|
var CURRENCIES = ["USD", "EUR", "GBP", "CAD"];
|
|
@@ -2089,7 +2111,7 @@ function CreateDealModal({
|
|
|
2089
2111
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
2090
2112
|
children: [
|
|
2091
2113
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
2092
|
-
className: "bg-background/80
|
|
2114
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2093
2115
|
onClick: onClose,
|
|
2094
2116
|
role: "button",
|
|
2095
2117
|
tabIndex: 0,
|
|
@@ -2100,10 +2122,10 @@ function CreateDealModal({
|
|
|
2100
2122
|
"aria-label": "Close modal"
|
|
2101
2123
|
}, undefined, false, undefined, this),
|
|
2102
2124
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
2103
|
-
className: "
|
|
2125
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2104
2126
|
children: [
|
|
2105
2127
|
/* @__PURE__ */ jsxDEV3("h2", {
|
|
2106
|
-
className: "mb-4 text-xl
|
|
2128
|
+
className: "mb-4 font-semibold text-xl",
|
|
2107
2129
|
children: "Create New Deal"
|
|
2108
2130
|
}, undefined, false, undefined, this),
|
|
2109
2131
|
/* @__PURE__ */ jsxDEV3("form", {
|
|
@@ -2114,7 +2136,7 @@ function CreateDealModal({
|
|
|
2114
2136
|
children: [
|
|
2115
2137
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
2116
2138
|
htmlFor: "deal-name",
|
|
2117
|
-
className: "
|
|
2139
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2118
2140
|
children: "Deal Name *"
|
|
2119
2141
|
}, undefined, false, undefined, this),
|
|
2120
2142
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -2134,7 +2156,7 @@ function CreateDealModal({
|
|
|
2134
2156
|
children: [
|
|
2135
2157
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
2136
2158
|
htmlFor: "deal-value",
|
|
2137
|
-
className: "
|
|
2159
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2138
2160
|
children: "Value *"
|
|
2139
2161
|
}, undefined, false, undefined, this),
|
|
2140
2162
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -2154,7 +2176,7 @@ function CreateDealModal({
|
|
|
2154
2176
|
children: [
|
|
2155
2177
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
2156
2178
|
htmlFor: "deal-currency",
|
|
2157
|
-
className: "
|
|
2179
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2158
2180
|
children: "Currency"
|
|
2159
2181
|
}, undefined, false, undefined, this),
|
|
2160
2182
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -2162,7 +2184,7 @@ function CreateDealModal({
|
|
|
2162
2184
|
value: currency,
|
|
2163
2185
|
onChange: (e) => setCurrency(e.target.value),
|
|
2164
2186
|
disabled: isLoading,
|
|
2165
|
-
className: "
|
|
2187
|
+
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",
|
|
2166
2188
|
children: CURRENCIES.map((c) => /* @__PURE__ */ jsxDEV3("option", {
|
|
2167
2189
|
value: c,
|
|
2168
2190
|
children: c
|
|
@@ -2176,7 +2198,7 @@ function CreateDealModal({
|
|
|
2176
2198
|
children: [
|
|
2177
2199
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
2178
2200
|
htmlFor: "deal-stage",
|
|
2179
|
-
className: "
|
|
2201
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2180
2202
|
children: "Pipeline Stage *"
|
|
2181
2203
|
}, undefined, false, undefined, this),
|
|
2182
2204
|
/* @__PURE__ */ jsxDEV3("select", {
|
|
@@ -2184,7 +2206,7 @@ function CreateDealModal({
|
|
|
2184
2206
|
value: stageId,
|
|
2185
2207
|
onChange: (e) => setStageId(e.target.value),
|
|
2186
2208
|
disabled: isLoading,
|
|
2187
|
-
className: "
|
|
2209
|
+
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",
|
|
2188
2210
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV3("option", {
|
|
2189
2211
|
value: stage.id,
|
|
2190
2212
|
children: stage.name
|
|
@@ -2196,7 +2218,7 @@ function CreateDealModal({
|
|
|
2196
2218
|
children: [
|
|
2197
2219
|
/* @__PURE__ */ jsxDEV3("label", {
|
|
2198
2220
|
htmlFor: "deal-close-date",
|
|
2199
|
-
className: "
|
|
2221
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2200
2222
|
children: "Expected Close Date"
|
|
2201
2223
|
}, undefined, false, undefined, this),
|
|
2202
2224
|
/* @__PURE__ */ jsxDEV3(Input, {
|
|
@@ -2209,7 +2231,7 @@ function CreateDealModal({
|
|
|
2209
2231
|
]
|
|
2210
2232
|
}, undefined, true, undefined, this),
|
|
2211
2233
|
error && /* @__PURE__ */ jsxDEV3("div", {
|
|
2212
|
-
className: "bg-destructive/10
|
|
2234
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2213
2235
|
children: error
|
|
2214
2236
|
}, undefined, false, undefined, this),
|
|
2215
2237
|
/* @__PURE__ */ jsxDEV3("div", {
|
|
@@ -2238,8 +2260,8 @@ function CreateDealModal({
|
|
|
2238
2260
|
}
|
|
2239
2261
|
|
|
2240
2262
|
// src/ui/modals/DealActionsModal.tsx
|
|
2241
|
-
import { useState as useState5 } from "react";
|
|
2242
2263
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
2264
|
+
import { useState as useState5 } from "react";
|
|
2243
2265
|
import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
|
|
2244
2266
|
"use client";
|
|
2245
2267
|
function formatCurrency3(value, currency) {
|
|
@@ -2340,7 +2362,7 @@ function DealActionsModal({
|
|
|
2340
2362
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
2341
2363
|
children: [
|
|
2342
2364
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
2343
|
-
className: "bg-background/80
|
|
2365
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2344
2366
|
onClick: handleClose,
|
|
2345
2367
|
role: "button",
|
|
2346
2368
|
tabIndex: 0,
|
|
@@ -2351,21 +2373,21 @@ function DealActionsModal({
|
|
|
2351
2373
|
"aria-label": "Close modal"
|
|
2352
2374
|
}, undefined, false, undefined, this),
|
|
2353
2375
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
2354
|
-
className: "
|
|
2376
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2355
2377
|
children: [
|
|
2356
2378
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
2357
|
-
className: "
|
|
2379
|
+
className: "mb-4 border-border border-b pb-4",
|
|
2358
2380
|
children: [
|
|
2359
2381
|
/* @__PURE__ */ jsxDEV4("h2", {
|
|
2360
|
-
className: "text-xl
|
|
2382
|
+
className: "font-semibold text-xl",
|
|
2361
2383
|
children: deal3.name
|
|
2362
2384
|
}, undefined, false, undefined, this),
|
|
2363
2385
|
/* @__PURE__ */ jsxDEV4("p", {
|
|
2364
|
-
className: "
|
|
2386
|
+
className: "font-medium text-lg text-primary",
|
|
2365
2387
|
children: formatCurrency3(deal3.value, deal3.currency)
|
|
2366
2388
|
}, undefined, false, undefined, this),
|
|
2367
2389
|
/* @__PURE__ */ jsxDEV4("span", {
|
|
2368
|
-
className: `mt-2 inline-flex rounded-full px-2 py-0.5 text-xs
|
|
2390
|
+
className: `mt-2 inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
|
|
2369
2391
|
children: deal3.status
|
|
2370
2392
|
}, undefined, false, undefined, this)
|
|
2371
2393
|
]
|
|
@@ -2417,7 +2439,7 @@ function DealActionsModal({
|
|
|
2417
2439
|
]
|
|
2418
2440
|
}, undefined, true, undefined, this),
|
|
2419
2441
|
deal3.status !== "OPEN" && /* @__PURE__ */ jsxDEV4("p", {
|
|
2420
|
-
className: "
|
|
2442
|
+
className: "py-4 text-center text-muted-foreground",
|
|
2421
2443
|
children: [
|
|
2422
2444
|
"This deal is already ",
|
|
2423
2445
|
deal3.status.toLowerCase(),
|
|
@@ -2442,14 +2464,14 @@ function DealActionsModal({
|
|
|
2442
2464
|
children: [
|
|
2443
2465
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
2444
2466
|
htmlFor: "won-source",
|
|
2445
|
-
className: "
|
|
2467
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2446
2468
|
children: "How did you win this deal?"
|
|
2447
2469
|
}, undefined, false, undefined, this),
|
|
2448
2470
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
2449
2471
|
id: "won-source",
|
|
2450
2472
|
value: wonSource,
|
|
2451
2473
|
onChange: (e) => setWonSource(e.target.value),
|
|
2452
|
-
className: "
|
|
2474
|
+
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",
|
|
2453
2475
|
children: [
|
|
2454
2476
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
2455
2477
|
value: "",
|
|
@@ -2483,7 +2505,7 @@ function DealActionsModal({
|
|
|
2483
2505
|
children: [
|
|
2484
2506
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
2485
2507
|
htmlFor: "win-notes",
|
|
2486
|
-
className: "
|
|
2508
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2487
2509
|
children: "Notes (optional)"
|
|
2488
2510
|
}, undefined, false, undefined, this),
|
|
2489
2511
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -2492,12 +2514,12 @@ function DealActionsModal({
|
|
|
2492
2514
|
onChange: (e) => setNotes(e.target.value),
|
|
2493
2515
|
placeholder: "Any additional notes about the win...",
|
|
2494
2516
|
rows: 3,
|
|
2495
|
-
className: "
|
|
2517
|
+
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"
|
|
2496
2518
|
}, undefined, false, undefined, this)
|
|
2497
2519
|
]
|
|
2498
2520
|
}, undefined, true, undefined, this),
|
|
2499
2521
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
2500
|
-
className: "bg-destructive/10
|
|
2522
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2501
2523
|
children: error
|
|
2502
2524
|
}, undefined, false, undefined, this),
|
|
2503
2525
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -2525,14 +2547,14 @@ function DealActionsModal({
|
|
|
2525
2547
|
children: [
|
|
2526
2548
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
2527
2549
|
htmlFor: "lost-reason",
|
|
2528
|
-
className: "
|
|
2550
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2529
2551
|
children: "Why was this deal lost? *"
|
|
2530
2552
|
}, undefined, false, undefined, this),
|
|
2531
2553
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
2532
2554
|
id: "lost-reason",
|
|
2533
2555
|
value: lostReason,
|
|
2534
2556
|
onChange: (e) => setLostReason(e.target.value),
|
|
2535
|
-
className: "
|
|
2557
|
+
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",
|
|
2536
2558
|
children: [
|
|
2537
2559
|
/* @__PURE__ */ jsxDEV4("option", {
|
|
2538
2560
|
value: "",
|
|
@@ -2574,7 +2596,7 @@ function DealActionsModal({
|
|
|
2574
2596
|
children: [
|
|
2575
2597
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
2576
2598
|
htmlFor: "lose-notes",
|
|
2577
|
-
className: "
|
|
2599
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2578
2600
|
children: "Notes (optional)"
|
|
2579
2601
|
}, undefined, false, undefined, this),
|
|
2580
2602
|
/* @__PURE__ */ jsxDEV4("textarea", {
|
|
@@ -2583,12 +2605,12 @@ function DealActionsModal({
|
|
|
2583
2605
|
onChange: (e) => setNotes(e.target.value),
|
|
2584
2606
|
placeholder: "Any additional details...",
|
|
2585
2607
|
rows: 3,
|
|
2586
|
-
className: "
|
|
2608
|
+
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"
|
|
2587
2609
|
}, undefined, false, undefined, this)
|
|
2588
2610
|
]
|
|
2589
2611
|
}, undefined, true, undefined, this),
|
|
2590
2612
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
2591
|
-
className: "bg-destructive/10
|
|
2613
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2592
2614
|
children: error
|
|
2593
2615
|
}, undefined, false, undefined, this),
|
|
2594
2616
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -2617,14 +2639,14 @@ function DealActionsModal({
|
|
|
2617
2639
|
children: [
|
|
2618
2640
|
/* @__PURE__ */ jsxDEV4("label", {
|
|
2619
2641
|
htmlFor: "move-stage",
|
|
2620
|
-
className: "
|
|
2642
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2621
2643
|
children: "Move to Stage"
|
|
2622
2644
|
}, undefined, false, undefined, this),
|
|
2623
2645
|
/* @__PURE__ */ jsxDEV4("select", {
|
|
2624
2646
|
id: "move-stage",
|
|
2625
2647
|
value: selectedStageId,
|
|
2626
2648
|
onChange: (e) => setSelectedStageId(e.target.value),
|
|
2627
|
-
className: "
|
|
2649
|
+
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",
|
|
2628
2650
|
children: stages.map((stage) => /* @__PURE__ */ jsxDEV4("option", {
|
|
2629
2651
|
value: stage.id,
|
|
2630
2652
|
children: [
|
|
@@ -2636,7 +2658,7 @@ function DealActionsModal({
|
|
|
2636
2658
|
]
|
|
2637
2659
|
}, undefined, true, undefined, this),
|
|
2638
2660
|
error && /* @__PURE__ */ jsxDEV4("div", {
|
|
2639
|
-
className: "bg-destructive/10
|
|
2661
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2640
2662
|
children: error
|
|
2641
2663
|
}, undefined, false, undefined, this),
|
|
2642
2664
|
/* @__PURE__ */ jsxDEV4("div", {
|
|
@@ -2663,12 +2685,305 @@ function DealActionsModal({
|
|
|
2663
2685
|
}, undefined, true, undefined, this);
|
|
2664
2686
|
}
|
|
2665
2687
|
|
|
2666
|
-
// src/ui/
|
|
2667
|
-
import { useCallback as useCallback3, useState as useState6 } from "react";
|
|
2688
|
+
// src/ui/tables/DealListTab.tsx
|
|
2668
2689
|
import {
|
|
2669
2690
|
Button as Button3,
|
|
2691
|
+
DataTable,
|
|
2692
|
+
LoaderBlock
|
|
2693
|
+
} from "@contractspec/lib.design-system";
|
|
2694
|
+
import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
|
|
2695
|
+
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
2696
|
+
import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
|
|
2697
|
+
import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
|
|
2698
|
+
import * as React from "react";
|
|
2699
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
2700
|
+
"use client";
|
|
2701
|
+
function formatCurrency4(value, currency = "USD") {
|
|
2702
|
+
return new Intl.NumberFormat("en-US", {
|
|
2703
|
+
style: "currency",
|
|
2704
|
+
currency,
|
|
2705
|
+
minimumFractionDigits: 0,
|
|
2706
|
+
maximumFractionDigits: 0
|
|
2707
|
+
}).format(value);
|
|
2708
|
+
}
|
|
2709
|
+
function statusVariant(status) {
|
|
2710
|
+
switch (status) {
|
|
2711
|
+
case "WON":
|
|
2712
|
+
return "default";
|
|
2713
|
+
case "LOST":
|
|
2714
|
+
return "destructive";
|
|
2715
|
+
case "STALE":
|
|
2716
|
+
return "outline";
|
|
2717
|
+
default:
|
|
2718
|
+
return "secondary";
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
function DealListDataTable({
|
|
2722
|
+
deals,
|
|
2723
|
+
totalItems,
|
|
2724
|
+
pageIndex,
|
|
2725
|
+
pageSize,
|
|
2726
|
+
sorting,
|
|
2727
|
+
loading,
|
|
2728
|
+
onSortingChange,
|
|
2729
|
+
onPaginationChange,
|
|
2730
|
+
onDealClick
|
|
2731
|
+
}) {
|
|
2732
|
+
const controller = useContractTable({
|
|
2733
|
+
data: deals,
|
|
2734
|
+
columns: [
|
|
2735
|
+
{
|
|
2736
|
+
id: "deal",
|
|
2737
|
+
header: "Deal",
|
|
2738
|
+
label: "Deal",
|
|
2739
|
+
accessor: (deal3) => deal3.name,
|
|
2740
|
+
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
2741
|
+
gap: "xs",
|
|
2742
|
+
children: [
|
|
2743
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2744
|
+
className: "font-medium text-sm",
|
|
2745
|
+
children: item.name
|
|
2746
|
+
}, undefined, false, undefined, this),
|
|
2747
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2748
|
+
className: "text-muted-foreground text-xs",
|
|
2749
|
+
children: item.companyId ?? "Unassigned company"
|
|
2750
|
+
}, undefined, false, undefined, this)
|
|
2751
|
+
]
|
|
2752
|
+
}, undefined, true, undefined, this),
|
|
2753
|
+
size: 240,
|
|
2754
|
+
minSize: 180,
|
|
2755
|
+
canSort: true,
|
|
2756
|
+
canPin: true,
|
|
2757
|
+
canResize: true
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
id: "value",
|
|
2761
|
+
header: "Value",
|
|
2762
|
+
label: "Value",
|
|
2763
|
+
accessorKey: "value",
|
|
2764
|
+
cell: ({ item }) => formatCurrency4(item.value, item.currency),
|
|
2765
|
+
align: "right",
|
|
2766
|
+
size: 140,
|
|
2767
|
+
canSort: true,
|
|
2768
|
+
canResize: true
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
id: "status",
|
|
2772
|
+
header: "Status",
|
|
2773
|
+
label: "Status",
|
|
2774
|
+
accessorKey: "status",
|
|
2775
|
+
cell: ({ value }) => /* @__PURE__ */ jsxDEV5(Badge, {
|
|
2776
|
+
variant: statusVariant(value),
|
|
2777
|
+
children: String(value)
|
|
2778
|
+
}, undefined, false, undefined, this),
|
|
2779
|
+
size: 130,
|
|
2780
|
+
canSort: true,
|
|
2781
|
+
canHide: true,
|
|
2782
|
+
canPin: true,
|
|
2783
|
+
canResize: true
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
id: "expectedCloseDate",
|
|
2787
|
+
header: "Expected Close",
|
|
2788
|
+
label: "Expected Close",
|
|
2789
|
+
accessor: (deal3) => deal3.expectedCloseDate?.toISOString() ?? "",
|
|
2790
|
+
cell: ({ item }) => item.expectedCloseDate?.toLocaleDateString() ?? "Not scheduled",
|
|
2791
|
+
size: 170,
|
|
2792
|
+
canSort: true,
|
|
2793
|
+
canHide: true,
|
|
2794
|
+
canResize: true
|
|
2795
|
+
},
|
|
2796
|
+
{
|
|
2797
|
+
id: "updatedAt",
|
|
2798
|
+
header: "Updated",
|
|
2799
|
+
label: "Updated",
|
|
2800
|
+
accessor: (deal3) => deal3.updatedAt.toISOString(),
|
|
2801
|
+
cell: ({ item }) => item.updatedAt.toLocaleDateString(),
|
|
2802
|
+
size: 140,
|
|
2803
|
+
canSort: true,
|
|
2804
|
+
canHide: true,
|
|
2805
|
+
canResize: true
|
|
2806
|
+
},
|
|
2807
|
+
{
|
|
2808
|
+
id: "actions",
|
|
2809
|
+
header: "Actions",
|
|
2810
|
+
label: "Actions",
|
|
2811
|
+
accessor: (deal3) => deal3.id,
|
|
2812
|
+
cell: ({ item }) => /* @__PURE__ */ jsxDEV5(Button3, {
|
|
2813
|
+
variant: "ghost",
|
|
2814
|
+
size: "sm",
|
|
2815
|
+
onPress: () => onDealClick?.(item.id),
|
|
2816
|
+
children: "Actions"
|
|
2817
|
+
}, undefined, false, undefined, this),
|
|
2818
|
+
size: 120,
|
|
2819
|
+
canSort: false,
|
|
2820
|
+
canHide: false,
|
|
2821
|
+
canPin: false,
|
|
2822
|
+
canResize: false
|
|
2823
|
+
}
|
|
2824
|
+
],
|
|
2825
|
+
executionMode: "server",
|
|
2826
|
+
selectionMode: "multiple",
|
|
2827
|
+
totalItems,
|
|
2828
|
+
state: {
|
|
2829
|
+
sorting,
|
|
2830
|
+
pagination: {
|
|
2831
|
+
pageIndex,
|
|
2832
|
+
pageSize
|
|
2833
|
+
}
|
|
2834
|
+
},
|
|
2835
|
+
onSortingChange,
|
|
2836
|
+
onPaginationChange,
|
|
2837
|
+
initialState: {
|
|
2838
|
+
columnVisibility: { updatedAt: false },
|
|
2839
|
+
columnPinning: { left: ["deal", "status"], right: [] }
|
|
2840
|
+
},
|
|
2841
|
+
renderExpandedContent: (deal3) => /* @__PURE__ */ jsxDEV5(VStack, {
|
|
2842
|
+
gap: "sm",
|
|
2843
|
+
className: "py-2",
|
|
2844
|
+
children: [
|
|
2845
|
+
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
2846
|
+
justify: "between",
|
|
2847
|
+
children: [
|
|
2848
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2849
|
+
className: "font-medium text-sm",
|
|
2850
|
+
children: "Owner"
|
|
2851
|
+
}, undefined, false, undefined, this),
|
|
2852
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2853
|
+
className: "text-muted-foreground text-sm",
|
|
2854
|
+
children: deal3.ownerId
|
|
2855
|
+
}, undefined, false, undefined, this)
|
|
2856
|
+
]
|
|
2857
|
+
}, undefined, true, undefined, this),
|
|
2858
|
+
/* @__PURE__ */ jsxDEV5(HStack, {
|
|
2859
|
+
justify: "between",
|
|
2860
|
+
children: [
|
|
2861
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2862
|
+
className: "font-medium text-sm",
|
|
2863
|
+
children: "Contact"
|
|
2864
|
+
}, undefined, false, undefined, this),
|
|
2865
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2866
|
+
className: "text-muted-foreground text-sm",
|
|
2867
|
+
children: deal3.contactId ?? "No linked contact"
|
|
2868
|
+
}, undefined, false, undefined, this)
|
|
2869
|
+
]
|
|
2870
|
+
}, undefined, true, undefined, this),
|
|
2871
|
+
deal3.wonSource ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
2872
|
+
justify: "between",
|
|
2873
|
+
children: [
|
|
2874
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2875
|
+
className: "font-medium text-sm",
|
|
2876
|
+
children: "Won Source"
|
|
2877
|
+
}, undefined, false, undefined, this),
|
|
2878
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2879
|
+
className: "text-muted-foreground text-sm",
|
|
2880
|
+
children: deal3.wonSource
|
|
2881
|
+
}, undefined, false, undefined, this)
|
|
2882
|
+
]
|
|
2883
|
+
}, undefined, true, undefined, this) : null,
|
|
2884
|
+
deal3.lostReason ? /* @__PURE__ */ jsxDEV5(HStack, {
|
|
2885
|
+
justify: "between",
|
|
2886
|
+
children: [
|
|
2887
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2888
|
+
className: "font-medium text-sm",
|
|
2889
|
+
children: "Lost Reason"
|
|
2890
|
+
}, undefined, false, undefined, this),
|
|
2891
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2892
|
+
className: "text-muted-foreground text-sm",
|
|
2893
|
+
children: deal3.lostReason
|
|
2894
|
+
}, undefined, false, undefined, this)
|
|
2895
|
+
]
|
|
2896
|
+
}, undefined, true, undefined, this) : null,
|
|
2897
|
+
deal3.notes ? /* @__PURE__ */ jsxDEV5(VStack, {
|
|
2898
|
+
gap: "xs",
|
|
2899
|
+
children: [
|
|
2900
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2901
|
+
className: "font-medium text-sm",
|
|
2902
|
+
children: "Notes"
|
|
2903
|
+
}, undefined, false, undefined, this),
|
|
2904
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2905
|
+
className: "text-muted-foreground text-sm",
|
|
2906
|
+
children: deal3.notes
|
|
2907
|
+
}, undefined, false, undefined, this)
|
|
2908
|
+
]
|
|
2909
|
+
}, undefined, true, undefined, this) : null
|
|
2910
|
+
]
|
|
2911
|
+
}, undefined, true, undefined, this),
|
|
2912
|
+
getCanExpand: () => true
|
|
2913
|
+
});
|
|
2914
|
+
return /* @__PURE__ */ jsxDEV5(DataTable, {
|
|
2915
|
+
controller,
|
|
2916
|
+
title: "All Deals",
|
|
2917
|
+
description: "Server-mode table using the shared ContractSpec controller.",
|
|
2918
|
+
loading,
|
|
2919
|
+
toolbar: /* @__PURE__ */ jsxDEV5(HStack, {
|
|
2920
|
+
gap: "sm",
|
|
2921
|
+
className: "flex-wrap",
|
|
2922
|
+
children: [
|
|
2923
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2924
|
+
className: "text-muted-foreground text-sm",
|
|
2925
|
+
children: [
|
|
2926
|
+
"Selected ",
|
|
2927
|
+
controller.selectedRowIds.length
|
|
2928
|
+
]
|
|
2929
|
+
}, undefined, true, undefined, this),
|
|
2930
|
+
/* @__PURE__ */ jsxDEV5(Text, {
|
|
2931
|
+
className: "text-muted-foreground text-sm",
|
|
2932
|
+
children: [
|
|
2933
|
+
totalItems,
|
|
2934
|
+
" total deals"
|
|
2935
|
+
]
|
|
2936
|
+
}, undefined, true, undefined, this)
|
|
2937
|
+
]
|
|
2938
|
+
}, undefined, true, undefined, this),
|
|
2939
|
+
footer: `Page ${controller.pageIndex + 1} of ${controller.pageCount}`,
|
|
2940
|
+
emptyState: /* @__PURE__ */ jsxDEV5("div", {
|
|
2941
|
+
className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
|
|
2942
|
+
children: "No deals found"
|
|
2943
|
+
}, undefined, false, undefined, this)
|
|
2944
|
+
}, undefined, false, undefined, this);
|
|
2945
|
+
}
|
|
2946
|
+
function DealListTab({
|
|
2947
|
+
onDealClick
|
|
2948
|
+
}) {
|
|
2949
|
+
const [sorting, setSorting] = React.useState([
|
|
2950
|
+
{ id: "value", desc: true }
|
|
2951
|
+
]);
|
|
2952
|
+
const [pagination, setPagination] = React.useState({
|
|
2953
|
+
pageIndex: 0,
|
|
2954
|
+
pageSize: 3
|
|
2955
|
+
});
|
|
2956
|
+
const { data, loading } = useDealList({
|
|
2957
|
+
pageIndex: pagination.pageIndex,
|
|
2958
|
+
pageSize: pagination.pageSize,
|
|
2959
|
+
sorting
|
|
2960
|
+
});
|
|
2961
|
+
if (loading && !data) {
|
|
2962
|
+
return /* @__PURE__ */ jsxDEV5(LoaderBlock, {
|
|
2963
|
+
label: "Loading deals..."
|
|
2964
|
+
}, undefined, false, undefined, this);
|
|
2965
|
+
}
|
|
2966
|
+
return /* @__PURE__ */ jsxDEV5(DealListDataTable, {
|
|
2967
|
+
deals: data?.deals ?? [],
|
|
2968
|
+
totalItems: data?.total ?? 0,
|
|
2969
|
+
pageIndex: pagination.pageIndex,
|
|
2970
|
+
pageSize: pagination.pageSize,
|
|
2971
|
+
sorting,
|
|
2972
|
+
loading,
|
|
2973
|
+
onSortingChange: (nextSorting) => {
|
|
2974
|
+
setSorting(nextSorting);
|
|
2975
|
+
setPagination((current) => ({ ...current, pageIndex: 0 }));
|
|
2976
|
+
},
|
|
2977
|
+
onPaginationChange: setPagination,
|
|
2978
|
+
onDealClick
|
|
2979
|
+
}, undefined, false, undefined, this);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// src/ui/CrmDashboard.tsx
|
|
2983
|
+
import {
|
|
2984
|
+
Button as Button4,
|
|
2670
2985
|
ErrorState,
|
|
2671
|
-
LoaderBlock,
|
|
2986
|
+
LoaderBlock as LoaderBlock2,
|
|
2672
2987
|
StatCard,
|
|
2673
2988
|
StatCardGroup
|
|
2674
2989
|
} from "@contractspec/lib.design-system";
|
|
@@ -2678,9 +2993,10 @@ import {
|
|
|
2678
2993
|
TabsList,
|
|
2679
2994
|
TabsTrigger
|
|
2680
2995
|
} from "@contractspec/lib.ui-kit-web/ui/tabs";
|
|
2681
|
-
import {
|
|
2996
|
+
import { useCallback as useCallback3, useState as useState7 } from "react";
|
|
2997
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
2682
2998
|
"use client";
|
|
2683
|
-
function
|
|
2999
|
+
function formatCurrency5(value, currency = "USD") {
|
|
2684
3000
|
return new Intl.NumberFormat("en-US", {
|
|
2685
3001
|
style: "currency",
|
|
2686
3002
|
currency,
|
|
@@ -2689,9 +3005,9 @@ function formatCurrency4(value, currency = "USD") {
|
|
|
2689
3005
|
}).format(value);
|
|
2690
3006
|
}
|
|
2691
3007
|
function CrmDashboard() {
|
|
2692
|
-
const [isCreateModalOpen, setIsCreateModalOpen] =
|
|
2693
|
-
const [selectedDeal, setSelectedDeal] =
|
|
2694
|
-
const [isDealActionsOpen, setIsDealActionsOpen] =
|
|
3008
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState7(false);
|
|
3009
|
+
const [selectedDeal, setSelectedDeal] = useState7(null);
|
|
3010
|
+
const [isDealActionsOpen, setIsDealActionsOpen] = useState7(false);
|
|
2695
3011
|
const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
|
|
2696
3012
|
const mutations = useDealMutations({
|
|
2697
3013
|
onSuccess: () => {
|
|
@@ -2709,32 +3025,32 @@ function CrmDashboard() {
|
|
|
2709
3025
|
await mutations.moveDeal({ dealId, stageId: toStageId });
|
|
2710
3026
|
}, [mutations]);
|
|
2711
3027
|
if (loading && !data) {
|
|
2712
|
-
return /* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
|
|
2713
3029
|
label: "Loading CRM..."
|
|
2714
3030
|
}, undefined, false, undefined, this);
|
|
2715
3031
|
}
|
|
2716
3032
|
if (error) {
|
|
2717
|
-
return /* @__PURE__ */
|
|
3033
|
+
return /* @__PURE__ */ jsxDEV6(ErrorState, {
|
|
2718
3034
|
title: "Failed to load CRM",
|
|
2719
3035
|
description: error.message,
|
|
2720
3036
|
onRetry: refetch,
|
|
2721
3037
|
retryLabel: "Retry"
|
|
2722
3038
|
}, undefined, false, undefined, this);
|
|
2723
3039
|
}
|
|
2724
|
-
return /* @__PURE__ */
|
|
3040
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2725
3041
|
className: "space-y-6",
|
|
2726
3042
|
children: [
|
|
2727
|
-
/* @__PURE__ */
|
|
3043
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2728
3044
|
className: "flex items-center justify-between",
|
|
2729
3045
|
children: [
|
|
2730
|
-
/* @__PURE__ */
|
|
2731
|
-
className: "text-2xl
|
|
3046
|
+
/* @__PURE__ */ jsxDEV6("h2", {
|
|
3047
|
+
className: "font-bold text-2xl",
|
|
2732
3048
|
children: "CRM Pipeline"
|
|
2733
3049
|
}, undefined, false, undefined, this),
|
|
2734
|
-
/* @__PURE__ */
|
|
3050
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
2735
3051
|
onClick: () => setIsCreateModalOpen(true),
|
|
2736
3052
|
children: [
|
|
2737
|
-
/* @__PURE__ */
|
|
3053
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2738
3054
|
className: "mr-2",
|
|
2739
3055
|
children: "+"
|
|
2740
3056
|
}, undefined, false, undefined, this),
|
|
@@ -2743,60 +3059,60 @@ function CrmDashboard() {
|
|
|
2743
3059
|
}, undefined, true, undefined, this)
|
|
2744
3060
|
]
|
|
2745
3061
|
}, undefined, true, undefined, this),
|
|
2746
|
-
stats && /* @__PURE__ */
|
|
3062
|
+
stats && /* @__PURE__ */ jsxDEV6(StatCardGroup, {
|
|
2747
3063
|
children: [
|
|
2748
|
-
/* @__PURE__ */
|
|
3064
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
2749
3065
|
label: "Total Pipeline",
|
|
2750
|
-
value:
|
|
3066
|
+
value: formatCurrency5(stats.totalValue),
|
|
2751
3067
|
hint: `${stats.total} deals`
|
|
2752
3068
|
}, undefined, false, undefined, this),
|
|
2753
|
-
/* @__PURE__ */
|
|
3069
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
2754
3070
|
label: "Open Deals",
|
|
2755
|
-
value:
|
|
3071
|
+
value: formatCurrency5(stats.openValue),
|
|
2756
3072
|
hint: `${stats.openCount} active`
|
|
2757
3073
|
}, undefined, false, undefined, this),
|
|
2758
|
-
/* @__PURE__ */
|
|
3074
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
2759
3075
|
label: "Won",
|
|
2760
|
-
value:
|
|
3076
|
+
value: formatCurrency5(stats.wonValue),
|
|
2761
3077
|
hint: `${stats.wonCount} closed`
|
|
2762
3078
|
}, undefined, false, undefined, this),
|
|
2763
|
-
/* @__PURE__ */
|
|
3079
|
+
/* @__PURE__ */ jsxDEV6(StatCard, {
|
|
2764
3080
|
label: "Lost",
|
|
2765
3081
|
value: stats.lostCount,
|
|
2766
3082
|
hint: "deals lost"
|
|
2767
3083
|
}, undefined, false, undefined, this)
|
|
2768
3084
|
]
|
|
2769
3085
|
}, undefined, true, undefined, this),
|
|
2770
|
-
/* @__PURE__ */
|
|
3086
|
+
/* @__PURE__ */ jsxDEV6(Tabs, {
|
|
2771
3087
|
defaultValue: "pipeline",
|
|
2772
3088
|
className: "w-full",
|
|
2773
3089
|
children: [
|
|
2774
|
-
/* @__PURE__ */
|
|
3090
|
+
/* @__PURE__ */ jsxDEV6(TabsList, {
|
|
2775
3091
|
children: [
|
|
2776
|
-
/* @__PURE__ */
|
|
3092
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
2777
3093
|
value: "pipeline",
|
|
2778
3094
|
children: [
|
|
2779
|
-
/* @__PURE__ */
|
|
3095
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2780
3096
|
className: "mr-2",
|
|
2781
3097
|
children: "\uD83D\uDCCA"
|
|
2782
3098
|
}, undefined, false, undefined, this),
|
|
2783
3099
|
"Pipeline"
|
|
2784
3100
|
]
|
|
2785
3101
|
}, undefined, true, undefined, this),
|
|
2786
|
-
/* @__PURE__ */
|
|
3102
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
2787
3103
|
value: "list",
|
|
2788
3104
|
children: [
|
|
2789
|
-
/* @__PURE__ */
|
|
3105
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2790
3106
|
className: "mr-2",
|
|
2791
3107
|
children: "\uD83D\uDCCB"
|
|
2792
3108
|
}, undefined, false, undefined, this),
|
|
2793
3109
|
"All Deals"
|
|
2794
3110
|
]
|
|
2795
3111
|
}, undefined, true, undefined, this),
|
|
2796
|
-
/* @__PURE__ */
|
|
3112
|
+
/* @__PURE__ */ jsxDEV6(TabsTrigger, {
|
|
2797
3113
|
value: "metrics",
|
|
2798
3114
|
children: [
|
|
2799
|
-
/* @__PURE__ */
|
|
3115
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2800
3116
|
className: "mr-2",
|
|
2801
3117
|
children: "\uD83D\uDCC8"
|
|
2802
3118
|
}, undefined, false, undefined, this),
|
|
@@ -2805,34 +3121,33 @@ function CrmDashboard() {
|
|
|
2805
3121
|
}, undefined, true, undefined, this)
|
|
2806
3122
|
]
|
|
2807
3123
|
}, undefined, true, undefined, this),
|
|
2808
|
-
/* @__PURE__ */
|
|
3124
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
2809
3125
|
value: "pipeline",
|
|
2810
3126
|
className: "min-h-[400px]",
|
|
2811
|
-
children: /* @__PURE__ */
|
|
3127
|
+
children: /* @__PURE__ */ jsxDEV6(CrmPipelineBoard, {
|
|
2812
3128
|
dealsByStage,
|
|
2813
3129
|
stages,
|
|
2814
3130
|
onDealClick: handleDealClick,
|
|
2815
3131
|
onDealMove: handleDealMove
|
|
2816
3132
|
}, undefined, false, undefined, this)
|
|
2817
3133
|
}, undefined, false, undefined, this),
|
|
2818
|
-
/* @__PURE__ */
|
|
3134
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
2819
3135
|
value: "list",
|
|
2820
3136
|
className: "min-h-[400px]",
|
|
2821
|
-
children: /* @__PURE__ */
|
|
2822
|
-
data,
|
|
3137
|
+
children: /* @__PURE__ */ jsxDEV6(DealListTab, {
|
|
2823
3138
|
onDealClick: handleDealClick
|
|
2824
3139
|
}, undefined, false, undefined, this)
|
|
2825
3140
|
}, undefined, false, undefined, this),
|
|
2826
|
-
/* @__PURE__ */
|
|
3141
|
+
/* @__PURE__ */ jsxDEV6(TabsContent, {
|
|
2827
3142
|
value: "metrics",
|
|
2828
3143
|
className: "min-h-[400px]",
|
|
2829
|
-
children: /* @__PURE__ */
|
|
3144
|
+
children: /* @__PURE__ */ jsxDEV6(MetricsTab, {
|
|
2830
3145
|
stats
|
|
2831
3146
|
}, undefined, false, undefined, this)
|
|
2832
3147
|
}, undefined, false, undefined, this)
|
|
2833
3148
|
]
|
|
2834
3149
|
}, undefined, true, undefined, this),
|
|
2835
|
-
/* @__PURE__ */
|
|
3150
|
+
/* @__PURE__ */ jsxDEV6(CreateDealModal, {
|
|
2836
3151
|
isOpen: isCreateModalOpen,
|
|
2837
3152
|
onClose: () => setIsCreateModalOpen(false),
|
|
2838
3153
|
onSubmit: async (input) => {
|
|
@@ -2841,7 +3156,7 @@ function CrmDashboard() {
|
|
|
2841
3156
|
stages,
|
|
2842
3157
|
isLoading: mutations.createState.loading
|
|
2843
3158
|
}, undefined, false, undefined, this),
|
|
2844
|
-
/* @__PURE__ */
|
|
3159
|
+
/* @__PURE__ */ jsxDEV6(DealActionsModal, {
|
|
2845
3160
|
isOpen: isDealActionsOpen,
|
|
2846
3161
|
deal: selectedDeal,
|
|
2847
3162
|
stages,
|
|
@@ -2864,113 +3179,31 @@ function CrmDashboard() {
|
|
|
2864
3179
|
]
|
|
2865
3180
|
}, undefined, true, undefined, this);
|
|
2866
3181
|
}
|
|
2867
|
-
function DealListTab({ data, onDealClick }) {
|
|
2868
|
-
if (!data?.deals.length) {
|
|
2869
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2870
|
-
className: "text-muted-foreground flex h-64 items-center justify-center",
|
|
2871
|
-
children: "No deals found"
|
|
2872
|
-
}, undefined, false, undefined, this);
|
|
2873
|
-
}
|
|
2874
|
-
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2875
|
-
className: "border-border rounded-lg border",
|
|
2876
|
-
children: /* @__PURE__ */ jsxDEV5("table", {
|
|
2877
|
-
className: "w-full",
|
|
2878
|
-
children: [
|
|
2879
|
-
/* @__PURE__ */ jsxDEV5("thead", {
|
|
2880
|
-
className: "border-border bg-muted/30 border-b",
|
|
2881
|
-
children: /* @__PURE__ */ jsxDEV5("tr", {
|
|
2882
|
-
children: [
|
|
2883
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
2884
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
2885
|
-
children: "Deal"
|
|
2886
|
-
}, undefined, false, undefined, this),
|
|
2887
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
2888
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
2889
|
-
children: "Value"
|
|
2890
|
-
}, undefined, false, undefined, this),
|
|
2891
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
2892
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
2893
|
-
children: "Status"
|
|
2894
|
-
}, undefined, false, undefined, this),
|
|
2895
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
2896
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
2897
|
-
children: "Expected Close"
|
|
2898
|
-
}, undefined, false, undefined, this),
|
|
2899
|
-
/* @__PURE__ */ jsxDEV5("th", {
|
|
2900
|
-
className: "px-4 py-3 text-left text-sm font-medium",
|
|
2901
|
-
children: "Actions"
|
|
2902
|
-
}, undefined, false, undefined, this)
|
|
2903
|
-
]
|
|
2904
|
-
}, undefined, true, undefined, this)
|
|
2905
|
-
}, undefined, false, undefined, this),
|
|
2906
|
-
/* @__PURE__ */ jsxDEV5("tbody", {
|
|
2907
|
-
className: "divide-border divide-y",
|
|
2908
|
-
children: data.deals.map((deal3) => /* @__PURE__ */ jsxDEV5("tr", {
|
|
2909
|
-
className: "hover:bg-muted/50",
|
|
2910
|
-
children: [
|
|
2911
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
2912
|
-
className: "px-4 py-3",
|
|
2913
|
-
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
2914
|
-
className: "font-medium",
|
|
2915
|
-
children: deal3.name
|
|
2916
|
-
}, undefined, false, undefined, this)
|
|
2917
|
-
}, undefined, false, undefined, this),
|
|
2918
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
2919
|
-
className: "px-4 py-3 font-mono",
|
|
2920
|
-
children: formatCurrency4(deal3.value, deal3.currency)
|
|
2921
|
-
}, undefined, false, undefined, this),
|
|
2922
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
2923
|
-
className: "px-4 py-3",
|
|
2924
|
-
children: /* @__PURE__ */ jsxDEV5("span", {
|
|
2925
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${deal3.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal3.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"}`,
|
|
2926
|
-
children: deal3.status
|
|
2927
|
-
}, undefined, false, undefined, this)
|
|
2928
|
-
}, undefined, false, undefined, this),
|
|
2929
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
2930
|
-
className: "text-muted-foreground px-4 py-3",
|
|
2931
|
-
children: deal3.expectedCloseDate?.toLocaleDateString() ?? "-"
|
|
2932
|
-
}, undefined, false, undefined, this),
|
|
2933
|
-
/* @__PURE__ */ jsxDEV5("td", {
|
|
2934
|
-
className: "px-4 py-3",
|
|
2935
|
-
children: /* @__PURE__ */ jsxDEV5(Button3, {
|
|
2936
|
-
variant: "ghost",
|
|
2937
|
-
size: "sm",
|
|
2938
|
-
onPress: () => onDealClick?.(deal3.id),
|
|
2939
|
-
children: "Actions"
|
|
2940
|
-
}, undefined, false, undefined, this)
|
|
2941
|
-
}, undefined, false, undefined, this)
|
|
2942
|
-
]
|
|
2943
|
-
}, deal3.id, true, undefined, this))
|
|
2944
|
-
}, undefined, false, undefined, this)
|
|
2945
|
-
]
|
|
2946
|
-
}, undefined, true, undefined, this)
|
|
2947
|
-
}, undefined, false, undefined, this);
|
|
2948
|
-
}
|
|
2949
3182
|
function MetricsTab({
|
|
2950
3183
|
stats
|
|
2951
3184
|
}) {
|
|
2952
3185
|
if (!stats)
|
|
2953
3186
|
return null;
|
|
2954
|
-
return /* @__PURE__ */
|
|
3187
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2955
3188
|
className: "space-y-6",
|
|
2956
|
-
children: /* @__PURE__ */
|
|
2957
|
-
className: "border-border bg-card
|
|
3189
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
3190
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2958
3191
|
children: [
|
|
2959
|
-
/* @__PURE__ */
|
|
2960
|
-
className: "mb-4 text-lg
|
|
3192
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3193
|
+
className: "mb-4 font-semibold text-lg",
|
|
2961
3194
|
children: "Pipeline Overview"
|
|
2962
3195
|
}, undefined, false, undefined, this),
|
|
2963
|
-
/* @__PURE__ */
|
|
3196
|
+
/* @__PURE__ */ jsxDEV6("dl", {
|
|
2964
3197
|
className: "grid gap-4 sm:grid-cols-3",
|
|
2965
3198
|
children: [
|
|
2966
|
-
/* @__PURE__ */
|
|
3199
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2967
3200
|
children: [
|
|
2968
|
-
/* @__PURE__ */
|
|
3201
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
2969
3202
|
className: "text-muted-foreground text-sm",
|
|
2970
3203
|
children: "Win Rate"
|
|
2971
3204
|
}, undefined, false, undefined, this),
|
|
2972
|
-
/* @__PURE__ */
|
|
2973
|
-
className: "text-2xl
|
|
3205
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
3206
|
+
className: "font-semibold text-2xl",
|
|
2974
3207
|
children: [
|
|
2975
3208
|
stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0,
|
|
2976
3209
|
"%"
|
|
@@ -2978,26 +3211,26 @@ function MetricsTab({
|
|
|
2978
3211
|
}, undefined, true, undefined, this)
|
|
2979
3212
|
]
|
|
2980
3213
|
}, undefined, true, undefined, this),
|
|
2981
|
-
/* @__PURE__ */
|
|
3214
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2982
3215
|
children: [
|
|
2983
|
-
/* @__PURE__ */
|
|
3216
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
2984
3217
|
className: "text-muted-foreground text-sm",
|
|
2985
3218
|
children: "Avg Deal Size"
|
|
2986
3219
|
}, undefined, false, undefined, this),
|
|
2987
|
-
/* @__PURE__ */
|
|
2988
|
-
className: "text-2xl
|
|
2989
|
-
children:
|
|
3220
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
3221
|
+
className: "font-semibold text-2xl",
|
|
3222
|
+
children: formatCurrency5(stats.total > 0 ? stats.totalValue / stats.total : 0)
|
|
2990
3223
|
}, undefined, false, undefined, this)
|
|
2991
3224
|
]
|
|
2992
3225
|
}, undefined, true, undefined, this),
|
|
2993
|
-
/* @__PURE__ */
|
|
3226
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2994
3227
|
children: [
|
|
2995
|
-
/* @__PURE__ */
|
|
3228
|
+
/* @__PURE__ */ jsxDEV6("dt", {
|
|
2996
3229
|
className: "text-muted-foreground text-sm",
|
|
2997
3230
|
children: "Conversion"
|
|
2998
3231
|
}, undefined, false, undefined, this),
|
|
2999
|
-
/* @__PURE__ */
|
|
3000
|
-
className: "text-2xl
|
|
3232
|
+
/* @__PURE__ */ jsxDEV6("dd", {
|
|
3233
|
+
className: "font-semibold text-2xl",
|
|
3001
3234
|
children: [
|
|
3002
3235
|
stats.wonCount,
|
|
3003
3236
|
" / ",
|
|
@@ -3012,33 +3245,61 @@ function MetricsTab({
|
|
|
3012
3245
|
}, undefined, true, undefined, this)
|
|
3013
3246
|
}, undefined, false, undefined, this);
|
|
3014
3247
|
}
|
|
3248
|
+
|
|
3015
3249
|
// src/ui/hooks/index.ts
|
|
3016
3250
|
"use client";
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
},
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3251
|
+
// src/ui/overlays/demo-overlays.ts
|
|
3252
|
+
var crmDemoOverlay = {
|
|
3253
|
+
overlayId: "crm-pipeline.demo-user",
|
|
3254
|
+
version: "1.0.0",
|
|
3255
|
+
description: "Demo mode with sample data",
|
|
3256
|
+
appliesTo: {
|
|
3257
|
+
feature: "crm-pipeline",
|
|
3258
|
+
role: "demo"
|
|
3259
|
+
},
|
|
3260
|
+
modifications: [
|
|
3261
|
+
{
|
|
3262
|
+
type: "hideField",
|
|
3263
|
+
field: "importButton",
|
|
3264
|
+
reason: "Not available in demo"
|
|
3265
|
+
},
|
|
3266
|
+
{
|
|
3267
|
+
type: "hideField",
|
|
3268
|
+
field: "exportButton",
|
|
3269
|
+
reason: "Not available in demo"
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
type: "addBadge",
|
|
3273
|
+
position: "header",
|
|
3274
|
+
label: "Demo Mode",
|
|
3275
|
+
variant: "warning"
|
|
3035
3276
|
}
|
|
3036
|
-
|
|
3037
|
-
}
|
|
3277
|
+
]
|
|
3038
3278
|
};
|
|
3039
|
-
|
|
3279
|
+
var crmSalesRepOverlay = {
|
|
3280
|
+
overlayId: "crm-pipeline.sales-rep",
|
|
3281
|
+
version: "1.0.0",
|
|
3282
|
+
description: "Sales rep focused view",
|
|
3283
|
+
appliesTo: {
|
|
3284
|
+
feature: "crm-pipeline",
|
|
3285
|
+
role: "sales-rep"
|
|
3286
|
+
},
|
|
3287
|
+
modifications: [
|
|
3288
|
+
{
|
|
3289
|
+
type: "hideField",
|
|
3290
|
+
field: "teamMetrics",
|
|
3291
|
+
reason: "Team metrics for managers only"
|
|
3292
|
+
},
|
|
3293
|
+
{ type: "hideField", field: "pipelineSettings", reason: "Admin only" },
|
|
3294
|
+
{ type: "renameLabel", field: "deals", newLabel: "My Deals" }
|
|
3295
|
+
]
|
|
3296
|
+
};
|
|
3297
|
+
var crmOverlays = [
|
|
3298
|
+
crmDemoOverlay,
|
|
3299
|
+
crmSalesRepOverlay
|
|
3300
|
+
];
|
|
3040
3301
|
// src/ui/renderers/pipeline.markdown.ts
|
|
3041
|
-
function
|
|
3302
|
+
function formatCurrency6(value, currency = "USD") {
|
|
3042
3303
|
return new Intl.NumberFormat("en-US", {
|
|
3043
3304
|
style: "currency",
|
|
3044
3305
|
currency,
|
|
@@ -3065,7 +3326,7 @@ var crmPipelineMarkdownRenderer = {
|
|
|
3065
3326
|
const lines = [
|
|
3066
3327
|
"# CRM Pipeline",
|
|
3067
3328
|
"",
|
|
3068
|
-
`**Total Value**: ${
|
|
3329
|
+
`**Total Value**: ${formatCurrency6(dealsResult.totalValue)}`,
|
|
3069
3330
|
`**Total Deals**: ${dealsResult.total}`,
|
|
3070
3331
|
""
|
|
3071
3332
|
];
|
|
@@ -3073,13 +3334,13 @@ var crmPipelineMarkdownRenderer = {
|
|
|
3073
3334
|
const stageDeals = dealsByStage[stage.id] ?? [];
|
|
3074
3335
|
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
3075
3336
|
lines.push(`## ${stage.name}`);
|
|
3076
|
-
lines.push(`_${stageDeals.length} deals \xB7 ${
|
|
3337
|
+
lines.push(`_${stageDeals.length} deals \xB7 ${formatCurrency6(stageValue)}_`);
|
|
3077
3338
|
lines.push("");
|
|
3078
3339
|
if (stageDeals.length === 0) {
|
|
3079
3340
|
lines.push("_No deals_");
|
|
3080
3341
|
} else {
|
|
3081
3342
|
for (const deal3 of stageDeals) {
|
|
3082
|
-
lines.push(`- **${deal3.name}** - ${
|
|
3343
|
+
lines.push(`- **${deal3.name}** - ${formatCurrency6(deal3.value, deal3.currency)}`);
|
|
3083
3344
|
}
|
|
3084
3345
|
}
|
|
3085
3346
|
lines.push("");
|
|
@@ -3119,9 +3380,9 @@ var crmDashboardMarkdownRenderer = {
|
|
|
3119
3380
|
"| Metric | Value |",
|
|
3120
3381
|
"|--------|-------|",
|
|
3121
3382
|
`| Total Deals | ${dealsResult.total} |`,
|
|
3122
|
-
`| Pipeline Value | ${
|
|
3123
|
-
`| Open Deals | ${openDeals.length} (${
|
|
3124
|
-
`| Won Deals | ${wonDeals.length} (${
|
|
3383
|
+
`| Pipeline Value | ${formatCurrency6(dealsResult.totalValue)} |`,
|
|
3384
|
+
`| Open Deals | ${openDeals.length} (${formatCurrency6(openValue)}) |`,
|
|
3385
|
+
`| Won Deals | ${wonDeals.length} (${formatCurrency6(wonValue)}) |`,
|
|
3125
3386
|
`| Lost Deals | ${lostDeals.length} |`,
|
|
3126
3387
|
"",
|
|
3127
3388
|
"## Pipeline Stages",
|
|
@@ -3132,7 +3393,7 @@ var crmDashboardMarkdownRenderer = {
|
|
|
3132
3393
|
for (const stage of stageList.sort((a, b) => a.position - b.position)) {
|
|
3133
3394
|
const stageDeals = openDeals.filter((d) => d.stageId === stage.id);
|
|
3134
3395
|
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
3135
|
-
lines.push(`| ${stage.name} | ${stageDeals.length} | ${
|
|
3396
|
+
lines.push(`| ${stage.name} | ${stageDeals.length} | ${formatCurrency6(stageValue)} |`);
|
|
3136
3397
|
}
|
|
3137
3398
|
lines.push("");
|
|
3138
3399
|
lines.push("## Recent Deals");
|
|
@@ -3145,7 +3406,7 @@ var crmDashboardMarkdownRenderer = {
|
|
|
3145
3406
|
lines.push("|------|-------|-------|--------|");
|
|
3146
3407
|
for (const deal3 of recentDeals) {
|
|
3147
3408
|
const stage = stageList.find((s) => s.id === deal3.stageId);
|
|
3148
|
-
lines.push(`| ${deal3.name} | ${
|
|
3409
|
+
lines.push(`| ${deal3.name} | ${formatCurrency6(deal3.value, deal3.currency)} | ${stage?.name ?? "-"} | ${deal3.status} |`);
|
|
3149
3410
|
}
|
|
3150
3411
|
}
|
|
3151
3412
|
return {
|
|
@@ -3155,56 +3416,28 @@ var crmDashboardMarkdownRenderer = {
|
|
|
3155
3416
|
};
|
|
3156
3417
|
}
|
|
3157
3418
|
};
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
},
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
{
|
|
3174
|
-
type: "hideField",
|
|
3175
|
-
field: "exportButton",
|
|
3176
|
-
reason: "Not available in demo"
|
|
3177
|
-
},
|
|
3178
|
-
{
|
|
3179
|
-
type: "addBadge",
|
|
3180
|
-
position: "header",
|
|
3181
|
-
label: "Demo Mode",
|
|
3182
|
-
variant: "warning"
|
|
3419
|
+
|
|
3420
|
+
// src/ui/renderers/pipeline.renderer.tsx
|
|
3421
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
3422
|
+
function CrmPipelineBoardWrapper() {
|
|
3423
|
+
const { dealsByStage, stages } = useDealList();
|
|
3424
|
+
return /* @__PURE__ */ jsxDEV7(CrmPipelineBoard, {
|
|
3425
|
+
dealsByStage,
|
|
3426
|
+
stages
|
|
3427
|
+
}, undefined, false, undefined, this);
|
|
3428
|
+
}
|
|
3429
|
+
var crmPipelineReactRenderer = {
|
|
3430
|
+
target: "react",
|
|
3431
|
+
render: async (desc, _ctx) => {
|
|
3432
|
+
if (desc.source.type !== "component") {
|
|
3433
|
+
throw new Error("Invalid source type");
|
|
3183
3434
|
}
|
|
3184
|
-
|
|
3185
|
-
};
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
description: "Sales rep focused view",
|
|
3190
|
-
appliesTo: {
|
|
3191
|
-
feature: "crm-pipeline",
|
|
3192
|
-
role: "sales-rep"
|
|
3193
|
-
},
|
|
3194
|
-
modifications: [
|
|
3195
|
-
{
|
|
3196
|
-
type: "hideField",
|
|
3197
|
-
field: "teamMetrics",
|
|
3198
|
-
reason: "Team metrics for managers only"
|
|
3199
|
-
},
|
|
3200
|
-
{ type: "hideField", field: "pipelineSettings", reason: "Admin only" },
|
|
3201
|
-
{ type: "renameLabel", field: "deals", newLabel: "My Deals" }
|
|
3202
|
-
]
|
|
3435
|
+
if (desc.source.componentKey !== "CrmPipelineView") {
|
|
3436
|
+
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
3437
|
+
}
|
|
3438
|
+
return /* @__PURE__ */ jsxDEV7(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
|
|
3439
|
+
}
|
|
3203
3440
|
};
|
|
3204
|
-
var crmOverlays = [
|
|
3205
|
-
crmDemoOverlay,
|
|
3206
|
-
crmSalesRepOverlay
|
|
3207
|
-
];
|
|
3208
3441
|
// src/index.ts
|
|
3209
3442
|
import { identityRbacSchemaContribution } from "@contractspec/lib.identity-rbac";
|
|
3210
3443
|
import { auditTrailSchemaContribution } from "@contractspec/module.audit-trail";
|