=== DONE === - MCP Apps UI system added (11 apps with _meta.ui.resourceUri) - 19 new tool modules added - Tool count: 269 → 461 across 38 categories - Upstream changes merged - All tools tagged with _meta labels - Package lock updated === TO-DO === - [ ] Fix 42 failing edge case tests (BLOCKER — Stage 11) - [ ] Live API testing with GHL credentials - [ ] App design approval for Stage 7→8
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import { UITree } from '../types.js';
|
|
|
|
export function buildPipelineBoardTree(data: {
|
|
pipeline: any;
|
|
opportunities: any[];
|
|
stages: any[];
|
|
}): UITree {
|
|
const pipeline = data.pipeline || {};
|
|
const opportunities = data.opportunities || [];
|
|
const stages = data.stages || [];
|
|
|
|
const totalValue = opportunities.reduce((s: number, o: any) => s + (o.monetaryValue || 0), 0);
|
|
const openCount = opportunities.filter((o: any) => o.status === 'open').length;
|
|
const wonCount = opportunities.filter((o: any) => o.status === 'won').length;
|
|
|
|
// Build kanban columns from real pipeline stages
|
|
const columns = stages.map((stage: any) => {
|
|
const stageOpps = opportunities.filter((o: any) => o.pipelineStageId === stage.id);
|
|
const stageValue = stageOpps.reduce((s: number, o: any) => s + (o.monetaryValue || 0), 0);
|
|
|
|
return {
|
|
id: stage.id,
|
|
title: stage.name,
|
|
count: stageOpps.length,
|
|
totalValue: stageValue > 0 ? `$${stageValue.toLocaleString()}` : undefined,
|
|
cards: stageOpps.slice(0, 8).map((opp: any) => ({
|
|
id: opp.id,
|
|
title: opp.name || 'Untitled',
|
|
subtitle: opp.contact?.name || opp.contact?.email || undefined,
|
|
value: opp.monetaryValue ? `$${Number(opp.monetaryValue).toLocaleString()}` : undefined,
|
|
status: opp.status || 'open',
|
|
statusVariant: opp.status || 'open',
|
|
date: opp.updatedAt ? new Date(opp.updatedAt).toLocaleDateString() : undefined,
|
|
avatarInitials: opp.contact?.name
|
|
? opp.contact.name.split(' ').map((n: string) => n[0]).join('').slice(0, 2).toUpperCase()
|
|
: undefined,
|
|
})),
|
|
};
|
|
});
|
|
|
|
return {
|
|
root: 'page',
|
|
elements: {
|
|
page: {
|
|
key: 'page',
|
|
type: 'PageHeader',
|
|
props: {
|
|
title: pipeline.name || 'Pipeline',
|
|
subtitle: `${opportunities.length} opportunities`,
|
|
gradient: true,
|
|
stats: [
|
|
{ label: 'Total Value', value: `$${totalValue.toLocaleString()}` },
|
|
{ label: 'Open', value: String(openCount) },
|
|
{ label: 'Won', value: String(wonCount) },
|
|
{ label: 'Stages', value: String(stages.length) },
|
|
],
|
|
},
|
|
children: ['board'],
|
|
},
|
|
board: {
|
|
key: 'board',
|
|
type: 'KanbanBoard',
|
|
props: { columns },
|
|
},
|
|
},
|
|
};
|
|
}
|