@contractspec/example.crm-pipeline 3.7.16 → 3.7.18
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 +135 -135
- package/CHANGELOG.md +40 -0
- package/dist/browser/crm-pipeline.feature.js +1 -82
- package/dist/browser/deal/deal.enum.js +1 -18
- package/dist/browser/deal/deal.operation.js +1 -396
- package/dist/browser/deal/deal.schema.js +1 -141
- package/dist/browser/deal/deal.test-spec.js +1 -58
- package/dist/browser/deal/index.js +1 -408
- package/dist/browser/docs/crm-pipeline.docblock.js +5 -49
- package/dist/browser/docs/index.js +5 -49
- package/dist/browser/entities/company.entity.js +1 -52
- package/dist/browser/entities/contact.entity.js +1 -66
- package/dist/browser/entities/deal.entity.js +1 -107
- package/dist/browser/entities/index.js +1 -343
- package/dist/browser/entities/task.entity.js +1 -99
- package/dist/browser/events/contact.event.js +1 -31
- package/dist/browser/events/deal.event.js +1 -101
- package/dist/browser/events/index.js +1 -158
- package/dist/browser/events/task.event.js +1 -28
- package/dist/browser/example.js +1 -39
- package/dist/browser/handlers/crm.handlers.js +2 -171
- package/dist/browser/handlers/deal.handlers.js +1 -293
- package/dist/browser/handlers/index.js +2 -467
- package/dist/browser/handlers/mock-data.js +1 -165
- package/dist/browser/index.js +8 -3461
- package/dist/browser/operations/index.js +1 -407
- package/dist/browser/presentations/dashboard.presentation.js +1 -55
- package/dist/browser/presentations/index.js +1 -290
- package/dist/browser/presentations/pipeline.presentation.js +1 -236
- package/dist/browser/seeders/index.js +1 -22
- package/dist/browser/ui/CrmDashboard.js +1 -1547
- package/dist/browser/ui/CrmDealCard.js +1 -50
- package/dist/browser/ui/CrmPipelineBoard.js +1 -160
- package/dist/browser/ui/hooks/index.js +1 -197
- package/dist/browser/ui/hooks/useDealList.js +1 -95
- package/dist/browser/ui/hooks/useDealMutations.js +1 -100
- package/dist/browser/ui/index.js +4 -2205
- package/dist/browser/ui/modals/CreateDealModal.js +1 -211
- package/dist/browser/ui/modals/DealActionsModal.js +1 -428
- package/dist/browser/ui/modals/index.js +1 -638
- package/dist/browser/ui/overlays/demo-overlays.js +1 -55
- package/dist/browser/ui/overlays/index.js +1 -55
- package/dist/browser/ui/renderers/index.js +4 -849
- package/dist/browser/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/browser/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/browser/ui/tables/DealListTab.js +1 -390
- package/dist/crm-pipeline.feature.js +1 -82
- package/dist/deal/deal.enum.js +1 -18
- package/dist/deal/deal.operation.js +1 -396
- package/dist/deal/deal.schema.js +1 -141
- package/dist/deal/deal.test-spec.js +1 -58
- package/dist/deal/index.js +1 -408
- package/dist/docs/crm-pipeline.docblock.js +5 -49
- package/dist/docs/index.js +5 -49
- package/dist/entities/company.entity.js +1 -52
- package/dist/entities/contact.entity.js +1 -66
- package/dist/entities/deal.entity.js +1 -107
- package/dist/entities/index.js +1 -343
- package/dist/entities/task.entity.js +1 -99
- package/dist/events/contact.event.js +1 -31
- package/dist/events/deal.event.js +1 -101
- package/dist/events/index.js +1 -158
- package/dist/events/task.event.js +1 -28
- package/dist/example.js +1 -39
- package/dist/handlers/crm.handlers.js +2 -171
- package/dist/handlers/deal.handlers.js +1 -293
- package/dist/handlers/index.js +2 -467
- package/dist/handlers/mock-data.js +1 -165
- package/dist/index.js +8 -3461
- package/dist/node/crm-pipeline.feature.js +1 -82
- package/dist/node/deal/deal.enum.js +1 -18
- package/dist/node/deal/deal.operation.js +1 -396
- package/dist/node/deal/deal.schema.js +1 -141
- package/dist/node/deal/deal.test-spec.js +1 -58
- package/dist/node/deal/index.js +1 -408
- package/dist/node/docs/crm-pipeline.docblock.js +5 -49
- package/dist/node/docs/index.js +5 -49
- package/dist/node/entities/company.entity.js +1 -52
- package/dist/node/entities/contact.entity.js +1 -66
- package/dist/node/entities/deal.entity.js +1 -107
- package/dist/node/entities/index.js +1 -343
- package/dist/node/entities/task.entity.js +1 -99
- package/dist/node/events/contact.event.js +1 -31
- package/dist/node/events/deal.event.js +1 -101
- package/dist/node/events/index.js +1 -158
- package/dist/node/events/task.event.js +1 -28
- package/dist/node/example.js +1 -39
- package/dist/node/handlers/crm.handlers.js +2 -171
- package/dist/node/handlers/deal.handlers.js +1 -293
- package/dist/node/handlers/index.js +2 -467
- package/dist/node/handlers/mock-data.js +1 -165
- package/dist/node/index.js +8 -3461
- package/dist/node/operations/index.js +1 -407
- package/dist/node/presentations/dashboard.presentation.js +1 -55
- package/dist/node/presentations/index.js +1 -290
- package/dist/node/presentations/pipeline.presentation.js +1 -236
- package/dist/node/seeders/index.js +1 -22
- package/dist/node/ui/CrmDashboard.js +1 -1547
- package/dist/node/ui/CrmDealCard.js +1 -50
- package/dist/node/ui/CrmPipelineBoard.js +1 -160
- package/dist/node/ui/hooks/index.js +1 -197
- package/dist/node/ui/hooks/useDealList.js +1 -95
- package/dist/node/ui/hooks/useDealMutations.js +1 -100
- package/dist/node/ui/index.js +4 -2205
- package/dist/node/ui/modals/CreateDealModal.js +1 -211
- package/dist/node/ui/modals/DealActionsModal.js +1 -428
- package/dist/node/ui/modals/index.js +1 -638
- package/dist/node/ui/overlays/demo-overlays.js +1 -55
- package/dist/node/ui/overlays/index.js +1 -55
- package/dist/node/ui/renderers/index.js +4 -849
- package/dist/node/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/node/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/node/ui/tables/DealListTab.js +1 -390
- package/dist/operations/index.js +1 -407
- package/dist/presentations/dashboard.presentation.js +1 -55
- package/dist/presentations/index.js +1 -290
- package/dist/presentations/pipeline.presentation.js +1 -236
- package/dist/seeders/index.js +1 -22
- package/dist/ui/CrmDashboard.js +1 -1547
- package/dist/ui/CrmDealCard.js +1 -50
- package/dist/ui/CrmPipelineBoard.js +1 -160
- package/dist/ui/hooks/index.js +1 -197
- package/dist/ui/hooks/useDealList.js +1 -95
- package/dist/ui/hooks/useDealMutations.js +1 -100
- package/dist/ui/index.js +4 -2205
- package/dist/ui/modals/CreateDealModal.js +1 -211
- package/dist/ui/modals/DealActionsModal.js +1 -428
- package/dist/ui/modals/index.js +1 -638
- package/dist/ui/overlays/demo-overlays.js +1 -55
- package/dist/ui/overlays/index.js +1 -55
- package/dist/ui/renderers/index.js +4 -849
- package/dist/ui/renderers/pipeline.markdown.js +4 -575
- package/dist/ui/renderers/pipeline.renderer.js +1 -275
- package/dist/ui/tables/DealListTab.js +1 -390
- package/package.json +16 -16
|
@@ -1,849 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { web } from "@contractspec/lib.runtime-sandbox";
|
|
3
|
-
|
|
4
|
-
function
|
|
5
|
-
return {
|
|
6
|
-
id: row.id,
|
|
7
|
-
projectId: row.projectId,
|
|
8
|
-
name: row.name,
|
|
9
|
-
value: row.value,
|
|
10
|
-
currency: row.currency,
|
|
11
|
-
pipelineId: row.pipelineId,
|
|
12
|
-
stageId: row.stageId,
|
|
13
|
-
status: row.status,
|
|
14
|
-
contactId: row.contactId ?? undefined,
|
|
15
|
-
companyId: row.companyId ?? undefined,
|
|
16
|
-
ownerId: row.ownerId,
|
|
17
|
-
expectedCloseDate: row.expectedCloseDate ? new Date(row.expectedCloseDate) : undefined,
|
|
18
|
-
wonSource: row.wonSource ?? undefined,
|
|
19
|
-
lostReason: row.lostReason ?? undefined,
|
|
20
|
-
notes: row.notes ?? undefined,
|
|
21
|
-
createdAt: new Date(row.createdAt),
|
|
22
|
-
updatedAt: new Date(row.updatedAt)
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
var DEAL_SORT_COLUMNS = {
|
|
26
|
-
name: "name",
|
|
27
|
-
value: "value",
|
|
28
|
-
status: "status",
|
|
29
|
-
expectedCloseDate: "expectedCloseDate",
|
|
30
|
-
updatedAt: "updatedAt"
|
|
31
|
-
};
|
|
32
|
-
function createCrmHandlers(db) {
|
|
33
|
-
async function listDeals(input) {
|
|
34
|
-
const {
|
|
35
|
-
projectId,
|
|
36
|
-
pipelineId,
|
|
37
|
-
stageId,
|
|
38
|
-
status,
|
|
39
|
-
ownerId,
|
|
40
|
-
search,
|
|
41
|
-
limit = 20,
|
|
42
|
-
offset = 0,
|
|
43
|
-
sortBy = "value",
|
|
44
|
-
sortDirection = "desc"
|
|
45
|
-
} = input;
|
|
46
|
-
let whereClause = "WHERE projectId = ?";
|
|
47
|
-
const params = [projectId];
|
|
48
|
-
if (pipelineId) {
|
|
49
|
-
whereClause += " AND pipelineId = ?";
|
|
50
|
-
params.push(pipelineId);
|
|
51
|
-
}
|
|
52
|
-
if (stageId) {
|
|
53
|
-
whereClause += " AND stageId = ?";
|
|
54
|
-
params.push(stageId);
|
|
55
|
-
}
|
|
56
|
-
if (status && status !== "all") {
|
|
57
|
-
whereClause += " AND status = ?";
|
|
58
|
-
params.push(status);
|
|
59
|
-
}
|
|
60
|
-
if (ownerId) {
|
|
61
|
-
whereClause += " AND ownerId = ?";
|
|
62
|
-
params.push(ownerId);
|
|
63
|
-
}
|
|
64
|
-
if (search) {
|
|
65
|
-
whereClause += " AND name LIKE ?";
|
|
66
|
-
params.push(`%${search}%`);
|
|
67
|
-
}
|
|
68
|
-
const countResult = (await db.query(`SELECT COUNT(*) as count FROM crm_deal ${whereClause}`, params)).rows;
|
|
69
|
-
const total = countResult[0]?.count ?? 0;
|
|
70
|
-
const valueResult = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows;
|
|
71
|
-
const totalValue = valueResult[0]?.total ?? 0;
|
|
72
|
-
const orderByColumn = DEAL_SORT_COLUMNS[sortBy] ?? DEAL_SORT_COLUMNS.value;
|
|
73
|
-
const orderByDirection = sortDirection === "asc" ? "ASC" : "DESC";
|
|
74
|
-
const dealRows = (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY ${orderByColumn} ${orderByDirection} LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
|
|
75
|
-
return {
|
|
76
|
-
deals: dealRows.map(rowToDeal),
|
|
77
|
-
total,
|
|
78
|
-
totalValue
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
async function createDeal(input, context) {
|
|
82
|
-
const id = generateId("deal");
|
|
83
|
-
const now = new Date().toISOString();
|
|
84
|
-
await db.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
|
|
85
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
86
|
-
id,
|
|
87
|
-
context.projectId,
|
|
88
|
-
input.pipelineId,
|
|
89
|
-
input.stageId,
|
|
90
|
-
input.name,
|
|
91
|
-
input.value,
|
|
92
|
-
input.currency ?? "USD",
|
|
93
|
-
"OPEN",
|
|
94
|
-
input.contactId ?? null,
|
|
95
|
-
input.companyId ?? null,
|
|
96
|
-
context.ownerId,
|
|
97
|
-
input.expectedCloseDate?.toISOString() ?? null,
|
|
98
|
-
now,
|
|
99
|
-
now
|
|
100
|
-
]);
|
|
101
|
-
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [id])).rows;
|
|
102
|
-
if (!rows[0]) {
|
|
103
|
-
throw new Error("Failed to create deal");
|
|
104
|
-
}
|
|
105
|
-
return rowToDeal(rows[0]);
|
|
106
|
-
}
|
|
107
|
-
async function moveDeal(input) {
|
|
108
|
-
const now = new Date().toISOString();
|
|
109
|
-
const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
110
|
-
if (!existing[0]) {
|
|
111
|
-
throw new Error("NOT_FOUND");
|
|
112
|
-
}
|
|
113
|
-
const stage = (await db.query(`SELECT * FROM crm_stage WHERE id = ?`, [input.stageId])).rows;
|
|
114
|
-
if (!stage[0]) {
|
|
115
|
-
throw new Error("INVALID_STAGE");
|
|
116
|
-
}
|
|
117
|
-
await db.execute(`UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?`, [input.stageId, now, input.dealId]);
|
|
118
|
-
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
119
|
-
return rowToDeal(rows[0]);
|
|
120
|
-
}
|
|
121
|
-
async function winDeal(input) {
|
|
122
|
-
const now = new Date().toISOString();
|
|
123
|
-
const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
124
|
-
if (!existing[0]) {
|
|
125
|
-
throw new Error("NOT_FOUND");
|
|
126
|
-
}
|
|
127
|
-
await db.execute(`UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.wonSource ?? null, input.notes ?? null, now, input.dealId]);
|
|
128
|
-
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
129
|
-
return rowToDeal(rows[0]);
|
|
130
|
-
}
|
|
131
|
-
async function loseDeal(input) {
|
|
132
|
-
const now = new Date().toISOString();
|
|
133
|
-
const existing = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
134
|
-
if (!existing[0]) {
|
|
135
|
-
throw new Error("NOT_FOUND");
|
|
136
|
-
}
|
|
137
|
-
await db.execute(`UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?`, [input.lostReason, input.notes ?? null, now, input.dealId]);
|
|
138
|
-
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
139
|
-
return rowToDeal(rows[0]);
|
|
140
|
-
}
|
|
141
|
-
async function getDealsByStage(input) {
|
|
142
|
-
const deals = (await db.query(`SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC`, [input.projectId, input.pipelineId])).rows;
|
|
143
|
-
const stages = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
|
|
144
|
-
const grouped = {};
|
|
145
|
-
for (const stage of stages) {
|
|
146
|
-
grouped[stage.id] = deals.filter((d) => d.stageId === stage.id).map(rowToDeal);
|
|
147
|
-
}
|
|
148
|
-
return grouped;
|
|
149
|
-
}
|
|
150
|
-
async function getPipelineStages(input) {
|
|
151
|
-
const rows = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
|
|
152
|
-
return rows.map((row) => ({
|
|
153
|
-
id: row.id,
|
|
154
|
-
pipelineId: row.pipelineId,
|
|
155
|
-
name: row.name,
|
|
156
|
-
position: row.position
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
listDeals,
|
|
161
|
-
createDeal,
|
|
162
|
-
moveDeal,
|
|
163
|
-
winDeal,
|
|
164
|
-
loseDeal,
|
|
165
|
-
getDealsByStage,
|
|
166
|
-
getPipelineStages
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/handlers/mock-data.ts
|
|
171
|
-
var MOCK_STAGES = [
|
|
172
|
-
{ id: "stage-1", name: "Lead", position: 1, pipelineId: "pipeline-1" },
|
|
173
|
-
{ id: "stage-2", name: "Qualified", position: 2, pipelineId: "pipeline-1" },
|
|
174
|
-
{ id: "stage-3", name: "Proposal", position: 3, pipelineId: "pipeline-1" },
|
|
175
|
-
{ id: "stage-4", name: "Negotiation", position: 4, pipelineId: "pipeline-1" },
|
|
176
|
-
{ id: "stage-5", name: "Closed", position: 5, pipelineId: "pipeline-1" }
|
|
177
|
-
];
|
|
178
|
-
var MOCK_DEALS = [
|
|
179
|
-
{
|
|
180
|
-
id: "deal-1",
|
|
181
|
-
name: "Enterprise License - Acme Corp",
|
|
182
|
-
value: 75000,
|
|
183
|
-
currency: "USD",
|
|
184
|
-
pipelineId: "pipeline-1",
|
|
185
|
-
stageId: "stage-3",
|
|
186
|
-
status: "OPEN",
|
|
187
|
-
contactId: "contact-1",
|
|
188
|
-
companyId: "company-1",
|
|
189
|
-
ownerId: "user-1",
|
|
190
|
-
expectedCloseDate: new Date("2024-05-15T00:00:00Z"),
|
|
191
|
-
createdAt: new Date("2024-02-01T10:00:00Z"),
|
|
192
|
-
updatedAt: new Date("2024-04-10T14:30:00Z")
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
id: "deal-2",
|
|
196
|
-
name: "Startup Plan - TechStart Inc",
|
|
197
|
-
value: 12000,
|
|
198
|
-
currency: "USD",
|
|
199
|
-
pipelineId: "pipeline-1",
|
|
200
|
-
stageId: "stage-2",
|
|
201
|
-
status: "OPEN",
|
|
202
|
-
contactId: "contact-2",
|
|
203
|
-
companyId: "company-2",
|
|
204
|
-
ownerId: "user-2",
|
|
205
|
-
expectedCloseDate: new Date("2024-04-30T00:00:00Z"),
|
|
206
|
-
createdAt: new Date("2024-03-15T09:00:00Z"),
|
|
207
|
-
updatedAt: new Date("2024-04-08T11:15:00Z")
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
id: "deal-3",
|
|
211
|
-
name: "Professional Services - Global Ltd",
|
|
212
|
-
value: 45000,
|
|
213
|
-
currency: "USD",
|
|
214
|
-
pipelineId: "pipeline-1",
|
|
215
|
-
stageId: "stage-4",
|
|
216
|
-
status: "OPEN",
|
|
217
|
-
contactId: "contact-3",
|
|
218
|
-
companyId: "company-3",
|
|
219
|
-
ownerId: "user-1",
|
|
220
|
-
expectedCloseDate: new Date("2024-04-20T00:00:00Z"),
|
|
221
|
-
createdAt: new Date("2024-01-20T08:00:00Z"),
|
|
222
|
-
updatedAt: new Date("2024-04-12T16:45:00Z")
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
id: "deal-4",
|
|
226
|
-
name: "Annual Contract - SmallBiz Co",
|
|
227
|
-
value: 8500,
|
|
228
|
-
currency: "USD",
|
|
229
|
-
pipelineId: "pipeline-1",
|
|
230
|
-
stageId: "stage-1",
|
|
231
|
-
status: "OPEN",
|
|
232
|
-
contactId: "contact-4",
|
|
233
|
-
companyId: "company-4",
|
|
234
|
-
ownerId: "user-3",
|
|
235
|
-
createdAt: new Date("2024-04-05T12:00:00Z"),
|
|
236
|
-
updatedAt: new Date("2024-04-05T12:00:00Z")
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
id: "deal-5",
|
|
240
|
-
name: "Custom Integration - MegaCorp",
|
|
241
|
-
value: 125000,
|
|
242
|
-
currency: "USD",
|
|
243
|
-
pipelineId: "pipeline-1",
|
|
244
|
-
stageId: "stage-5",
|
|
245
|
-
status: "WON",
|
|
246
|
-
contactId: "contact-5",
|
|
247
|
-
companyId: "company-5",
|
|
248
|
-
ownerId: "user-1",
|
|
249
|
-
expectedCloseDate: new Date("2024-03-31T00:00:00Z"),
|
|
250
|
-
createdAt: new Date("2023-11-10T10:00:00Z"),
|
|
251
|
-
updatedAt: new Date("2024-03-28T09:00:00Z")
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
id: "deal-6",
|
|
255
|
-
name: "Pilot Project - NewCo",
|
|
256
|
-
value: 5000,
|
|
257
|
-
currency: "USD",
|
|
258
|
-
pipelineId: "pipeline-1",
|
|
259
|
-
stageId: "stage-2",
|
|
260
|
-
status: "LOST",
|
|
261
|
-
contactId: "contact-6",
|
|
262
|
-
companyId: "company-6",
|
|
263
|
-
ownerId: "user-2",
|
|
264
|
-
createdAt: new Date("2024-01-15T14:00:00Z"),
|
|
265
|
-
updatedAt: new Date("2024-02-28T10:30:00Z")
|
|
266
|
-
}
|
|
267
|
-
];
|
|
268
|
-
var MOCK_COMPANIES = [
|
|
269
|
-
{
|
|
270
|
-
id: "company-1",
|
|
271
|
-
name: "Acme Corporation",
|
|
272
|
-
domain: "acme.com",
|
|
273
|
-
industry: "Technology",
|
|
274
|
-
size: "1000-5000",
|
|
275
|
-
website: "https://acme.com",
|
|
276
|
-
createdAt: new Date("2024-01-01T00:00:00Z")
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
id: "company-2",
|
|
280
|
-
name: "TechStart Inc",
|
|
281
|
-
domain: "techstart.io",
|
|
282
|
-
industry: "Software",
|
|
283
|
-
size: "10-50",
|
|
284
|
-
website: "https://techstart.io",
|
|
285
|
-
createdAt: new Date("2024-02-15T00:00:00Z")
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
id: "company-3",
|
|
289
|
-
name: "Global Ltd",
|
|
290
|
-
domain: "global.com",
|
|
291
|
-
industry: "Consulting",
|
|
292
|
-
size: "500-1000",
|
|
293
|
-
website: "https://global.com",
|
|
294
|
-
createdAt: new Date("2023-12-01T00:00:00Z")
|
|
295
|
-
}
|
|
296
|
-
];
|
|
297
|
-
var MOCK_CONTACTS = [
|
|
298
|
-
{
|
|
299
|
-
id: "contact-1",
|
|
300
|
-
firstName: "John",
|
|
301
|
-
lastName: "Smith",
|
|
302
|
-
email: "john.smith@acme.com",
|
|
303
|
-
phone: "+1-555-0101",
|
|
304
|
-
title: "VP of Engineering",
|
|
305
|
-
companyId: "company-1",
|
|
306
|
-
createdAt: new Date("2024-01-05T00:00:00Z")
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
id: "contact-2",
|
|
310
|
-
firstName: "Sarah",
|
|
311
|
-
lastName: "Johnson",
|
|
312
|
-
email: "sarah@techstart.io",
|
|
313
|
-
phone: "+1-555-0102",
|
|
314
|
-
title: "CEO",
|
|
315
|
-
companyId: "company-2",
|
|
316
|
-
createdAt: new Date("2024-02-20T00:00:00Z")
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
id: "contact-3",
|
|
320
|
-
firstName: "Michael",
|
|
321
|
-
lastName: "Brown",
|
|
322
|
-
email: "michael.brown@global.com",
|
|
323
|
-
phone: "+1-555-0103",
|
|
324
|
-
title: "CTO",
|
|
325
|
-
companyId: "company-3",
|
|
326
|
-
createdAt: new Date("2023-12-10T00:00:00Z")
|
|
327
|
-
}
|
|
328
|
-
];
|
|
329
|
-
|
|
330
|
-
// src/handlers/deal.handlers.ts
|
|
331
|
-
async function mockListDealsHandler(input) {
|
|
332
|
-
const {
|
|
333
|
-
pipelineId,
|
|
334
|
-
stageId,
|
|
335
|
-
status,
|
|
336
|
-
ownerId,
|
|
337
|
-
search,
|
|
338
|
-
limit = 20,
|
|
339
|
-
offset = 0
|
|
340
|
-
} = input;
|
|
341
|
-
let filtered = [...MOCK_DEALS];
|
|
342
|
-
if (pipelineId) {
|
|
343
|
-
filtered = filtered.filter((d) => d.pipelineId === pipelineId);
|
|
344
|
-
}
|
|
345
|
-
if (stageId) {
|
|
346
|
-
filtered = filtered.filter((d) => d.stageId === stageId);
|
|
347
|
-
}
|
|
348
|
-
if (status && status !== "all") {
|
|
349
|
-
filtered = filtered.filter((d) => d.status === status);
|
|
350
|
-
}
|
|
351
|
-
if (ownerId) {
|
|
352
|
-
filtered = filtered.filter((d) => d.ownerId === ownerId);
|
|
353
|
-
}
|
|
354
|
-
if (search) {
|
|
355
|
-
const q = search.toLowerCase();
|
|
356
|
-
filtered = filtered.filter((d) => d.name.toLowerCase().includes(q));
|
|
357
|
-
}
|
|
358
|
-
filtered.sort((a, b) => b.value - a.value);
|
|
359
|
-
const total = filtered.length;
|
|
360
|
-
const totalValue = filtered.reduce((sum, d) => sum + d.value, 0);
|
|
361
|
-
const deals = filtered.slice(offset, offset + limit);
|
|
362
|
-
return {
|
|
363
|
-
deals,
|
|
364
|
-
total,
|
|
365
|
-
totalValue
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
async function mockCreateDealHandler(input, context) {
|
|
369
|
-
const now = new Date;
|
|
370
|
-
const deal = {
|
|
371
|
-
id: `deal-${Date.now()}`,
|
|
372
|
-
name: input.name,
|
|
373
|
-
value: input.value,
|
|
374
|
-
currency: input.currency ?? "USD",
|
|
375
|
-
pipelineId: input.pipelineId,
|
|
376
|
-
stageId: input.stageId,
|
|
377
|
-
status: "OPEN",
|
|
378
|
-
contactId: input.contactId,
|
|
379
|
-
companyId: input.companyId,
|
|
380
|
-
ownerId: context.ownerId,
|
|
381
|
-
expectedCloseDate: input.expectedCloseDate,
|
|
382
|
-
createdAt: now,
|
|
383
|
-
updatedAt: now
|
|
384
|
-
};
|
|
385
|
-
MOCK_DEALS.push(deal);
|
|
386
|
-
return deal;
|
|
387
|
-
}
|
|
388
|
-
async function mockMoveDealHandler(input) {
|
|
389
|
-
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
390
|
-
if (dealIndex === -1) {
|
|
391
|
-
throw new Error("NOT_FOUND");
|
|
392
|
-
}
|
|
393
|
-
const deal = MOCK_DEALS[dealIndex];
|
|
394
|
-
if (!deal) {
|
|
395
|
-
throw new Error("NOT_FOUND");
|
|
396
|
-
}
|
|
397
|
-
const stage = MOCK_STAGES.find((s) => s.id === input.stageId);
|
|
398
|
-
if (!stage) {
|
|
399
|
-
throw new Error("INVALID_STAGE");
|
|
400
|
-
}
|
|
401
|
-
const updatedDeal = {
|
|
402
|
-
...deal,
|
|
403
|
-
stageId: input.stageId,
|
|
404
|
-
updatedAt: new Date
|
|
405
|
-
};
|
|
406
|
-
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
407
|
-
return updatedDeal;
|
|
408
|
-
}
|
|
409
|
-
async function mockWinDealHandler(input) {
|
|
410
|
-
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
411
|
-
if (dealIndex === -1) {
|
|
412
|
-
throw new Error("NOT_FOUND");
|
|
413
|
-
}
|
|
414
|
-
const deal = MOCK_DEALS[dealIndex];
|
|
415
|
-
if (!deal) {
|
|
416
|
-
throw new Error("NOT_FOUND");
|
|
417
|
-
}
|
|
418
|
-
const updatedDeal = {
|
|
419
|
-
...deal,
|
|
420
|
-
status: "WON",
|
|
421
|
-
updatedAt: new Date
|
|
422
|
-
};
|
|
423
|
-
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
424
|
-
return updatedDeal;
|
|
425
|
-
}
|
|
426
|
-
async function mockLoseDealHandler(input) {
|
|
427
|
-
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
428
|
-
if (dealIndex === -1) {
|
|
429
|
-
throw new Error("NOT_FOUND");
|
|
430
|
-
}
|
|
431
|
-
const deal = MOCK_DEALS[dealIndex];
|
|
432
|
-
if (!deal) {
|
|
433
|
-
throw new Error("NOT_FOUND");
|
|
434
|
-
}
|
|
435
|
-
const updatedDeal = {
|
|
436
|
-
...deal,
|
|
437
|
-
status: "LOST",
|
|
438
|
-
updatedAt: new Date
|
|
439
|
-
};
|
|
440
|
-
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
441
|
-
return updatedDeal;
|
|
442
|
-
}
|
|
443
|
-
async function mockGetDealsByStageHandler(input) {
|
|
444
|
-
const deals = MOCK_DEALS.filter((d) => d.pipelineId === input.pipelineId && d.status === "OPEN");
|
|
445
|
-
const grouped = {};
|
|
446
|
-
for (const stage of MOCK_STAGES) {
|
|
447
|
-
grouped[stage.id] = deals.filter((d) => d.stageId === stage.id);
|
|
448
|
-
}
|
|
449
|
-
return grouped;
|
|
450
|
-
}
|
|
451
|
-
async function mockGetPipelineStagesHandler(input) {
|
|
452
|
-
return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
|
|
453
|
-
}
|
|
454
|
-
// src/ui/CrmDealCard.tsx
|
|
455
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
456
|
-
"use client";
|
|
457
|
-
function formatCurrency(value, currency) {
|
|
458
|
-
return new Intl.NumberFormat("en-US", {
|
|
459
|
-
style: "currency",
|
|
460
|
-
currency,
|
|
461
|
-
minimumFractionDigits: 0,
|
|
462
|
-
maximumFractionDigits: 0
|
|
463
|
-
}).format(value);
|
|
464
|
-
}
|
|
465
|
-
function CrmDealCard({ deal, onClick }) {
|
|
466
|
-
const daysUntilClose = deal.expectedCloseDate ? Math.ceil((deal.expectedCloseDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null;
|
|
467
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
468
|
-
onClick,
|
|
469
|
-
className: "cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",
|
|
470
|
-
role: "button",
|
|
471
|
-
tabIndex: 0,
|
|
472
|
-
onKeyDown: (e) => {
|
|
473
|
-
if (e.key === "Enter" || e.key === " ")
|
|
474
|
-
onClick?.();
|
|
475
|
-
},
|
|
476
|
-
children: [
|
|
477
|
-
/* @__PURE__ */ jsxDEV("h4", {
|
|
478
|
-
className: "font-medium leading-snug",
|
|
479
|
-
children: deal.name
|
|
480
|
-
}, undefined, false, undefined, this),
|
|
481
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
482
|
-
className: "mt-2 font-semibold text-lg text-primary",
|
|
483
|
-
children: formatCurrency(deal.value, deal.currency)
|
|
484
|
-
}, undefined, false, undefined, this),
|
|
485
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
486
|
-
className: "mt-3 flex items-center justify-between text-muted-foreground text-xs",
|
|
487
|
-
children: [
|
|
488
|
-
daysUntilClose !== null && /* @__PURE__ */ jsxDEV("span", {
|
|
489
|
-
className: daysUntilClose < 0 ? "text-red-500" : daysUntilClose <= 7 ? "text-yellow-600 dark:text-yellow-500" : "",
|
|
490
|
-
children: daysUntilClose < 0 ? `${Math.abs(daysUntilClose)}d overdue` : daysUntilClose === 0 ? "Due today" : `${daysUntilClose}d left`
|
|
491
|
-
}, undefined, false, undefined, this),
|
|
492
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
493
|
-
className: `rounded px-1.5 py-0.5 font-medium text-xs ${deal.status === "WON" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : deal.status === "LOST" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"}`,
|
|
494
|
-
children: deal.status
|
|
495
|
-
}, undefined, false, undefined, this)
|
|
496
|
-
]
|
|
497
|
-
}, undefined, true, undefined, this)
|
|
498
|
-
]
|
|
499
|
-
}, undefined, true, undefined, this);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// src/ui/CrmPipelineBoard.tsx
|
|
503
|
-
import { useState } from "react";
|
|
504
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
505
|
-
"use client";
|
|
506
|
-
function formatCurrency2(value) {
|
|
507
|
-
if (value >= 1e6)
|
|
508
|
-
return `$${(value / 1e6).toFixed(1)}M`;
|
|
509
|
-
if (value >= 1000)
|
|
510
|
-
return `$${(value / 1000).toFixed(0)}K`;
|
|
511
|
-
return `$${value}`;
|
|
512
|
-
}
|
|
513
|
-
function CrmPipelineBoard({
|
|
514
|
-
dealsByStage,
|
|
515
|
-
stages,
|
|
516
|
-
onDealClick,
|
|
517
|
-
onDealMove
|
|
518
|
-
}) {
|
|
519
|
-
const [quickMoveOpen, setQuickMoveOpen] = useState(null);
|
|
520
|
-
const sortedStages = [...stages].sort((a, b) => a.position - b.position);
|
|
521
|
-
const handleQuickMove = (dealId, toStageId) => {
|
|
522
|
-
onDealMove?.(dealId, toStageId);
|
|
523
|
-
setQuickMoveOpen(null);
|
|
524
|
-
};
|
|
525
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
526
|
-
className: "flex gap-4 overflow-x-auto pb-4",
|
|
527
|
-
children: sortedStages.map((stage) => {
|
|
528
|
-
const deals = dealsByStage[stage.id] ?? [];
|
|
529
|
-
const stageValue = deals.reduce((sum, d) => sum + d.value, 0);
|
|
530
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
531
|
-
className: "flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",
|
|
532
|
-
children: [
|
|
533
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
534
|
-
className: "flex items-center justify-between border-border border-b px-3 py-2",
|
|
535
|
-
children: [
|
|
536
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
537
|
-
children: [
|
|
538
|
-
/* @__PURE__ */ jsxDEV2("h3", {
|
|
539
|
-
className: "font-medium",
|
|
540
|
-
children: stage.name
|
|
541
|
-
}, undefined, false, undefined, this),
|
|
542
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
543
|
-
className: "text-muted-foreground text-xs",
|
|
544
|
-
children: [
|
|
545
|
-
deals.length,
|
|
546
|
-
" deals · ",
|
|
547
|
-
formatCurrency2(stageValue)
|
|
548
|
-
]
|
|
549
|
-
}, undefined, true, undefined, this)
|
|
550
|
-
]
|
|
551
|
-
}, undefined, true, undefined, this),
|
|
552
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
553
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",
|
|
554
|
-
children: deals.length
|
|
555
|
-
}, undefined, false, undefined, this)
|
|
556
|
-
]
|
|
557
|
-
}, undefined, true, undefined, this),
|
|
558
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
559
|
-
className: "flex flex-1 flex-col gap-2 p-2",
|
|
560
|
-
children: deals.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
|
|
561
|
-
className: "flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",
|
|
562
|
-
children: "No deals"
|
|
563
|
-
}, undefined, false, undefined, this) : deals.map((deal) => /* @__PURE__ */ jsxDEV2("div", {
|
|
564
|
-
className: "group relative",
|
|
565
|
-
children: [
|
|
566
|
-
/* @__PURE__ */ jsxDEV2(CrmDealCard, {
|
|
567
|
-
deal,
|
|
568
|
-
onClick: () => onDealClick?.(deal.id)
|
|
569
|
-
}, undefined, false, undefined, this),
|
|
570
|
-
deal.status === "OPEN" && onDealMove && /* @__PURE__ */ jsxDEV2("div", {
|
|
571
|
-
className: "absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",
|
|
572
|
-
children: [
|
|
573
|
-
/* @__PURE__ */ jsxDEV2("button", {
|
|
574
|
-
type: "button",
|
|
575
|
-
onClick: (e) => {
|
|
576
|
-
e.stopPropagation();
|
|
577
|
-
setQuickMoveOpen(quickMoveOpen === deal.id ? null : deal.id);
|
|
578
|
-
},
|
|
579
|
-
className: "flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",
|
|
580
|
-
title: "Quick move",
|
|
581
|
-
children: "➡️"
|
|
582
|
-
}, undefined, false, undefined, this),
|
|
583
|
-
quickMoveOpen === deal.id && /* @__PURE__ */ jsxDEV2("div", {
|
|
584
|
-
className: "absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",
|
|
585
|
-
children: [
|
|
586
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
587
|
-
className: "px-3 py-1 font-medium text-muted-foreground text-xs",
|
|
588
|
-
children: "Move to:"
|
|
589
|
-
}, undefined, false, undefined, this),
|
|
590
|
-
sortedStages.filter((s) => s.id !== deal.stageId).map((s) => /* @__PURE__ */ jsxDEV2("button", {
|
|
591
|
-
type: "button",
|
|
592
|
-
onClick: (e) => {
|
|
593
|
-
e.stopPropagation();
|
|
594
|
-
handleQuickMove(deal.id, s.id);
|
|
595
|
-
},
|
|
596
|
-
className: "w-full px-3 py-1.5 text-left text-sm hover:bg-muted",
|
|
597
|
-
children: s.name
|
|
598
|
-
}, s.id, false, undefined, this))
|
|
599
|
-
]
|
|
600
|
-
}, undefined, true, undefined, this)
|
|
601
|
-
]
|
|
602
|
-
}, undefined, true, undefined, this)
|
|
603
|
-
]
|
|
604
|
-
}, deal.id, true, undefined, this))
|
|
605
|
-
}, undefined, false, undefined, this)
|
|
606
|
-
]
|
|
607
|
-
}, stage.id, true, undefined, this);
|
|
608
|
-
})
|
|
609
|
-
}, undefined, false, undefined, this);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// src/ui/hooks/useDealList.ts
|
|
613
|
-
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
614
|
-
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
615
|
-
"use client";
|
|
616
|
-
function useDealList(options = {}) {
|
|
617
|
-
const { handlers, projectId } = useTemplateRuntime();
|
|
618
|
-
const { crm: crm2 } = handlers;
|
|
619
|
-
const [data, setData] = useState2(null);
|
|
620
|
-
const [dealsByStage, setDealsByStage] = useState2({});
|
|
621
|
-
const [stages, setStages] = useState2([]);
|
|
622
|
-
const [loading, setLoading] = useState2(true);
|
|
623
|
-
const [error, setError] = useState2(null);
|
|
624
|
-
const [internalPage, setInternalPage] = useState2(0);
|
|
625
|
-
const pipelineId = options.pipelineId ?? "pipeline-1";
|
|
626
|
-
const pageIndex = options.pageIndex ?? internalPage;
|
|
627
|
-
const pageSize = options.pageSize ?? options.limit ?? 50;
|
|
628
|
-
const [sort] = options.sorting ?? [];
|
|
629
|
-
const sortBy = sort?.id;
|
|
630
|
-
const sortDirection = sort ? sort.desc ? "desc" : "asc" : undefined;
|
|
631
|
-
const fetchData = useCallback(async () => {
|
|
632
|
-
setLoading(true);
|
|
633
|
-
setError(null);
|
|
634
|
-
try {
|
|
635
|
-
const [dealsResult, stageDealsResult, stagesResult] = await Promise.all([
|
|
636
|
-
crm2.listDeals({
|
|
637
|
-
projectId,
|
|
638
|
-
pipelineId,
|
|
639
|
-
stageId: options.stageId,
|
|
640
|
-
status: options.status === "all" ? undefined : options.status,
|
|
641
|
-
search: options.search,
|
|
642
|
-
limit: pageSize,
|
|
643
|
-
offset: pageIndex * pageSize,
|
|
644
|
-
sortBy: sortBy === "name" || sortBy === "value" || sortBy === "status" || sortBy === "expectedCloseDate" || sortBy === "updatedAt" ? sortBy : undefined,
|
|
645
|
-
sortDirection
|
|
646
|
-
}),
|
|
647
|
-
crm2.getDealsByStage({ projectId, pipelineId }),
|
|
648
|
-
crm2.getPipelineStages({ pipelineId })
|
|
649
|
-
]);
|
|
650
|
-
setData(dealsResult);
|
|
651
|
-
setDealsByStage(stageDealsResult);
|
|
652
|
-
setStages(stagesResult);
|
|
653
|
-
} catch (err) {
|
|
654
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
655
|
-
} finally {
|
|
656
|
-
setLoading(false);
|
|
657
|
-
}
|
|
658
|
-
}, [
|
|
659
|
-
crm2,
|
|
660
|
-
projectId,
|
|
661
|
-
pipelineId,
|
|
662
|
-
options.stageId,
|
|
663
|
-
options.status,
|
|
664
|
-
options.search,
|
|
665
|
-
pageIndex,
|
|
666
|
-
pageSize,
|
|
667
|
-
sortBy,
|
|
668
|
-
sortDirection
|
|
669
|
-
]);
|
|
670
|
-
useEffect(() => {
|
|
671
|
-
fetchData();
|
|
672
|
-
}, [fetchData]);
|
|
673
|
-
const stats = useMemo(() => {
|
|
674
|
-
if (!data)
|
|
675
|
-
return null;
|
|
676
|
-
const open = data.deals.filter((d) => d.status === "OPEN");
|
|
677
|
-
const won = data.deals.filter((d) => d.status === "WON");
|
|
678
|
-
const lost = data.deals.filter((d) => d.status === "LOST");
|
|
679
|
-
return {
|
|
680
|
-
total: data.total,
|
|
681
|
-
totalValue: data.totalValue,
|
|
682
|
-
openCount: open.length,
|
|
683
|
-
openValue: open.reduce((sum, d) => sum + d.value, 0),
|
|
684
|
-
wonCount: won.length,
|
|
685
|
-
wonValue: won.reduce((sum, d) => sum + d.value, 0),
|
|
686
|
-
lostCount: lost.length
|
|
687
|
-
};
|
|
688
|
-
}, [data]);
|
|
689
|
-
return {
|
|
690
|
-
data,
|
|
691
|
-
dealsByStage,
|
|
692
|
-
stages,
|
|
693
|
-
loading,
|
|
694
|
-
error,
|
|
695
|
-
stats,
|
|
696
|
-
page: pageIndex + 1,
|
|
697
|
-
pageIndex,
|
|
698
|
-
pageSize,
|
|
699
|
-
refetch: fetchData,
|
|
700
|
-
nextPage: options.pageIndex === undefined ? () => setInternalPage((page) => page + 1) : undefined,
|
|
701
|
-
prevPage: options.pageIndex === undefined ? () => pageIndex > 0 && setInternalPage((page) => page - 1) : undefined
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// src/ui/renderers/pipeline.markdown.ts
|
|
706
|
-
function formatCurrency3(value, currency = "USD") {
|
|
707
|
-
return new Intl.NumberFormat("en-US", {
|
|
708
|
-
style: "currency",
|
|
709
|
-
currency,
|
|
710
|
-
minimumFractionDigits: 0
|
|
711
|
-
}).format(value);
|
|
712
|
-
}
|
|
713
|
-
var crmPipelineMarkdownRenderer = {
|
|
714
|
-
target: "markdown",
|
|
715
|
-
render: async (desc, _ctx) => {
|
|
716
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "PipelineKanbanView") {
|
|
717
|
-
throw new Error("crmPipelineMarkdownRenderer: not PipelineKanbanView");
|
|
718
|
-
}
|
|
719
|
-
const pipelineId = "pipeline-1";
|
|
720
|
-
const [dealsResult, stages] = await Promise.all([
|
|
721
|
-
mockListDealsHandler({ pipelineId, limit: 50 }),
|
|
722
|
-
mockGetPipelineStagesHandler({ pipelineId })
|
|
723
|
-
]);
|
|
724
|
-
const deals = dealsResult.deals;
|
|
725
|
-
const stageList = stages;
|
|
726
|
-
const dealsByStage = {};
|
|
727
|
-
for (const stage of stageList) {
|
|
728
|
-
dealsByStage[stage.id] = deals.filter((d) => d.stageId === stage.id && d.status === "OPEN");
|
|
729
|
-
}
|
|
730
|
-
const lines = [
|
|
731
|
-
"# CRM Pipeline",
|
|
732
|
-
"",
|
|
733
|
-
`**Total Value**: ${formatCurrency3(dealsResult.totalValue)}`,
|
|
734
|
-
`**Total Deals**: ${dealsResult.total}`,
|
|
735
|
-
""
|
|
736
|
-
];
|
|
737
|
-
for (const stage of stageList.sort((a, b) => a.position - b.position)) {
|
|
738
|
-
const stageDeals = dealsByStage[stage.id] ?? [];
|
|
739
|
-
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
740
|
-
lines.push(`## ${stage.name}`);
|
|
741
|
-
lines.push(`_${stageDeals.length} deals · ${formatCurrency3(stageValue)}_`);
|
|
742
|
-
lines.push("");
|
|
743
|
-
if (stageDeals.length === 0) {
|
|
744
|
-
lines.push("_No deals_");
|
|
745
|
-
} else {
|
|
746
|
-
for (const deal of stageDeals) {
|
|
747
|
-
lines.push(`- **${deal.name}** - ${formatCurrency3(deal.value, deal.currency)}`);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
lines.push("");
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
mimeType: "text/markdown",
|
|
754
|
-
body: lines.join(`
|
|
755
|
-
`)
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
var crmDashboardMarkdownRenderer = {
|
|
760
|
-
target: "markdown",
|
|
761
|
-
render: async (desc, _ctx) => {
|
|
762
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "CrmDashboard") {
|
|
763
|
-
throw new Error("crmDashboardMarkdownRenderer: not CrmDashboard");
|
|
764
|
-
}
|
|
765
|
-
const pipelineId = "pipeline-1";
|
|
766
|
-
const [dealsResult, stages] = await Promise.all([
|
|
767
|
-
mockListDealsHandler({ pipelineId, limit: 100 }),
|
|
768
|
-
mockGetPipelineStagesHandler({ pipelineId })
|
|
769
|
-
]);
|
|
770
|
-
const deals = dealsResult.deals;
|
|
771
|
-
const stageList = stages;
|
|
772
|
-
const openDeals = deals.filter((d) => d.status === "OPEN");
|
|
773
|
-
const wonDeals = deals.filter((d) => d.status === "WON");
|
|
774
|
-
const lostDeals = deals.filter((d) => d.status === "LOST");
|
|
775
|
-
const openValue = openDeals.reduce((sum, d) => sum + d.value, 0);
|
|
776
|
-
const wonValue = wonDeals.reduce((sum, d) => sum + d.value, 0);
|
|
777
|
-
const lines = [
|
|
778
|
-
"# CRM Dashboard",
|
|
779
|
-
"",
|
|
780
|
-
"> Sales pipeline overview and key metrics",
|
|
781
|
-
"",
|
|
782
|
-
"## Summary",
|
|
783
|
-
"",
|
|
784
|
-
"| Metric | Value |",
|
|
785
|
-
"|--------|-------|",
|
|
786
|
-
`| Total Deals | ${dealsResult.total} |`,
|
|
787
|
-
`| Pipeline Value | ${formatCurrency3(dealsResult.totalValue)} |`,
|
|
788
|
-
`| Open Deals | ${openDeals.length} (${formatCurrency3(openValue)}) |`,
|
|
789
|
-
`| Won Deals | ${wonDeals.length} (${formatCurrency3(wonValue)}) |`,
|
|
790
|
-
`| Lost Deals | ${lostDeals.length} |`,
|
|
791
|
-
"",
|
|
792
|
-
"## Pipeline Stages",
|
|
793
|
-
""
|
|
794
|
-
];
|
|
795
|
-
lines.push("| Stage | Deals | Value |");
|
|
796
|
-
lines.push("|-------|-------|-------|");
|
|
797
|
-
for (const stage of stageList.sort((a, b) => a.position - b.position)) {
|
|
798
|
-
const stageDeals = openDeals.filter((d) => d.stageId === stage.id);
|
|
799
|
-
const stageValue = stageDeals.reduce((sum, d) => sum + d.value, 0);
|
|
800
|
-
lines.push(`| ${stage.name} | ${stageDeals.length} | ${formatCurrency3(stageValue)} |`);
|
|
801
|
-
}
|
|
802
|
-
lines.push("");
|
|
803
|
-
lines.push("## Recent Deals");
|
|
804
|
-
lines.push("");
|
|
805
|
-
const recentDeals = deals.slice(0, 10);
|
|
806
|
-
if (recentDeals.length === 0) {
|
|
807
|
-
lines.push("_No deals yet._");
|
|
808
|
-
} else {
|
|
809
|
-
lines.push("| Deal | Value | Stage | Status |");
|
|
810
|
-
lines.push("|------|-------|-------|--------|");
|
|
811
|
-
for (const deal of recentDeals) {
|
|
812
|
-
const stage = stageList.find((s) => s.id === deal.stageId);
|
|
813
|
-
lines.push(`| ${deal.name} | ${formatCurrency3(deal.value, deal.currency)} | ${stage?.name ?? "-"} | ${deal.status} |`);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
return {
|
|
817
|
-
mimeType: "text/markdown",
|
|
818
|
-
body: lines.join(`
|
|
819
|
-
`)
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
// src/ui/renderers/pipeline.renderer.tsx
|
|
825
|
-
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
826
|
-
function CrmPipelineBoardWrapper() {
|
|
827
|
-
const { dealsByStage, stages } = useDealList();
|
|
828
|
-
return /* @__PURE__ */ jsxDEV3(CrmPipelineBoard, {
|
|
829
|
-
dealsByStage,
|
|
830
|
-
stages
|
|
831
|
-
}, undefined, false, undefined, this);
|
|
832
|
-
}
|
|
833
|
-
var crmPipelineReactRenderer = {
|
|
834
|
-
target: "react",
|
|
835
|
-
render: async (desc, _ctx) => {
|
|
836
|
-
if (desc.source.type !== "component") {
|
|
837
|
-
throw new Error("Invalid source type");
|
|
838
|
-
}
|
|
839
|
-
if (desc.source.componentKey !== "CrmPipelineView") {
|
|
840
|
-
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
841
|
-
}
|
|
842
|
-
return /* @__PURE__ */ jsxDEV3(CrmPipelineBoardWrapper, {}, undefined, false, undefined, this);
|
|
843
|
-
}
|
|
844
|
-
};
|
|
845
|
-
export {
|
|
846
|
-
crmPipelineReactRenderer,
|
|
847
|
-
crmPipelineMarkdownRenderer,
|
|
848
|
-
crmDashboardMarkdownRenderer
|
|
849
|
-
};
|
|
1
|
+
import{web as i}from"@contractspec/lib.runtime-sandbox";var{generateId:o}=i;function M(H){return{id:H.id,projectId:H.projectId,name:H.name,value:H.value,currency:H.currency,pipelineId:H.pipelineId,stageId:H.stageId,status:H.status,contactId:H.contactId??void 0,companyId:H.companyId??void 0,ownerId:H.ownerId,expectedCloseDate:H.expectedCloseDate?new Date(H.expectedCloseDate):void 0,wonSource:H.wonSource??void 0,lostReason:H.lostReason??void 0,notes:H.notes??void 0,createdAt:new Date(H.createdAt),updatedAt:new Date(H.updatedAt)}}var w={name:"name",value:"value",status:"status",expectedCloseDate:"expectedCloseDate",updatedAt:"updatedAt"};function GH(H){async function Y(W){let{projectId:Z,pipelineId:q,stageId:$,status:F,ownerId:P,search:G,limit:V=20,offset:K=0,sortBy:_="value",sortDirection:B="desc"}=W,A="WHERE projectId = ?",Q=[Z];if(q)A+=" AND pipelineId = ?",Q.push(q);if($)A+=" AND stageId = ?",Q.push($);if(F&&F!=="all")A+=" AND status = ?",Q.push(F);if(P)A+=" AND ownerId = ?",Q.push(P);if(G)A+=" AND name LIKE ?",Q.push(`%${G}%`);let f=(await H.query(`SELECT COUNT(*) as count FROM crm_deal ${A}`,Q)).rows[0]?.count??0,T=(await H.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${A}`,Q)).rows[0]?.total??0,h=w[_]??w.value,y=B==="asc"?"ASC":"DESC";return{deals:(await H.query(`SELECT * FROM crm_deal ${A} ORDER BY ${h} ${y} LIMIT ? OFFSET ?`,[...Q,V,K])).rows.map(M),total:f,totalValue:T}}async function X(W,Z){let q=o("deal"),$=new Date().toISOString();await H.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
|
|
2
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[q,Z.projectId,W.pipelineId,W.stageId,W.name,W.value,W.currency??"USD","OPEN",W.contactId??null,W.companyId??null,Z.ownerId,W.expectedCloseDate?.toISOString()??null,$,$]);let F=(await H.query("SELECT * FROM crm_deal WHERE id = ?",[q])).rows;if(!F[0])throw Error("Failed to create deal");return M(F[0])}async function J(W){let Z=new Date().toISOString();if(!(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows[0])throw Error("NOT_FOUND");if(!(await H.query("SELECT * FROM crm_stage WHERE id = ?",[W.stageId])).rows[0])throw Error("INVALID_STAGE");await H.execute("UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?",[W.stageId,Z,W.dealId]);let F=(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows;return M(F[0])}async function z(W){let Z=new Date().toISOString();if(!(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows[0])throw Error("NOT_FOUND");await H.execute("UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?",[W.wonSource??null,W.notes??null,Z,W.dealId]);let $=(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows;return M($[0])}async function N(W){let Z=new Date().toISOString();if(!(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows[0])throw Error("NOT_FOUND");await H.execute("UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?",[W.lostReason,W.notes??null,Z,W.dealId]);let $=(await H.query("SELECT * FROM crm_deal WHERE id = ?",[W.dealId])).rows;return M($[0])}async function U(W){let Z=(await H.query("SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC",[W.projectId,W.pipelineId])).rows,q=(await H.query("SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position",[W.pipelineId])).rows,$={};for(let F of q)$[F.id]=Z.filter((P)=>P.stageId===F.id).map(M);return $}async function R(W){return(await H.query("SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position",[W.pipelineId])).rows.map((q)=>({id:q.id,pipelineId:q.pipelineId,name:q.name,position:q.position}))}return{listDeals:Y,createDeal:X,moveDeal:J,winDeal:z,loseDeal:N,getDealsByStage:U,getPipelineStages:R}}var I=[{id:"stage-1",name:"Lead",position:1,pipelineId:"pipeline-1"},{id:"stage-2",name:"Qualified",position:2,pipelineId:"pipeline-1"},{id:"stage-3",name:"Proposal",position:3,pipelineId:"pipeline-1"},{id:"stage-4",name:"Negotiation",position:4,pipelineId:"pipeline-1"},{id:"stage-5",name:"Closed",position:5,pipelineId:"pipeline-1"}],k=[{id:"deal-1",name:"Enterprise License - Acme Corp",value:75000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-3",status:"OPEN",contactId:"contact-1",companyId:"company-1",ownerId:"user-1",expectedCloseDate:new Date("2024-05-15T00:00:00Z"),createdAt:new Date("2024-02-01T10:00:00Z"),updatedAt:new Date("2024-04-10T14:30:00Z")},{id:"deal-2",name:"Startup Plan - TechStart Inc",value:12000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-2",status:"OPEN",contactId:"contact-2",companyId:"company-2",ownerId:"user-2",expectedCloseDate:new Date("2024-04-30T00:00:00Z"),createdAt:new Date("2024-03-15T09:00:00Z"),updatedAt:new Date("2024-04-08T11:15:00Z")},{id:"deal-3",name:"Professional Services - Global Ltd",value:45000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-4",status:"OPEN",contactId:"contact-3",companyId:"company-3",ownerId:"user-1",expectedCloseDate:new Date("2024-04-20T00:00:00Z"),createdAt:new Date("2024-01-20T08:00:00Z"),updatedAt:new Date("2024-04-12T16:45:00Z")},{id:"deal-4",name:"Annual Contract - SmallBiz Co",value:8500,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-1",status:"OPEN",contactId:"contact-4",companyId:"company-4",ownerId:"user-3",createdAt:new Date("2024-04-05T12:00:00Z"),updatedAt:new Date("2024-04-05T12:00:00Z")},{id:"deal-5",name:"Custom Integration - MegaCorp",value:125000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-5",status:"WON",contactId:"contact-5",companyId:"company-5",ownerId:"user-1",expectedCloseDate:new Date("2024-03-31T00:00:00Z"),createdAt:new Date("2023-11-10T10:00:00Z"),updatedAt:new Date("2024-03-28T09:00:00Z")},{id:"deal-6",name:"Pilot Project - NewCo",value:5000,currency:"USD",pipelineId:"pipeline-1",stageId:"stage-2",status:"LOST",contactId:"contact-6",companyId:"company-6",ownerId:"user-2",createdAt:new Date("2024-01-15T14:00:00Z"),updatedAt:new Date("2024-02-28T10:30:00Z")}],PH=[{id:"company-1",name:"Acme Corporation",domain:"acme.com",industry:"Technology",size:"1000-5000",website:"https://acme.com",createdAt:new Date("2024-01-01T00:00:00Z")},{id:"company-2",name:"TechStart Inc",domain:"techstart.io",industry:"Software",size:"10-50",website:"https://techstart.io",createdAt:new Date("2024-02-15T00:00:00Z")},{id:"company-3",name:"Global Ltd",domain:"global.com",industry:"Consulting",size:"500-1000",website:"https://global.com",createdAt:new Date("2023-12-01T00:00:00Z")}],QH=[{id:"contact-1",firstName:"John",lastName:"Smith",email:"john.smith@acme.com",phone:"+1-555-0101",title:"VP of Engineering",companyId:"company-1",createdAt:new Date("2024-01-05T00:00:00Z")},{id:"contact-2",firstName:"Sarah",lastName:"Johnson",email:"sarah@techstart.io",phone:"+1-555-0102",title:"CEO",companyId:"company-2",createdAt:new Date("2024-02-20T00:00:00Z")},{id:"contact-3",firstName:"Michael",lastName:"Brown",email:"michael.brown@global.com",phone:"+1-555-0103",title:"CTO",companyId:"company-3",createdAt:new Date("2023-12-10T00:00:00Z")}];async function x(H){let{pipelineId:Y,stageId:X,status:J,ownerId:z,search:N,limit:U=20,offset:R=0}=H,W=[...k];if(Y)W=W.filter((F)=>F.pipelineId===Y);if(X)W=W.filter((F)=>F.stageId===X);if(J&&J!=="all")W=W.filter((F)=>F.status===J);if(z)W=W.filter((F)=>F.ownerId===z);if(N){let F=N.toLowerCase();W=W.filter((P)=>P.name.toLowerCase().includes(F))}W.sort((F,P)=>P.value-F.value);let Z=W.length,q=W.reduce((F,P)=>F+P.value,0);return{deals:W.slice(R,R+U),total:Z,totalValue:q}}async function n(H,Y){let X=new Date,J={id:`deal-${Date.now()}`,name:H.name,value:H.value,currency:H.currency??"USD",pipelineId:H.pipelineId,stageId:H.stageId,status:"OPEN",contactId:H.contactId,companyId:H.companyId,ownerId:Y.ownerId,expectedCloseDate:H.expectedCloseDate,createdAt:X,updatedAt:X};return k.push(J),J}async function l(H){let Y=k.findIndex((N)=>N.id===H.dealId);if(Y===-1)throw Error("NOT_FOUND");let X=k[Y];if(!X)throw Error("NOT_FOUND");if(!I.find((N)=>N.id===H.stageId))throw Error("INVALID_STAGE");let z={...X,stageId:H.stageId,updatedAt:new Date};return k[Y]=z,z}async function d(H){let Y=k.findIndex((z)=>z.id===H.dealId);if(Y===-1)throw Error("NOT_FOUND");let X=k[Y];if(!X)throw Error("NOT_FOUND");let J={...X,status:"WON",updatedAt:new Date};return k[Y]=J,J}async function s(H){let Y=k.findIndex((z)=>z.id===H.dealId);if(Y===-1)throw Error("NOT_FOUND");let X=k[Y];if(!X)throw Error("NOT_FOUND");let J={...X,status:"LOST",updatedAt:new Date};return k[Y]=J,J}async function t(H){let Y=k.filter((J)=>J.pipelineId===H.pipelineId&&J.status==="OPEN"),X={};for(let J of I)X[J.id]=Y.filter((z)=>z.stageId===J.id);return X}async function m(H){return I.filter((Y)=>Y.pipelineId===H.pipelineId)}import{jsx as S,jsxs as c}from"react/jsx-runtime";function a(H,Y){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0,maximumFractionDigits:0}).format(H)}function g({deal:H,onClick:Y}){let X=H.expectedCloseDate?Math.ceil((H.expectedCloseDate.getTime()-Date.now())/86400000):null;return c("div",{onClick:Y,className:"cursor-pointer rounded-lg border border-border bg-card p-3 shadow-sm transition-shadow hover:shadow-md",role:"button",tabIndex:0,onKeyDown:(J)=>{if(J.key==="Enter"||J.key===" ")Y?.()},children:[S("h4",{className:"font-medium leading-snug",children:H.name}),S("div",{className:"mt-2 font-semibold text-lg text-primary",children:a(H.value,H.currency)}),c("div",{className:"mt-3 flex items-center justify-between text-muted-foreground text-xs",children:[X!==null&&S("span",{className:X<0?"text-red-500":X<=7?"text-yellow-600 dark:text-yellow-500":"",children:X<0?`${Math.abs(X)}d overdue`:X===0?"Due today":`${X}d left`}),S("span",{className:`rounded px-1.5 py-0.5 font-medium text-xs ${H.status==="WON"?"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400":H.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"}`,children:H.status})]})]})}import{useState as e}from"react";import{jsx as E,jsxs as v}from"react/jsx-runtime";function HH(H){if(H>=1e6)return`$${(H/1e6).toFixed(1)}M`;if(H>=1000)return`$${(H/1000).toFixed(0)}K`;return`$${H}`}function p({dealsByStage:H,stages:Y,onDealClick:X,onDealMove:J}){let[z,N]=e(null),U=[...Y].sort((W,Z)=>W.position-Z.position),R=(W,Z)=>{J?.(W,Z),N(null)};return E("div",{className:"flex gap-4 overflow-x-auto pb-4",children:U.map((W)=>{let Z=H[W.id]??[],q=Z.reduce(($,F)=>$+F.value,0);return v("div",{className:"flex w-72 flex-shrink-0 flex-col rounded-lg bg-muted/30",children:[v("div",{className:"flex items-center justify-between border-border border-b px-3 py-2",children:[v("div",{children:[E("h3",{className:"font-medium",children:W.name}),v("p",{className:"text-muted-foreground text-xs",children:[Z.length," deals · ",HH(q)]})]}),E("span",{className:"flex h-6 w-6 items-center justify-center rounded-full bg-muted font-medium text-xs",children:Z.length})]}),E("div",{className:"flex flex-1 flex-col gap-2 p-2",children:Z.length===0?E("div",{className:"flex h-24 items-center justify-center rounded-md border-2 border-muted-foreground/20 border-dashed text-muted-foreground text-xs",children:"No deals"}):Z.map(($)=>v("div",{className:"group relative",children:[E(g,{deal:$,onClick:()=>X?.($.id)}),$.status==="OPEN"&&J&&v("div",{className:"absolute top-1 right-1 opacity-0 transition-opacity group-hover:opacity-100",children:[E("button",{type:"button",onClick:(F)=>{F.stopPropagation(),N(z===$.id?null:$.id)},className:"flex h-6 w-6 items-center justify-center rounded border border-border bg-background text-xs shadow-sm hover:bg-muted",title:"Quick move",children:"➡️"}),z===$.id&&v("div",{className:"absolute top-7 right-0 z-20 min-w-[140px] rounded-lg border border-border bg-card py-1 shadow-lg",children:[E("p",{className:"px-3 py-1 font-medium text-muted-foreground text-xs",children:"Move to:"}),U.filter((F)=>F.id!==$.stageId).map((F)=>E("button",{type:"button",onClick:(P)=>{P.stopPropagation(),R($.id,F.id)},className:"w-full px-3 py-1.5 text-left text-sm hover:bg-muted",children:F.name},F.id))]})]})]},$.id))})]},W.id)})})}import{useTemplateRuntime as WH}from"@contractspec/lib.example-shared-ui";import{useCallback as FH,useEffect as JH,useMemo as XH,useState as j}from"react";function u(H={}){let{handlers:Y,projectId:X}=WH(),{crm:J}=Y,[z,N]=j(null),[U,R]=j({}),[W,Z]=j([]),[q,$]=j(!0),[F,P]=j(null),[G,V]=j(0),K=H.pipelineId??"pipeline-1",_=H.pageIndex??G,B=H.pageSize??H.limit??50,[A]=H.sorting??[],Q=A?.id,b=A?A.desc?"desc":"asc":void 0,f=FH(async()=>{$(!0),P(null);try{let[T,h,y]=await Promise.all([J.listDeals({projectId:X,pipelineId:K,stageId:H.stageId,status:H.status==="all"?void 0:H.status,search:H.search,limit:B,offset:_*B,sortBy:Q==="name"||Q==="value"||Q==="status"||Q==="expectedCloseDate"||Q==="updatedAt"?Q:void 0,sortDirection:b}),J.getDealsByStage({projectId:X,pipelineId:K}),J.getPipelineStages({pipelineId:K})]);N(T),R(h),Z(y)}catch(T){P(T instanceof Error?T:Error("Unknown error"))}finally{$(!1)}},[J,X,K,H.stageId,H.status,H.search,_,B,Q,b]);JH(()=>{f()},[f]);let D=XH(()=>{if(!z)return null;let T=z.deals.filter((L)=>L.status==="OPEN"),h=z.deals.filter((L)=>L.status==="WON"),y=z.deals.filter((L)=>L.status==="LOST");return{total:z.total,totalValue:z.totalValue,openCount:T.length,openValue:T.reduce((L,C)=>L+C.value,0),wonCount:h.length,wonValue:h.reduce((L,C)=>L+C.value,0),lostCount:y.length}},[z]);return{data:z,dealsByStage:U,stages:W,loading:q,error:F,stats:D,page:_+1,pageIndex:_,pageSize:B,refetch:f,nextPage:H.pageIndex===void 0?()=>V((T)=>T+1):void 0,prevPage:H.pageIndex===void 0?()=>_>0&&V((T)=>T-1):void 0}}function O(H,Y="USD"){return new Intl.NumberFormat("en-US",{style:"currency",currency:Y,minimumFractionDigits:0}).format(H)}var YH={target:"markdown",render:async(H,Y)=>{if(H.source.type!=="component"||H.source.componentKey!=="PipelineKanbanView")throw Error("crmPipelineMarkdownRenderer: not PipelineKanbanView");let X="pipeline-1",[J,z]=await Promise.all([x({pipelineId:X,limit:50}),m({pipelineId:X})]),N=J.deals,U=z,R={};for(let Z of U)R[Z.id]=N.filter((q)=>q.stageId===Z.id&&q.status==="OPEN");let W=["# CRM Pipeline","",`**Total Value**: ${O(J.totalValue)}`,`**Total Deals**: ${J.total}`,""];for(let Z of U.sort((q,$)=>q.position-$.position)){let q=R[Z.id]??[],$=q.reduce((F,P)=>F+P.value,0);if(W.push(`## ${Z.name}`),W.push(`_${q.length} deals · ${O($)}_`),W.push(""),q.length===0)W.push("_No deals_");else for(let F of q)W.push(`- **${F.name}** - ${O(F.value,F.currency)}`);W.push("")}return{mimeType:"text/markdown",body:W.join(`
|
|
3
|
+
`)}}},ZH={target:"markdown",render:async(H,Y)=>{if(H.source.type!=="component"||H.source.componentKey!=="CrmDashboard")throw Error("crmDashboardMarkdownRenderer: not CrmDashboard");let X="pipeline-1",[J,z]=await Promise.all([x({pipelineId:X,limit:100}),m({pipelineId:X})]),N=J.deals,U=z,R=N.filter((G)=>G.status==="OPEN"),W=N.filter((G)=>G.status==="WON"),Z=N.filter((G)=>G.status==="LOST"),q=R.reduce((G,V)=>G+V.value,0),$=W.reduce((G,V)=>G+V.value,0),F=["# CRM Dashboard","","> Sales pipeline overview and key metrics","","## Summary","","| Metric | Value |","|--------|-------|",`| Total Deals | ${J.total} |`,`| Pipeline Value | ${O(J.totalValue)} |`,`| Open Deals | ${R.length} (${O(q)}) |`,`| Won Deals | ${W.length} (${O($)}) |`,`| Lost Deals | ${Z.length} |`,"","## Pipeline Stages",""];F.push("| Stage | Deals | Value |"),F.push("|-------|-------|-------|");for(let G of U.sort((V,K)=>V.position-K.position)){let V=R.filter((_)=>_.stageId===G.id),K=V.reduce((_,B)=>_+B.value,0);F.push(`| ${G.name} | ${V.length} | ${O(K)} |`)}F.push(""),F.push("## Recent Deals"),F.push("");let P=N.slice(0,10);if(P.length===0)F.push("_No deals yet._");else{F.push("| Deal | Value | Stage | Status |"),F.push("|------|-------|-------|--------|");for(let G of P){let V=U.find((K)=>K.id===G.stageId);F.push(`| ${G.name} | ${O(G.value,G.currency)} | ${V?.name??"-"} | ${G.status} |`)}}return{mimeType:"text/markdown",body:F.join(`
|
|
4
|
+
`)}}};import{jsx as r}from"react/jsx-runtime";function $H(){let{dealsByStage:H,stages:Y}=u();return r(p,{dealsByStage:H,stages:Y})}var qH={target:"react",render:async(H,Y)=>{if(H.source.type!=="component")throw Error("Invalid source type");if(H.source.componentKey!=="CrmPipelineView")throw Error(`Unknown component: ${H.source.componentKey}`);return r($H,{})}};export{qH as crmPipelineReactRenderer,YH as crmPipelineMarkdownRenderer,ZH as crmDashboardMarkdownRenderer};
|