@contractspec/example.crm-pipeline 1.46.1 → 1.47.0
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$colon$bundle.log +106 -31
- package/.turbo/turbo-build.log +109 -34
- package/CHANGELOG.md +44 -0
- package/dist/crm-pipeline.feature.d.ts +2 -2
- package/dist/crm-pipeline.feature.d.ts.map +1 -1
- package/dist/crm-pipeline.feature.js +9 -2
- package/dist/crm-pipeline.feature.js.map +1 -1
- package/dist/deal/deal.operation.d.ts +6 -6
- package/dist/deal/deal.test-spec.d.ts +8 -0
- package/dist/deal/deal.test-spec.d.ts.map +1 -0
- package/dist/deal/deal.test-spec.js +65 -0
- package/dist/deal/deal.test-spec.js.map +1 -0
- package/dist/entities/company.entity.d.ts +28 -28
- package/dist/entities/contact.entity.d.ts +32 -32
- package/dist/entities/index.js.map +1 -1
- package/dist/entities/task.entity.d.ts +43 -43
- package/dist/events/contact.event.d.ts +8 -8
- package/dist/events/contact.event.js +1 -1
- package/dist/events/deal.event.d.ts +5 -5
- package/dist/events/deal.event.js +1 -1
- package/dist/events/task.event.d.ts +2 -2
- package/dist/events/task.event.d.ts.map +1 -1
- package/dist/events/task.event.js +1 -1
- package/dist/example.d.ts +2 -2
- package/dist/example.d.ts.map +1 -1
- package/dist/example.js +4 -2
- package/dist/example.js.map +1 -1
- package/dist/handlers/crm.handlers.d.ts +89 -0
- package/dist/handlers/crm.handlers.d.ts.map +1 -0
- package/dist/handlers/crm.handlers.js +172 -0
- package/dist/handlers/crm.handlers.js.map +1 -0
- package/dist/handlers/deal.handlers.js.map +1 -1
- package/dist/handlers/index.d.ts +2 -1
- package/dist/handlers/index.js +2 -1
- package/dist/handlers/mock-data.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/presentations/dashboard.presentation.d.ts +3 -4
- package/dist/presentations/dashboard.presentation.d.ts.map +1 -1
- package/dist/presentations/dashboard.presentation.js +8 -5
- package/dist/presentations/dashboard.presentation.js.map +1 -1
- package/dist/presentations/pipeline.presentation.d.ts +5 -6
- package/dist/presentations/pipeline.presentation.d.ts.map +1 -1
- package/dist/presentations/pipeline.presentation.js +12 -9
- package/dist/presentations/pipeline.presentation.js.map +1 -1
- package/dist/seeders/index.d.ts +10 -0
- package/dist/seeders/index.d.ts.map +1 -0
- package/dist/seeders/index.js +47 -0
- package/dist/seeders/index.js.map +1 -0
- package/dist/shared/overlay-types.d.ts +34 -0
- package/dist/shared/overlay-types.d.ts.map +1 -0
- package/dist/shared/overlay-types.js +0 -0
- package/dist/ui/CrmDashboard.d.ts +7 -0
- package/dist/ui/CrmDashboard.d.ts.map +1 -0
- package/dist/ui/CrmDashboard.js +304 -0
- package/dist/ui/CrmDashboard.js.map +1 -0
- package/dist/ui/CrmDealCard.d.ts +15 -0
- package/dist/ui/CrmDealCard.d.ts.map +1 -0
- package/dist/ui/CrmDealCard.js +49 -0
- package/dist/ui/CrmDealCard.js.map +1 -0
- package/dist/ui/CrmPipelineBoard.d.ts +23 -0
- package/dist/ui/CrmPipelineBoard.d.ts.map +1 -0
- package/dist/ui/CrmPipelineBoard.js +98 -0
- package/dist/ui/CrmPipelineBoard.js.map +1 -0
- package/dist/ui/hooks/index.d.ts +3 -0
- package/dist/ui/hooks/index.js +6 -0
- package/dist/ui/hooks/useDealList.d.ts +35 -0
- package/dist/ui/hooks/useDealList.d.ts.map +1 -0
- package/dist/ui/hooks/useDealList.js +94 -0
- package/dist/ui/hooks/useDealList.js.map +1 -0
- package/dist/ui/hooks/useDealMutations.d.ts +26 -0
- package/dist/ui/hooks/useDealMutations.d.ts.map +1 -0
- package/dist/ui/hooks/useDealMutations.js +159 -0
- package/dist/ui/hooks/useDealMutations.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/modals/CreateDealModal.d.ts +33 -0
- package/dist/ui/modals/CreateDealModal.d.ts.map +1 -0
- package/dist/ui/modals/CreateDealModal.js +183 -0
- package/dist/ui/modals/CreateDealModal.js.map +1 -0
- package/dist/ui/modals/DealActionsModal.d.ts +51 -0
- package/dist/ui/modals/DealActionsModal.d.ts.map +1 -0
- package/dist/ui/modals/DealActionsModal.js +372 -0
- package/dist/ui/modals/DealActionsModal.js.map +1 -0
- package/dist/ui/modals/index.d.ts +3 -0
- package/dist/ui/modals/index.js +4 -0
- package/dist/ui/overlays/demo-overlays.d.ts +19 -0
- package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
- package/dist/ui/overlays/demo-overlays.js +68 -0
- package/dist/ui/overlays/demo-overlays.js.map +1 -0
- package/dist/ui/overlays/index.d.ts +2 -0
- package/dist/ui/overlays/index.js +3 -0
- package/dist/ui/renderers/index.d.ts +3 -0
- package/dist/ui/renderers/index.js +4 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts +23 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.markdown.js +118 -0
- package/dist/ui/renderers/pipeline.markdown.js.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts +9 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.js +28 -0
- package/dist/ui/renderers/pipeline.renderer.js.map +1 -0
- package/package.json +38 -13
- package/src/crm-pipeline.feature.ts +3 -3
- package/src/deal/deal.test-spec.ts +55 -0
- package/src/example.ts +3 -3
- package/src/handlers/crm.handlers.ts +415 -0
- package/src/handlers/index.ts +3 -0
- package/src/index.ts +1 -0
- package/src/presentations/dashboard.presentation.ts +5 -6
- package/src/presentations/pipeline.presentation.ts +9 -10
- package/src/seeders/index.ts +35 -0
- package/src/shared/overlay-types.ts +39 -0
- package/src/ui/CrmDashboard.tsx +311 -0
- package/src/ui/CrmDealCard.tsx +83 -0
- package/src/ui/CrmPipelineBoard.tsx +136 -0
- package/src/ui/hooks/index.ts +10 -0
- package/src/ui/hooks/useDealList.ts +113 -0
- package/src/ui/hooks/useDealMutations.ts +174 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/modals/CreateDealModal.tsx +239 -0
- package/src/ui/modals/DealActionsModal.tsx +424 -0
- package/src/ui/modals/index.ts +2 -0
- package/src/ui/overlays/demo-overlays.ts +68 -0
- package/src/ui/overlays/index.ts +1 -0
- package/src/ui/renderers/index.ts +6 -0
- package/src/ui/renderers/pipeline.markdown.ts +198 -0
- package/src/ui/renderers/pipeline.renderer.tsx +35 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { DealModel } from "../deal/deal.schema.js";
|
|
2
|
-
import { StabilityEnum } from "@contractspec/lib.contracts";
|
|
2
|
+
import { StabilityEnum, definePresentation } from "@contractspec/lib.contracts";
|
|
3
3
|
|
|
4
4
|
//#region src/presentations/pipeline.presentation.ts
|
|
5
5
|
/**
|
|
6
|
+
* Pipeline Presentation Descriptors
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
6
9
|
* Kanban board presentation for the sales pipeline.
|
|
7
10
|
*/
|
|
8
|
-
const PipelineKanbanPresentation = {
|
|
11
|
+
const PipelineKanbanPresentation = definePresentation({
|
|
9
12
|
meta: {
|
|
10
13
|
key: "crm.pipeline.kanban",
|
|
11
14
|
version: "1.0.0",
|
|
@@ -30,11 +33,11 @@ const PipelineKanbanPresentation = {
|
|
|
30
33
|
},
|
|
31
34
|
targets: ["react", "markdown"],
|
|
32
35
|
policy: { flags: ["crm.pipeline.enabled"] }
|
|
33
|
-
};
|
|
36
|
+
});
|
|
34
37
|
/**
|
|
35
38
|
* List view of deals with filtering.
|
|
36
39
|
*/
|
|
37
|
-
const DealListPresentation = {
|
|
40
|
+
const DealListPresentation = definePresentation({
|
|
38
41
|
meta: {
|
|
39
42
|
key: "crm.deal.list",
|
|
40
43
|
version: "1.0.0",
|
|
@@ -59,11 +62,11 @@ const DealListPresentation = {
|
|
|
59
62
|
"application/json"
|
|
60
63
|
],
|
|
61
64
|
policy: { flags: ["crm.deals.enabled"] }
|
|
62
|
-
};
|
|
65
|
+
});
|
|
63
66
|
/**
|
|
64
67
|
* Deal detail presentation.
|
|
65
68
|
*/
|
|
66
|
-
const DealDetailPresentation = {
|
|
69
|
+
const DealDetailPresentation = definePresentation({
|
|
67
70
|
meta: {
|
|
68
71
|
key: "crm.deal.detail",
|
|
69
72
|
version: "1.0.0",
|
|
@@ -83,11 +86,11 @@ const DealDetailPresentation = {
|
|
|
83
86
|
},
|
|
84
87
|
targets: ["react", "markdown"],
|
|
85
88
|
policy: { flags: ["crm.deals.enabled"] }
|
|
86
|
-
};
|
|
89
|
+
});
|
|
87
90
|
/**
|
|
88
91
|
* Deal card for kanban board.
|
|
89
92
|
*/
|
|
90
|
-
const DealCardPresentation = {
|
|
93
|
+
const DealCardPresentation = definePresentation({
|
|
91
94
|
meta: {
|
|
92
95
|
key: "crm.deal.card",
|
|
93
96
|
version: "1.0.0",
|
|
@@ -112,7 +115,7 @@ const DealCardPresentation = {
|
|
|
112
115
|
},
|
|
113
116
|
targets: ["react"],
|
|
114
117
|
policy: { flags: ["crm.deals.enabled"] }
|
|
115
|
-
};
|
|
118
|
+
});
|
|
116
119
|
|
|
117
120
|
//#endregion
|
|
118
121
|
export { DealCardPresentation, DealDetailPresentation, DealListPresentation, PipelineKanbanPresentation };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.presentation.js","names":[
|
|
1
|
+
{"version":3,"file":"pipeline.presentation.js","names":[],"sources":["../../src/presentations/pipeline.presentation.ts"],"sourcesContent":["/**\n * Pipeline Presentation Descriptors\n */\nimport { definePresentation, StabilityEnum } from '@contractspec/lib.contracts';\nimport { DealModel } from '../deal/deal.schema';\n\n/**\n * Kanban board presentation for the sales pipeline.\n */\nexport const PipelineKanbanPresentation = definePresentation({\n meta: {\n key: 'crm.pipeline.kanban',\n version: '1.0.0',\n title: 'Pipeline Kanban',\n description: 'Kanban board view of deals organized by stage',\n domain: 'crm-pipeline',\n owners: ['@crm-team'],\n tags: ['pipeline', 'kanban', 'deals'],\n stability: StabilityEnum.Experimental,\n goal: 'Visualize the sales pipeline status and deal distribution across stages.',\n context: 'Used in the sales dashboard and management reports.',\n },\n source: {\n type: 'component',\n framework: 'react',\n componentKey: 'PipelineKanbanView',\n props: DealModel,\n },\n targets: ['react', 'markdown'],\n policy: {\n flags: ['crm.pipeline.enabled'],\n },\n});\n\n/**\n * List view of deals with filtering.\n */\nexport const DealListPresentation = definePresentation({\n meta: {\n key: 'crm.deal.list',\n version: '1.0.0',\n title: 'Deal List',\n description: 'List view of deals with value, status, and owner info',\n domain: 'crm-pipeline',\n owners: ['@crm-team'],\n tags: ['deal', 'list'],\n stability: StabilityEnum.Experimental,\n goal: 'Search, filter, and review deal lists.',\n context: 'Standard view for deal management and bulk actions.',\n },\n source: {\n type: 'component',\n framework: 'react',\n componentKey: 'DealListView',\n props: DealModel,\n },\n targets: ['react', 'markdown', 'application/json'],\n policy: {\n flags: ['crm.deals.enabled'],\n },\n});\n\n/**\n * Deal detail presentation.\n */\nexport const DealDetailPresentation = definePresentation({\n meta: {\n key: 'crm.deal.detail',\n version: '1.0.0',\n title: 'Deal Details',\n description:\n 'Detailed view of a deal with activities, contacts, and history',\n domain: 'crm-pipeline',\n owners: ['@crm-team'],\n tags: ['deal', 'detail'],\n stability: StabilityEnum.Experimental,\n goal: 'Deep dive into deal details and historical activities.',\n context: 'The main workspace for managing a single deal execution.',\n },\n source: {\n type: 'component',\n framework: 'react',\n componentKey: 'DealDetailView',\n },\n targets: ['react', 'markdown'],\n policy: {\n flags: ['crm.deals.enabled'],\n },\n});\n\n/**\n * Deal card for kanban board.\n */\nexport const DealCardPresentation = definePresentation({\n meta: {\n key: 'crm.deal.card',\n version: '1.0.0',\n title: 'Deal Card',\n description: 'Compact deal card for kanban board display',\n domain: 'crm-pipeline',\n owners: ['@crm-team'],\n tags: ['deal', 'card', 'kanban'],\n stability: StabilityEnum.Experimental,\n goal: 'Provide a quick overview of deal status in the pipeline view.',\n context: 'Condensed representation used within the Pipeline Kanban board.',\n },\n source: {\n type: 'component',\n framework: 'react',\n componentKey: 'DealCard',\n props: DealModel,\n },\n targets: ['react'],\n policy: {\n flags: ['crm.deals.enabled'],\n },\n});\n"],"mappings":";;;;;;;;;;AASA,MAAa,6BAA6B,mBAAmB;CAC3D,MAAM;EACJ,KAAK;EACL,SAAS;EACT,OAAO;EACP,aAAa;EACb,QAAQ;EACR,QAAQ,CAAC,YAAY;EACrB,MAAM;GAAC;GAAY;GAAU;GAAQ;EACrC,WAAW,cAAc;EACzB,MAAM;EACN,SAAS;EACV;CACD,QAAQ;EACN,MAAM;EACN,WAAW;EACX,cAAc;EACd,OAAO;EACR;CACD,SAAS,CAAC,SAAS,WAAW;CAC9B,QAAQ,EACN,OAAO,CAAC,uBAAuB,EAChC;CACF,CAAC;;;;AAKF,MAAa,uBAAuB,mBAAmB;CACrD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,OAAO;EACP,aAAa;EACb,QAAQ;EACR,QAAQ,CAAC,YAAY;EACrB,MAAM,CAAC,QAAQ,OAAO;EACtB,WAAW,cAAc;EACzB,MAAM;EACN,SAAS;EACV;CACD,QAAQ;EACN,MAAM;EACN,WAAW;EACX,cAAc;EACd,OAAO;EACR;CACD,SAAS;EAAC;EAAS;EAAY;EAAmB;CAClD,QAAQ,EACN,OAAO,CAAC,oBAAoB,EAC7B;CACF,CAAC;;;;AAKF,MAAa,yBAAyB,mBAAmB;CACvD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,OAAO;EACP,aACE;EACF,QAAQ;EACR,QAAQ,CAAC,YAAY;EACrB,MAAM,CAAC,QAAQ,SAAS;EACxB,WAAW,cAAc;EACzB,MAAM;EACN,SAAS;EACV;CACD,QAAQ;EACN,MAAM;EACN,WAAW;EACX,cAAc;EACf;CACD,SAAS,CAAC,SAAS,WAAW;CAC9B,QAAQ,EACN,OAAO,CAAC,oBAAoB,EAC7B;CACF,CAAC;;;;AAKF,MAAa,uBAAuB,mBAAmB;CACrD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,OAAO;EACP,aAAa;EACb,QAAQ;EACR,QAAQ,CAAC,YAAY;EACrB,MAAM;GAAC;GAAQ;GAAQ;GAAS;EAChC,WAAW,cAAc;EACzB,MAAM;EACN,SAAS;EACV;CACD,QAAQ;EACN,MAAM;EACN,WAAW;EACX,cAAc;EACd,OAAO;EACR;CACD,SAAS,CAAC,QAAQ;CAClB,QAAQ,EACN,OAAO,CAAC,oBAAoB,EAC7B;CACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DatabasePort } from "@contractspec/lib.runtime-sandbox";
|
|
2
|
+
|
|
3
|
+
//#region src/seeders/index.d.ts
|
|
4
|
+
declare function seedCrmPipeline(params: {
|
|
5
|
+
projectId: string;
|
|
6
|
+
db: DatabasePort;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { seedCrmPipeline };
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/seeders/index.ts"],"sourcesContent":[],"mappings":";;;iBAEsB,eAAA;;EAAA,EAAA,EAEhB,YAFgB;IAGrB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/seeders/index.ts
|
|
2
|
+
async function seedCrmPipeline(params) {
|
|
3
|
+
const { projectId, db } = params;
|
|
4
|
+
if ((await db.query(`SELECT COUNT(*) as count FROM crm_pipeline WHERE "projectId" = $1`, [projectId])).rows[0]?.count > 0) return;
|
|
5
|
+
const pipelineId = "pipeline_sales";
|
|
6
|
+
await db.execute(`INSERT INTO crm_pipeline (id, "projectId", name) VALUES ($1, $2, $3)`, [
|
|
7
|
+
pipelineId,
|
|
8
|
+
projectId,
|
|
9
|
+
"Sales Pipeline"
|
|
10
|
+
]);
|
|
11
|
+
for (const stage of [
|
|
12
|
+
{
|
|
13
|
+
id: "stage_lead",
|
|
14
|
+
name: "Lead",
|
|
15
|
+
position: 1
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "stage_contact",
|
|
19
|
+
name: "Contact Made",
|
|
20
|
+
position: 2
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "stage_proposal",
|
|
24
|
+
name: "Proposal",
|
|
25
|
+
position: 3
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "stage_negotiation",
|
|
29
|
+
name: "Negotiation",
|
|
30
|
+
position: 4
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "stage_closed",
|
|
34
|
+
name: "Closed",
|
|
35
|
+
position: 5
|
|
36
|
+
}
|
|
37
|
+
]) await db.execute(`INSERT INTO crm_stage (id, "pipelineId", name, position) VALUES ($1, $2, $3, $4)`, [
|
|
38
|
+
stage.id,
|
|
39
|
+
pipelineId,
|
|
40
|
+
stage.name,
|
|
41
|
+
stage.position
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { seedCrmPipeline };
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/seeders/index.ts"],"sourcesContent":["import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';\n\nexport async function seedCrmPipeline(params: {\n projectId: string;\n db: DatabasePort;\n}) {\n const { projectId, db } = params;\n\n const existing = await db.query(\n `SELECT COUNT(*) as count FROM crm_pipeline WHERE \"projectId\" = $1`,\n [projectId]\n );\n if ((existing.rows[0]?.count as number) > 0) return;\n\n const pipelineId = 'pipeline_sales';\n await db.execute(\n `INSERT INTO crm_pipeline (id, \"projectId\", name) VALUES ($1, $2, $3)`,\n [pipelineId, projectId, 'Sales Pipeline']\n );\n\n const stages = [\n { id: 'stage_lead', name: 'Lead', position: 1 },\n { id: 'stage_contact', name: 'Contact Made', position: 2 },\n { id: 'stage_proposal', name: 'Proposal', position: 3 },\n { id: 'stage_negotiation', name: 'Negotiation', position: 4 },\n { id: 'stage_closed', name: 'Closed', position: 5 },\n ];\n\n for (const stage of stages) {\n await db.execute(\n `INSERT INTO crm_stage (id, \"pipelineId\", name, position) VALUES ($1, $2, $3, $4)`,\n [stage.id, pipelineId, stage.name, stage.position]\n );\n }\n}\n"],"mappings":";AAEA,eAAsB,gBAAgB,QAGnC;CACD,MAAM,EAAE,WAAW,OAAO;AAM1B,MAJiB,MAAM,GAAG,MACxB,qEACA,CAAC,UAAU,CACZ,EACa,KAAK,IAAI,QAAmB,EAAG;CAE7C,MAAM,aAAa;AACnB,OAAM,GAAG,QACP,wEACA;EAAC;EAAY;EAAW;EAAiB,CAC1C;AAUD,MAAK,MAAM,SARI;EACb;GAAE,IAAI;GAAc,MAAM;GAAQ,UAAU;GAAG;EAC/C;GAAE,IAAI;GAAiB,MAAM;GAAgB,UAAU;GAAG;EAC1D;GAAE,IAAI;GAAkB,MAAM;GAAY,UAAU;GAAG;EACvD;GAAE,IAAI;GAAqB,MAAM;GAAe,UAAU;GAAG;EAC7D;GAAE,IAAI;GAAgB,MAAM;GAAU,UAAU;GAAG;EACpD,CAGC,OAAM,GAAG,QACP,oFACA;EAAC,MAAM;EAAI;EAAY,MAAM;EAAM,MAAM;EAAS,CACnD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/shared/overlay-types.d.ts
|
|
2
|
+
interface OverlayDefinition {
|
|
3
|
+
overlayId: string;
|
|
4
|
+
version: string;
|
|
5
|
+
description: string;
|
|
6
|
+
appliesTo: Record<string, string>;
|
|
7
|
+
modifications: OverlayModification[];
|
|
8
|
+
}
|
|
9
|
+
type OverlayModification = HideFieldModification | RenameLabelModification | AddBadgeModification | SetLimitModification;
|
|
10
|
+
interface HideFieldModification {
|
|
11
|
+
type: 'hideField';
|
|
12
|
+
field: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
interface RenameLabelModification {
|
|
16
|
+
type: 'renameLabel';
|
|
17
|
+
field: string;
|
|
18
|
+
newLabel: string;
|
|
19
|
+
}
|
|
20
|
+
interface AddBadgeModification {
|
|
21
|
+
type: 'addBadge';
|
|
22
|
+
position: 'header' | 'footer';
|
|
23
|
+
label: string;
|
|
24
|
+
variant: 'warning' | 'info' | 'error' | 'success' | 'default';
|
|
25
|
+
}
|
|
26
|
+
interface SetLimitModification {
|
|
27
|
+
type: 'setLimit';
|
|
28
|
+
field: string;
|
|
29
|
+
max: number;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { AddBadgeModification, HideFieldModification, OverlayDefinition, OverlayModification, RenameLabelModification, SetLimitModification };
|
|
34
|
+
//# sourceMappingURL=overlay-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-types.d.ts","names":[],"sources":["../../src/shared/overlay-types.ts"],"sourcesContent":[],"mappings":";UAAiB,iBAAA;EAAA,SAAA,EAAA,MAAA;EAQL,OAAA,EAAA,MAAA;EACR,WAAA,EAAA,MAAA;EACA,SAAA,EANS,MAMT,CAAA,MAAA,EAAA,MAAA,CAAA;EACA,aAAA,EANa,mBAMb,EAAA;;AACoB,KAJZ,mBAAA,GACR,qBAGoB,GAFpB,uBAEoB,GADpB,oBACoB,GAApB,oBAAoB;AAEP,UAAA,qBAAA,CAAqB;EAMrB,IAAA,EAAA,WAAA;EAMA,KAAA,EAAA,MAAA;EAOA,MAAA,CAAA,EAAA,MAAA;;UAbA,uBAAA;;;;;UAMA,oBAAA;;;;;;UAOA,oBAAA"}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CrmDashboard.d.ts","names":[],"sources":["../../src/ui/CrmDashboard.tsx"],"sourcesContent":[],"mappings":";;;iBA6CgB,YAAA,CAAA,GAAY,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useDealList } from "./hooks/useDealList.js";
|
|
4
|
+
import { useDealMutations } from "./hooks/useDealMutations.js";
|
|
5
|
+
import { CrmPipelineBoard } from "./CrmPipelineBoard.js";
|
|
6
|
+
import { CreateDealModal } from "./modals/CreateDealModal.js";
|
|
7
|
+
import { DealActionsModal } from "./modals/DealActionsModal.js";
|
|
8
|
+
import { useCallback, useState } from "react";
|
|
9
|
+
import { Button, ErrorState, LoaderBlock, StatCard, StatCardGroup } from "@contractspec/lib.design-system";
|
|
10
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@contractspec/lib.ui-kit-web/ui/tabs";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
|
|
13
|
+
//#region src/ui/CrmDashboard.tsx
|
|
14
|
+
/**
|
|
15
|
+
* CRM Dashboard
|
|
16
|
+
*
|
|
17
|
+
* Fully integrated with ContractSpec example handlers
|
|
18
|
+
* and design-system components.
|
|
19
|
+
*
|
|
20
|
+
* Commands wired:
|
|
21
|
+
* - CreateDealContract -> Create Deal button + modal
|
|
22
|
+
* - MoveDealContract -> Move deal between stages
|
|
23
|
+
* - WinDealContract -> Mark deal as won
|
|
24
|
+
* - LoseDealContract -> Mark deal as lost
|
|
25
|
+
*/
|
|
26
|
+
function formatCurrency(value, currency = "USD") {
|
|
27
|
+
return new Intl.NumberFormat("en-US", {
|
|
28
|
+
style: "currency",
|
|
29
|
+
currency,
|
|
30
|
+
minimumFractionDigits: 0,
|
|
31
|
+
maximumFractionDigits: 0
|
|
32
|
+
}).format(value);
|
|
33
|
+
}
|
|
34
|
+
function CrmDashboard() {
|
|
35
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
36
|
+
const [selectedDeal, setSelectedDeal] = useState(null);
|
|
37
|
+
const [isDealActionsOpen, setIsDealActionsOpen] = useState(false);
|
|
38
|
+
const { data, dealsByStage, stages, loading, error, stats, refetch } = useDealList();
|
|
39
|
+
const mutations = useDealMutations({ onSuccess: () => {
|
|
40
|
+
refetch();
|
|
41
|
+
} });
|
|
42
|
+
const handleDealClick = useCallback((dealId) => {
|
|
43
|
+
const deal = dealsByStage ? Object.values(dealsByStage).flat().find((d) => d.id === dealId) : null;
|
|
44
|
+
if (deal) {
|
|
45
|
+
setSelectedDeal(deal);
|
|
46
|
+
setIsDealActionsOpen(true);
|
|
47
|
+
}
|
|
48
|
+
}, [dealsByStage]);
|
|
49
|
+
const handleDealMove = useCallback(async (dealId, toStageId) => {
|
|
50
|
+
await mutations.moveDeal({
|
|
51
|
+
dealId,
|
|
52
|
+
stageId: toStageId
|
|
53
|
+
});
|
|
54
|
+
}, [mutations]);
|
|
55
|
+
if (loading && !data) return /* @__PURE__ */ jsx(LoaderBlock, { label: "Loading CRM..." });
|
|
56
|
+
if (error) return /* @__PURE__ */ jsx(ErrorState, {
|
|
57
|
+
title: "Failed to load CRM",
|
|
58
|
+
description: error.message,
|
|
59
|
+
onRetry: refetch,
|
|
60
|
+
retryLabel: "Retry"
|
|
61
|
+
});
|
|
62
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
63
|
+
className: "space-y-6",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsxs("div", {
|
|
66
|
+
className: "flex items-center justify-between",
|
|
67
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
68
|
+
className: "text-2xl font-bold",
|
|
69
|
+
children: "CRM Pipeline"
|
|
70
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
71
|
+
onClick: () => setIsCreateModalOpen(true),
|
|
72
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
73
|
+
className: "mr-2",
|
|
74
|
+
children: "+"
|
|
75
|
+
}), " Create Deal"]
|
|
76
|
+
})]
|
|
77
|
+
}),
|
|
78
|
+
stats && /* @__PURE__ */ jsxs(StatCardGroup, { children: [
|
|
79
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
80
|
+
label: "Total Pipeline",
|
|
81
|
+
value: formatCurrency(stats.totalValue),
|
|
82
|
+
hint: `${stats.total} deals`
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
85
|
+
label: "Open Deals",
|
|
86
|
+
value: formatCurrency(stats.openValue),
|
|
87
|
+
hint: `${stats.openCount} active`
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
90
|
+
label: "Won",
|
|
91
|
+
value: formatCurrency(stats.wonValue),
|
|
92
|
+
hint: `${stats.wonCount} closed`
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
95
|
+
label: "Lost",
|
|
96
|
+
value: stats.lostCount,
|
|
97
|
+
hint: "deals lost"
|
|
98
|
+
})
|
|
99
|
+
] }),
|
|
100
|
+
/* @__PURE__ */ jsxs(Tabs, {
|
|
101
|
+
defaultValue: "pipeline",
|
|
102
|
+
className: "w-full",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
105
|
+
/* @__PURE__ */ jsxs(TabsTrigger, {
|
|
106
|
+
value: "pipeline",
|
|
107
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
108
|
+
className: "mr-2",
|
|
109
|
+
children: "📊"
|
|
110
|
+
}), "Pipeline"]
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ jsxs(TabsTrigger, {
|
|
113
|
+
value: "list",
|
|
114
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
115
|
+
className: "mr-2",
|
|
116
|
+
children: "📋"
|
|
117
|
+
}), "All Deals"]
|
|
118
|
+
}),
|
|
119
|
+
/* @__PURE__ */ jsxs(TabsTrigger, {
|
|
120
|
+
value: "metrics",
|
|
121
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
122
|
+
className: "mr-2",
|
|
123
|
+
children: "📈"
|
|
124
|
+
}), "Metrics"]
|
|
125
|
+
})
|
|
126
|
+
] }),
|
|
127
|
+
/* @__PURE__ */ jsx(TabsContent, {
|
|
128
|
+
value: "pipeline",
|
|
129
|
+
className: "min-h-[400px]",
|
|
130
|
+
children: /* @__PURE__ */ jsx(CrmPipelineBoard, {
|
|
131
|
+
dealsByStage,
|
|
132
|
+
stages,
|
|
133
|
+
onDealClick: handleDealClick,
|
|
134
|
+
onDealMove: handleDealMove
|
|
135
|
+
})
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ jsx(TabsContent, {
|
|
138
|
+
value: "list",
|
|
139
|
+
className: "min-h-[400px]",
|
|
140
|
+
children: /* @__PURE__ */ jsx(DealListTab, {
|
|
141
|
+
data,
|
|
142
|
+
onDealClick: handleDealClick
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ jsx(TabsContent, {
|
|
146
|
+
value: "metrics",
|
|
147
|
+
className: "min-h-[400px]",
|
|
148
|
+
children: /* @__PURE__ */ jsx(MetricsTab, { stats })
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ jsx(CreateDealModal, {
|
|
153
|
+
isOpen: isCreateModalOpen,
|
|
154
|
+
onClose: () => setIsCreateModalOpen(false),
|
|
155
|
+
onSubmit: async (input) => {
|
|
156
|
+
await mutations.createDeal(input);
|
|
157
|
+
},
|
|
158
|
+
stages,
|
|
159
|
+
isLoading: mutations.createState.loading
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ jsx(DealActionsModal, {
|
|
162
|
+
isOpen: isDealActionsOpen,
|
|
163
|
+
deal: selectedDeal,
|
|
164
|
+
stages,
|
|
165
|
+
onClose: () => {
|
|
166
|
+
setIsDealActionsOpen(false);
|
|
167
|
+
setSelectedDeal(null);
|
|
168
|
+
},
|
|
169
|
+
onWin: async (input) => {
|
|
170
|
+
await mutations.winDeal(input);
|
|
171
|
+
},
|
|
172
|
+
onLose: async (input) => {
|
|
173
|
+
await mutations.loseDeal(input);
|
|
174
|
+
},
|
|
175
|
+
onMove: async (input) => {
|
|
176
|
+
await mutations.moveDeal(input);
|
|
177
|
+
refetch();
|
|
178
|
+
},
|
|
179
|
+
isLoading: mutations.isLoading
|
|
180
|
+
})
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function DealListTab({ data, onDealClick }) {
|
|
185
|
+
if (!data?.deals.length) return /* @__PURE__ */ jsx("div", {
|
|
186
|
+
className: "text-muted-foreground flex h-64 items-center justify-center",
|
|
187
|
+
children: "No deals found"
|
|
188
|
+
});
|
|
189
|
+
return /* @__PURE__ */ jsx("div", {
|
|
190
|
+
className: "border-border rounded-lg border",
|
|
191
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
192
|
+
className: "w-full",
|
|
193
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
194
|
+
className: "border-border bg-muted/30 border-b",
|
|
195
|
+
children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
196
|
+
/* @__PURE__ */ jsx("th", {
|
|
197
|
+
className: "px-4 py-3 text-left text-sm font-medium",
|
|
198
|
+
children: "Deal"
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ jsx("th", {
|
|
201
|
+
className: "px-4 py-3 text-left text-sm font-medium",
|
|
202
|
+
children: "Value"
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ jsx("th", {
|
|
205
|
+
className: "px-4 py-3 text-left text-sm font-medium",
|
|
206
|
+
children: "Status"
|
|
207
|
+
}),
|
|
208
|
+
/* @__PURE__ */ jsx("th", {
|
|
209
|
+
className: "px-4 py-3 text-left text-sm font-medium",
|
|
210
|
+
children: "Expected Close"
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ jsx("th", {
|
|
213
|
+
className: "px-4 py-3 text-left text-sm font-medium",
|
|
214
|
+
children: "Actions"
|
|
215
|
+
})
|
|
216
|
+
] })
|
|
217
|
+
}), /* @__PURE__ */ jsx("tbody", {
|
|
218
|
+
className: "divide-border divide-y",
|
|
219
|
+
children: data.deals.map((deal) => /* @__PURE__ */ jsxs("tr", {
|
|
220
|
+
className: "hover:bg-muted/50",
|
|
221
|
+
children: [
|
|
222
|
+
/* @__PURE__ */ jsx("td", {
|
|
223
|
+
className: "px-4 py-3",
|
|
224
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
225
|
+
className: "font-medium",
|
|
226
|
+
children: deal.name
|
|
227
|
+
})
|
|
228
|
+
}),
|
|
229
|
+
/* @__PURE__ */ jsx("td", {
|
|
230
|
+
className: "px-4 py-3 font-mono",
|
|
231
|
+
children: formatCurrency(deal.value, deal.currency)
|
|
232
|
+
}),
|
|
233
|
+
/* @__PURE__ */ jsx("td", {
|
|
234
|
+
className: "px-4 py-3",
|
|
235
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
236
|
+
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
237
|
+
children: deal.status
|
|
238
|
+
})
|
|
239
|
+
}),
|
|
240
|
+
/* @__PURE__ */ jsx("td", {
|
|
241
|
+
className: "text-muted-foreground px-4 py-3",
|
|
242
|
+
children: deal.expectedCloseDate?.toLocaleDateString() ?? "-"
|
|
243
|
+
}),
|
|
244
|
+
/* @__PURE__ */ jsx("td", {
|
|
245
|
+
className: "px-4 py-3",
|
|
246
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
247
|
+
variant: "ghost",
|
|
248
|
+
size: "sm",
|
|
249
|
+
onPress: () => onDealClick?.(deal.id),
|
|
250
|
+
children: "Actions"
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
]
|
|
254
|
+
}, deal.id))
|
|
255
|
+
})]
|
|
256
|
+
})
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function MetricsTab({ stats }) {
|
|
260
|
+
if (!stats) return null;
|
|
261
|
+
return /* @__PURE__ */ jsx("div", {
|
|
262
|
+
className: "space-y-6",
|
|
263
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
264
|
+
className: "border-border bg-card rounded-xl border p-6",
|
|
265
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
266
|
+
className: "mb-4 text-lg font-semibold",
|
|
267
|
+
children: "Pipeline Overview"
|
|
268
|
+
}), /* @__PURE__ */ jsxs("dl", {
|
|
269
|
+
className: "grid gap-4 sm:grid-cols-3",
|
|
270
|
+
children: [
|
|
271
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
272
|
+
className: "text-muted-foreground text-sm",
|
|
273
|
+
children: "Win Rate"
|
|
274
|
+
}), /* @__PURE__ */ jsxs("dd", {
|
|
275
|
+
className: "text-2xl font-semibold",
|
|
276
|
+
children: [stats.total > 0 ? (stats.wonCount / stats.total * 100).toFixed(0) : 0, "%"]
|
|
277
|
+
})] }),
|
|
278
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
279
|
+
className: "text-muted-foreground text-sm",
|
|
280
|
+
children: "Avg Deal Size"
|
|
281
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
282
|
+
className: "text-2xl font-semibold",
|
|
283
|
+
children: formatCurrency(stats.total > 0 ? stats.totalValue / stats.total : 0)
|
|
284
|
+
})] }),
|
|
285
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
286
|
+
className: "text-muted-foreground text-sm",
|
|
287
|
+
children: "Conversion"
|
|
288
|
+
}), /* @__PURE__ */ jsxs("dd", {
|
|
289
|
+
className: "text-2xl font-semibold",
|
|
290
|
+
children: [
|
|
291
|
+
stats.wonCount,
|
|
292
|
+
" / ",
|
|
293
|
+
stats.total
|
|
294
|
+
]
|
|
295
|
+
})] })
|
|
296
|
+
]
|
|
297
|
+
})]
|
|
298
|
+
})
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
export { CrmDashboard };
|
|
304
|
+
//# sourceMappingURL=CrmDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CrmDashboard.js","names":[],"sources":["../../src/ui/CrmDashboard.tsx"],"sourcesContent":["'use client';\n\n/**\n * CRM Dashboard\n *\n * Fully integrated with ContractSpec example handlers\n * and design-system components.\n *\n * Commands wired:\n * - CreateDealContract -> Create Deal button + modal\n * - MoveDealContract -> Move deal between stages\n * - WinDealContract -> Mark deal as won\n * - LoseDealContract -> Mark deal as lost\n */\nimport { useCallback, useState } from 'react';\nimport {\n Button,\n ErrorState,\n LoaderBlock,\n StatCard,\n StatCardGroup,\n} from '@contractspec/lib.design-system';\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@contractspec/lib.ui-kit-web/ui/tabs';\nimport { type Deal, useDealList } from './hooks/useDealList';\nimport { useDealMutations } from './hooks/useDealMutations';\nimport { CrmPipelineBoard } from './CrmPipelineBoard';\nimport { CreateDealModal } from './modals/CreateDealModal';\nimport { DealActionsModal } from './modals/DealActionsModal';\n\n// type Tab = 'pipeline' | 'list' | 'metrics';\n\nfunction formatCurrency(value: number, currency = 'USD'): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }).format(value);\n}\n\nexport function CrmDashboard() {\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [selectedDeal, setSelectedDeal] = useState<Deal | null>(null);\n const [isDealActionsOpen, setIsDealActionsOpen] = useState(false);\n\n const { data, dealsByStage, stages, loading, error, stats, refetch } =\n useDealList();\n\n const mutations = useDealMutations({\n onSuccess: () => {\n refetch();\n },\n });\n\n const handleDealClick = useCallback(\n (dealId: string) => {\n // Find deal in data\n const deal = dealsByStage\n ? Object.values(dealsByStage)\n .flat()\n .find((d) => d.id === dealId)\n : null;\n\n if (deal) {\n setSelectedDeal(deal);\n setIsDealActionsOpen(true);\n }\n },\n [dealsByStage]\n );\n\n const handleDealMove = useCallback(\n async (dealId: string, toStageId: string) => {\n await mutations.moveDeal({ dealId, stageId: toStageId });\n },\n [mutations]\n );\n\n if (loading && !data) {\n return <LoaderBlock label=\"Loading CRM...\" />;\n }\n\n if (error) {\n return (\n <ErrorState\n title=\"Failed to load CRM\"\n description={error.message}\n onRetry={refetch}\n retryLabel=\"Retry\"\n />\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Header with Create Button */}\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-2xl font-bold\">CRM Pipeline</h2>\n <Button onClick={() => setIsCreateModalOpen(true)}>\n <span className=\"mr-2\">+</span> Create Deal\n </Button>\n </div>\n\n {/* Stats Row */}\n {stats && (\n <StatCardGroup>\n <StatCard\n label=\"Total Pipeline\"\n value={formatCurrency(stats.totalValue)}\n hint={`${stats.total} deals`}\n />\n <StatCard\n label=\"Open Deals\"\n value={formatCurrency(stats.openValue)}\n hint={`${stats.openCount} active`}\n />\n <StatCard\n label=\"Won\"\n value={formatCurrency(stats.wonValue)}\n hint={`${stats.wonCount} closed`}\n />\n <StatCard label=\"Lost\" value={stats.lostCount} hint=\"deals lost\" />\n </StatCardGroup>\n )}\n\n {/* Tabs */}\n <Tabs defaultValue=\"pipeline\" className=\"w-full\">\n <TabsList>\n <TabsTrigger value=\"pipeline\">\n <span className=\"mr-2\">📊</span>\n Pipeline\n </TabsTrigger>\n <TabsTrigger value=\"list\">\n <span className=\"mr-2\">📋</span>\n All Deals\n </TabsTrigger>\n <TabsTrigger value=\"metrics\">\n <span className=\"mr-2\">📈</span>\n Metrics\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"pipeline\" className=\"min-h-[400px]\">\n <CrmPipelineBoard\n dealsByStage={dealsByStage}\n stages={stages}\n onDealClick={handleDealClick}\n onDealMove={handleDealMove}\n />\n </TabsContent>\n\n <TabsContent value=\"list\" className=\"min-h-[400px]\">\n <DealListTab data={data} onDealClick={handleDealClick} />\n </TabsContent>\n\n <TabsContent value=\"metrics\" className=\"min-h-[400px]\">\n <MetricsTab stats={stats} />\n </TabsContent>\n </Tabs>\n\n {/* Create Deal Modal */}\n <CreateDealModal\n isOpen={isCreateModalOpen}\n onClose={() => setIsCreateModalOpen(false)}\n onSubmit={async (input) => {\n await mutations.createDeal(input);\n }}\n stages={stages}\n isLoading={mutations.createState.loading}\n />\n\n {/* Deal Actions Modal */}\n <DealActionsModal\n isOpen={isDealActionsOpen}\n deal={selectedDeal}\n stages={stages}\n onClose={() => {\n setIsDealActionsOpen(false);\n setSelectedDeal(null);\n }}\n onWin={async (input) => {\n await mutations.winDeal(input);\n }}\n onLose={async (input) => {\n await mutations.loseDeal(input);\n }}\n onMove={async (input) => {\n await mutations.moveDeal(input);\n refetch();\n }}\n isLoading={mutations.isLoading}\n />\n </div>\n );\n}\n\ninterface DealListTabProps {\n data: ReturnType<typeof useDealList>['data'];\n onDealClick?: (dealId: string) => void;\n}\n\nfunction DealListTab({ data, onDealClick }: DealListTabProps) {\n if (!data?.deals.length) {\n return (\n <div className=\"text-muted-foreground flex h-64 items-center justify-center\">\n No deals found\n </div>\n );\n }\n\n return (\n <div className=\"border-border rounded-lg border\">\n <table className=\"w-full\">\n <thead className=\"border-border bg-muted/30 border-b\">\n <tr>\n <th className=\"px-4 py-3 text-left text-sm font-medium\">Deal</th>\n <th className=\"px-4 py-3 text-left text-sm font-medium\">Value</th>\n <th className=\"px-4 py-3 text-left text-sm font-medium\">Status</th>\n <th className=\"px-4 py-3 text-left text-sm font-medium\">\n Expected Close\n </th>\n <th className=\"px-4 py-3 text-left text-sm font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-border divide-y\">\n {data.deals.map((deal: Deal) => (\n <tr key={deal.id} className=\"hover:bg-muted/50\">\n <td className=\"px-4 py-3\">\n <div className=\"font-medium\">{deal.name}</div>\n </td>\n <td className=\"px-4 py-3 font-mono\">\n {formatCurrency(deal.value, deal.currency)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${\n deal.status === 'WON'\n ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'\n : deal.status === 'LOST'\n ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'\n : 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'\n }`}\n >\n {deal.status}\n </span>\n </td>\n <td className=\"text-muted-foreground px-4 py-3\">\n {deal.expectedCloseDate?.toLocaleDateString() ?? '-'}\n </td>\n <td className=\"px-4 py-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onDealClick?.(deal.id)}\n >\n Actions\n </Button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction MetricsTab({\n stats,\n}: {\n stats: ReturnType<typeof useDealList>['stats'];\n}) {\n if (!stats) return null;\n\n return (\n <div className=\"space-y-6\">\n <div className=\"border-border bg-card rounded-xl border p-6\">\n <h3 className=\"mb-4 text-lg font-semibold\">Pipeline Overview</h3>\n <dl className=\"grid gap-4 sm:grid-cols-3\">\n <div>\n <dt className=\"text-muted-foreground text-sm\">Win Rate</dt>\n <dd className=\"text-2xl font-semibold\">\n {stats.total > 0\n ? ((stats.wonCount / stats.total) * 100).toFixed(0)\n : 0}\n %\n </dd>\n </div>\n <div>\n <dt className=\"text-muted-foreground text-sm\">Avg Deal Size</dt>\n <dd className=\"text-2xl font-semibold\">\n {formatCurrency(\n stats.total > 0 ? stats.totalValue / stats.total : 0\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-muted-foreground text-sm\">Conversion</dt>\n <dd className=\"text-2xl font-semibold\">\n {stats.wonCount} / {stats.total}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Deal } from "./hooks/useDealList.js";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/ui/CrmDealCard.d.ts
|
|
5
|
+
interface CrmDealCardProps {
|
|
6
|
+
deal: Deal;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare function CrmDealCard({
|
|
10
|
+
deal,
|
|
11
|
+
onClick
|
|
12
|
+
}: CrmDealCardProps): react_jsx_runtime0.JSX.Element;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { CrmDealCard };
|
|
15
|
+
//# sourceMappingURL=CrmDealCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CrmDealCard.d.ts","names":[],"sources":["../../src/ui/CrmDealCard.tsx"],"sourcesContent":[],"mappings":";;;;AAKgD,UAEtC,gBAAA,CAAgB;EAcV,IAAA,EAbR,IAaQ;EAAc,OAAA,CAAA,EAAA,GAAA,GAAA,IAAA;;AAAiB,iBAA/B,WAAA,CAA+B;EAAA,IAAA;EAAA;AAAA,CAAA,EAAA,gBAAA,CAAA,EAAgB,kBAAA,CAAA,GAAA,CAAA,OAAhB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/ui/CrmDealCard.tsx
|
|
6
|
+
function formatCurrency(value, currency) {
|
|
7
|
+
return new Intl.NumberFormat("en-US", {
|
|
8
|
+
style: "currency",
|
|
9
|
+
currency,
|
|
10
|
+
minimumFractionDigits: 0,
|
|
11
|
+
maximumFractionDigits: 0
|
|
12
|
+
}).format(value);
|
|
13
|
+
}
|
|
14
|
+
function CrmDealCard({ deal, onClick }) {
|
|
15
|
+
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1e3 * 60 * 60 * 24)) : null;
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
17
|
+
onClick,
|
|
18
|
+
className: "border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
19
|
+
role: "button",
|
|
20
|
+
tabIndex: 0,
|
|
21
|
+
onKeyDown: (e) => {
|
|
22
|
+
if (e.key === "Enter" || e.key === " ") onClick?.();
|
|
23
|
+
},
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("h4", {
|
|
26
|
+
className: "leading-snug font-medium",
|
|
27
|
+
children: deal.name
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ jsx("div", {
|
|
30
|
+
className: "text-primary mt-2 text-lg font-semibold",
|
|
31
|
+
children: formatCurrency(deal.value, deal.currency)
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ jsxs("div", {
|
|
34
|
+
className: "text-muted-foreground mt-3 flex items-center justify-between text-xs",
|
|
35
|
+
children: [daysUntilClose !== null && /* @__PURE__ */ jsx("span", {
|
|
36
|
+
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
37
|
+
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
38
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
39
|
+
className: `rounded px-1.5 py-0.5 text-xs font-medium ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
40
|
+
children: deal.status
|
|
41
|
+
})]
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { CrmDealCard };
|
|
49
|
+
//# sourceMappingURL=CrmDealCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CrmDealCard.js","names":[],"sources":["../../src/ui/CrmDealCard.tsx"],"sourcesContent":["'use client';\n\n/**\n * CRM Deal Card - Individual deal card for kanban board\n */\nimport type { Deal } from './hooks/useDealList';\n\ninterface CrmDealCardProps {\n deal: Deal;\n onClick?: () => void;\n}\n\nfunction formatCurrency(value: number, currency: string): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }).format(value);\n}\n\nexport function CrmDealCard({ deal, onClick }: CrmDealCardProps) {\n const daysUntilClose = deal.expectedCloseDate\n ? Math.ceil(\n (deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)\n )\n : null;\n\n return (\n <div\n onClick={onClick}\n className=\"border-border bg-card cursor-pointer rounded-lg border p-3 shadow-sm transition-shadow hover:shadow-md\"\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') onClick?.();\n }}\n >\n {/* Deal Name */}\n <h4 className=\"leading-snug font-medium\">{deal.name}</h4>\n\n {/* Deal Value */}\n <div className=\"text-primary mt-2 text-lg font-semibold\">\n {formatCurrency(deal.value, deal.currency)}\n </div>\n\n {/* Meta Info */}\n <div className=\"text-muted-foreground mt-3 flex items-center justify-between text-xs\">\n {/* Expected Close */}\n {daysUntilClose !== null && (\n <span\n className={\n daysUntilClose < 0\n ? 'text-red-500'\n : daysUntilClose <= 7\n ? 'text-yellow-600 dark:text-yellow-500'\n : ''\n }\n >\n {daysUntilClose < 0\n ? `${Math.abs(daysUntilClose)}d overdue`\n : daysUntilClose === 0\n ? 'Due today'\n : `${daysUntilClose}d left`}\n </span>\n )}\n\n {/* Status Badge */}\n <span\n className={`rounded px-1.5 py-0.5 text-xs font-medium ${\n deal.status === 'WON'\n ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'\n : deal.status === 'LOST'\n ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'\n : 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'\n }`}\n >\n {deal.status}\n </span>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAYA,SAAS,eAAe,OAAe,UAA0B;AAC/D,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP;EACA,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CAAC,OAAO,MAAM;;AAGlB,SAAgB,YAAY,EAAE,MAAM,WAA6B;CAC/D,MAAM,iBAAiB,KAAK,oBACxB,KAAK,MACF,KAAK,kBAAkB,SAAS,GAAG,KAAK,KAAK,KAAK,MAAO,KAAK,KAAK,IACrE,GACD;AAEJ,QACE,qBAAC;EACU;EACT,WAAU;EACV,MAAK;EACL,UAAU;EACV,YAAY,MAAM;AAChB,OAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,YAAW;;;GAIrD,oBAAC;IAAG,WAAU;cAA4B,KAAK;KAAU;GAGzD,oBAAC;IAAI,WAAU;cACZ,eAAe,KAAK,OAAO,KAAK,SAAS;KACtC;GAGN,qBAAC;IAAI,WAAU;eAEZ,mBAAmB,QAClB,oBAAC;KACC,WACE,iBAAiB,IACb,iBACA,kBAAkB,IAChB,yCACA;eAGP,iBAAiB,IACd,GAAG,KAAK,IAAI,eAAe,CAAC,aAC5B,mBAAmB,IACjB,cACA,GAAG,eAAe;MACnB,EAIT,oBAAC;KACC,WAAW,6CACT,KAAK,WAAW,QACZ,yEACA,KAAK,WAAW,SACd,iEACA;eAGP,KAAK;MACD;KACH;;GACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Deal } from "./hooks/useDealList.js";
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/ui/CrmPipelineBoard.d.ts
|
|
5
|
+
interface CrmPipelineBoardProps {
|
|
6
|
+
dealsByStage: Record<string, Deal[]>;
|
|
7
|
+
stages: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
position: number;
|
|
11
|
+
}[];
|
|
12
|
+
onDealClick?: (dealId: string) => void;
|
|
13
|
+
onDealMove?: (dealId: string, toStageId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
declare function CrmPipelineBoard({
|
|
16
|
+
dealsByStage,
|
|
17
|
+
stages,
|
|
18
|
+
onDealClick,
|
|
19
|
+
onDealMove
|
|
20
|
+
}: CrmPipelineBoardProps): react_jsx_runtime1.JSX.Element;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { CrmPipelineBoard };
|
|
23
|
+
//# sourceMappingURL=CrmPipelineBoard.d.ts.map
|