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