@contractspec/example.saas-boilerplate 3.7.7 → 3.8.4
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 +36 -24
- package/CHANGELOG.md +72 -0
- package/README.md +1 -0
- package/dist/browser/index.js +371 -92
- package/dist/browser/saas-boilerplate.feature.js +208 -0
- package/dist/browser/ui/SaasDashboard.js +311 -60
- package/dist/browser/ui/SaasDashboard.visualizations.js +249 -0
- package/dist/browser/ui/index.js +362 -92
- package/dist/browser/ui/renderers/index.js +229 -3
- package/dist/browser/ui/renderers/project-list.markdown.js +229 -3
- package/dist/browser/visualizations/catalog.js +155 -0
- package/dist/browser/visualizations/index.js +217 -0
- package/dist/browser/visualizations/selectors.js +210 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +371 -92
- package/dist/node/index.js +371 -92
- package/dist/node/saas-boilerplate.feature.js +208 -0
- package/dist/node/ui/SaasDashboard.js +311 -60
- package/dist/node/ui/SaasDashboard.visualizations.js +249 -0
- package/dist/node/ui/index.js +362 -92
- package/dist/node/ui/renderers/index.js +229 -3
- package/dist/node/ui/renderers/project-list.markdown.js +229 -3
- package/dist/node/visualizations/catalog.js +155 -0
- package/dist/node/visualizations/index.js +217 -0
- package/dist/node/visualizations/selectors.js +210 -0
- package/dist/saas-boilerplate.feature.js +208 -0
- package/dist/ui/SaasDashboard.js +311 -60
- package/dist/ui/SaasDashboard.visualizations.d.ts +5 -0
- package/dist/ui/SaasDashboard.visualizations.js +250 -0
- package/dist/ui/index.js +362 -92
- package/dist/ui/renderers/index.js +229 -3
- package/dist/ui/renderers/project-list.markdown.d.ts +1 -1
- package/dist/ui/renderers/project-list.markdown.js +229 -3
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/visualizations/catalog.d.ts +11 -0
- package/dist/visualizations/catalog.js +156 -0
- package/dist/visualizations/index.d.ts +2 -0
- package/dist/visualizations/index.js +218 -0
- package/dist/visualizations/selectors.d.ts +8 -0
- package/dist/visualizations/selectors.js +211 -0
- package/dist/visualizations/selectors.test.d.ts +1 -0
- package/package.json +70 -13
- package/src/index.ts +1 -0
- package/src/saas-boilerplate.feature.ts +3 -0
- package/src/ui/SaasDashboard.tsx +8 -0
- package/src/ui/SaasDashboard.visualizations.tsx +41 -0
- package/src/ui/renderers/project-list.markdown.ts +39 -15
- package/src/ui/renderers/project-list.renderer.tsx +1 -1
- package/src/visualizations/catalog.ts +153 -0
- package/src/visualizations/index.ts +2 -0
- package/src/visualizations/selectors.test.ts +25 -0
- package/src/visualizations/selectors.ts +85 -0
|
@@ -345,6 +345,213 @@ function createSaasHandlers(db) {
|
|
|
345
345
|
getSubscription
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
|
+
// src/visualizations/catalog.ts
|
|
349
|
+
import {
|
|
350
|
+
defineVisualization,
|
|
351
|
+
VisualizationRegistry
|
|
352
|
+
} from "@contractspec/lib.contracts-spec/visualizations";
|
|
353
|
+
var PROJECT_LIST_REF = {
|
|
354
|
+
key: "saas.project.list",
|
|
355
|
+
version: "1.0.0"
|
|
356
|
+
};
|
|
357
|
+
var META = {
|
|
358
|
+
version: "1.0.0",
|
|
359
|
+
domain: "saas",
|
|
360
|
+
stability: "experimental",
|
|
361
|
+
owners: ["@example.saas-boilerplate"],
|
|
362
|
+
tags: ["saas", "visualization", "projects"]
|
|
363
|
+
};
|
|
364
|
+
var SaasProjectUsageVisualization = defineVisualization({
|
|
365
|
+
meta: {
|
|
366
|
+
...META,
|
|
367
|
+
key: "saas-boilerplate.visualization.project-usage",
|
|
368
|
+
title: "Project Capacity",
|
|
369
|
+
description: "Current project count against the current plan limit.",
|
|
370
|
+
goal: "Show usage against the active plan allowance.",
|
|
371
|
+
context: "SaaS account overview."
|
|
372
|
+
},
|
|
373
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
374
|
+
visualization: {
|
|
375
|
+
kind: "metric",
|
|
376
|
+
measure: "totalProjects",
|
|
377
|
+
comparisonMeasure: "projectLimit",
|
|
378
|
+
measures: [
|
|
379
|
+
{
|
|
380
|
+
key: "totalProjects",
|
|
381
|
+
label: "Projects",
|
|
382
|
+
dataPath: "totalProjects",
|
|
383
|
+
format: "number"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
key: "projectLimit",
|
|
387
|
+
label: "Plan Limit",
|
|
388
|
+
dataPath: "projectLimit",
|
|
389
|
+
format: "number"
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
table: { caption: "Current project count and plan limit." }
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
var SaasProjectStatusVisualization = defineVisualization({
|
|
396
|
+
meta: {
|
|
397
|
+
...META,
|
|
398
|
+
key: "saas-boilerplate.visualization.project-status",
|
|
399
|
+
title: "Project Status",
|
|
400
|
+
description: "Distribution of project states.",
|
|
401
|
+
goal: "Show the mix of active, draft, and archived projects.",
|
|
402
|
+
context: "Project portfolio overview."
|
|
403
|
+
},
|
|
404
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
405
|
+
visualization: {
|
|
406
|
+
kind: "pie",
|
|
407
|
+
nameDimension: "status",
|
|
408
|
+
valueMeasure: "projects",
|
|
409
|
+
dimensions: [
|
|
410
|
+
{ key: "status", label: "Status", dataPath: "status", type: "category" }
|
|
411
|
+
],
|
|
412
|
+
measures: [
|
|
413
|
+
{
|
|
414
|
+
key: "projects",
|
|
415
|
+
label: "Projects",
|
|
416
|
+
dataPath: "projects",
|
|
417
|
+
format: "number"
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
table: { caption: "Project counts by status." }
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
var SaasProjectTierVisualization = defineVisualization({
|
|
424
|
+
meta: {
|
|
425
|
+
...META,
|
|
426
|
+
key: "saas-boilerplate.visualization.project-tiers",
|
|
427
|
+
title: "Tier Comparison",
|
|
428
|
+
description: "Distribution of projects across tiers.",
|
|
429
|
+
goal: "Compare how the current portfolio is distributed by tier.",
|
|
430
|
+
context: "Plan and packaging overview."
|
|
431
|
+
},
|
|
432
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
433
|
+
visualization: {
|
|
434
|
+
kind: "cartesian",
|
|
435
|
+
variant: "bar",
|
|
436
|
+
xDimension: "tier",
|
|
437
|
+
yMeasures: ["projects"],
|
|
438
|
+
dimensions: [
|
|
439
|
+
{ key: "tier", label: "Tier", dataPath: "tier", type: "category" }
|
|
440
|
+
],
|
|
441
|
+
measures: [
|
|
442
|
+
{
|
|
443
|
+
key: "projects",
|
|
444
|
+
label: "Projects",
|
|
445
|
+
dataPath: "projects",
|
|
446
|
+
format: "number",
|
|
447
|
+
color: "#1d4ed8"
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
table: { caption: "Project counts by tier." }
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
var SaasProjectActivityVisualization = defineVisualization({
|
|
454
|
+
meta: {
|
|
455
|
+
...META,
|
|
456
|
+
key: "saas-boilerplate.visualization.project-activity",
|
|
457
|
+
title: "Recent Project Activity",
|
|
458
|
+
description: "Daily project creation activity.",
|
|
459
|
+
goal: "Show recent project activity over time.",
|
|
460
|
+
context: "Project portfolio trend view."
|
|
461
|
+
},
|
|
462
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
463
|
+
visualization: {
|
|
464
|
+
kind: "cartesian",
|
|
465
|
+
variant: "line",
|
|
466
|
+
xDimension: "day",
|
|
467
|
+
yMeasures: ["projects"],
|
|
468
|
+
dimensions: [{ key: "day", label: "Day", dataPath: "day", type: "time" }],
|
|
469
|
+
measures: [
|
|
470
|
+
{
|
|
471
|
+
key: "projects",
|
|
472
|
+
label: "Projects",
|
|
473
|
+
dataPath: "projects",
|
|
474
|
+
format: "number",
|
|
475
|
+
color: "#0f766e"
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
table: { caption: "Daily project creation counts." }
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
var SaasVisualizationSpecs = [
|
|
482
|
+
SaasProjectUsageVisualization,
|
|
483
|
+
SaasProjectStatusVisualization,
|
|
484
|
+
SaasProjectTierVisualization,
|
|
485
|
+
SaasProjectActivityVisualization
|
|
486
|
+
];
|
|
487
|
+
var SaasVisualizationRegistry = new VisualizationRegistry([
|
|
488
|
+
...SaasVisualizationSpecs
|
|
489
|
+
]);
|
|
490
|
+
var SaasVisualizationRefs = SaasVisualizationSpecs.map((spec) => ({
|
|
491
|
+
key: spec.meta.key,
|
|
492
|
+
version: spec.meta.version
|
|
493
|
+
}));
|
|
494
|
+
|
|
495
|
+
// src/visualizations/selectors.ts
|
|
496
|
+
function toDayKey(value) {
|
|
497
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
498
|
+
return date.toISOString().slice(0, 10);
|
|
499
|
+
}
|
|
500
|
+
function createSaasVisualizationItems(projects, projectLimit = 10) {
|
|
501
|
+
const statusCounts = new Map;
|
|
502
|
+
const tierCounts = new Map;
|
|
503
|
+
const activityCounts = new Map;
|
|
504
|
+
for (const project of projects) {
|
|
505
|
+
statusCounts.set(project.status, (statusCounts.get(project.status) ?? 0) + 1);
|
|
506
|
+
tierCounts.set(project.tier, (tierCounts.get(project.tier) ?? 0) + 1);
|
|
507
|
+
const day = toDayKey(project.createdAt);
|
|
508
|
+
activityCounts.set(day, (activityCounts.get(day) ?? 0) + 1);
|
|
509
|
+
}
|
|
510
|
+
return [
|
|
511
|
+
{
|
|
512
|
+
key: "saas-capacity",
|
|
513
|
+
spec: SaasProjectUsageVisualization,
|
|
514
|
+
data: { data: [{ totalProjects: projects.length, projectLimit }] },
|
|
515
|
+
title: "Project Capacity",
|
|
516
|
+
description: "Current project count compared to the active limit.",
|
|
517
|
+
height: 220
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
key: "saas-status",
|
|
521
|
+
spec: SaasProjectStatusVisualization,
|
|
522
|
+
data: {
|
|
523
|
+
data: Array.from(statusCounts.entries()).map(([status, count]) => ({
|
|
524
|
+
status,
|
|
525
|
+
projects: count
|
|
526
|
+
}))
|
|
527
|
+
},
|
|
528
|
+
title: "Project Status",
|
|
529
|
+
description: "Status mix across the current project portfolio.",
|
|
530
|
+
height: 260
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
key: "saas-tier",
|
|
534
|
+
spec: SaasProjectTierVisualization,
|
|
535
|
+
data: {
|
|
536
|
+
data: Array.from(tierCounts.entries()).map(([tier, count]) => ({
|
|
537
|
+
tier,
|
|
538
|
+
projects: count
|
|
539
|
+
}))
|
|
540
|
+
},
|
|
541
|
+
title: "Tier Comparison",
|
|
542
|
+
description: "How projects are distributed across tiers."
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
key: "saas-activity",
|
|
546
|
+
spec: SaasProjectActivityVisualization,
|
|
547
|
+
data: {
|
|
548
|
+
data: Array.from(activityCounts.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([day, count]) => ({ day, projects: count }))
|
|
549
|
+
},
|
|
550
|
+
title: "Recent Project Activity",
|
|
551
|
+
description: "Daily project creation activity."
|
|
552
|
+
}
|
|
553
|
+
];
|
|
554
|
+
}
|
|
348
555
|
// src/ui/hooks/useProjectList.ts
|
|
349
556
|
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
350
557
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -410,6 +617,18 @@ function useProjectList(options = {}) {
|
|
|
410
617
|
}
|
|
411
618
|
|
|
412
619
|
// src/ui/renderers/project-list.markdown.ts
|
|
620
|
+
var PROJECT_TIERS = [
|
|
621
|
+
"FREE",
|
|
622
|
+
"PRO",
|
|
623
|
+
"ENTERPRISE"
|
|
624
|
+
];
|
|
625
|
+
function toVisualizationProject(project, index) {
|
|
626
|
+
return {
|
|
627
|
+
status: project.status === "DELETED" ? "ARCHIVED" : project.status,
|
|
628
|
+
tier: PROJECT_TIERS[index % PROJECT_TIERS.length] ?? "FREE",
|
|
629
|
+
createdAt: project.createdAt
|
|
630
|
+
};
|
|
631
|
+
}
|
|
413
632
|
var projectListMarkdownRenderer = {
|
|
414
633
|
target: "markdown",
|
|
415
634
|
render: async (desc, _ctx) => {
|
|
@@ -420,7 +639,7 @@ var projectListMarkdownRenderer = {
|
|
|
420
639
|
limit: 20,
|
|
421
640
|
offset: 0
|
|
422
641
|
});
|
|
423
|
-
const items = data.projects ??
|
|
642
|
+
const items = data.projects ?? [];
|
|
424
643
|
const lines = [
|
|
425
644
|
"# Projects",
|
|
426
645
|
"",
|
|
@@ -457,6 +676,7 @@ var saasDashboardMarkdownRenderer = {
|
|
|
457
676
|
const projects = projectsData.projects ?? [];
|
|
458
677
|
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
459
678
|
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
679
|
+
const visualizations = createSaasVisualizationItems(projects.map(toVisualizationProject), 10);
|
|
460
680
|
const lines = [
|
|
461
681
|
"# SaaS Dashboard",
|
|
462
682
|
"",
|
|
@@ -471,10 +691,16 @@ var saasDashboardMarkdownRenderer = {
|
|
|
471
691
|
`| Archived Projects | ${archivedProjects} |`,
|
|
472
692
|
`| Subscription Plan | ${subscription.planName} |`,
|
|
473
693
|
`| Subscription Status | ${subscription.status} |`,
|
|
474
|
-
"",
|
|
475
|
-
"## Projects",
|
|
476
694
|
""
|
|
477
695
|
];
|
|
696
|
+
lines.push("## Visualization Overview");
|
|
697
|
+
lines.push("");
|
|
698
|
+
for (const item of visualizations) {
|
|
699
|
+
lines.push(`- **${item.title}** via \`${item.spec.meta.key}\``);
|
|
700
|
+
}
|
|
701
|
+
lines.push("");
|
|
702
|
+
lines.push("## Projects");
|
|
703
|
+
lines.push("");
|
|
478
704
|
if (projects.length === 0) {
|
|
479
705
|
lines.push("_No projects yet._");
|
|
480
706
|
} else {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses dynamic import to ensure correct build order.
|
|
5
5
|
*/
|
|
6
|
-
import type { PresentationRenderer } from '@contractspec/lib.
|
|
6
|
+
import type { PresentationRenderer } from '@contractspec/lib.presentation-runtime-core/transform-engine';
|
|
7
7
|
/**
|
|
8
8
|
* Markdown renderer for saas-boilerplate.project.list presentation
|
|
9
9
|
* Only handles ProjectListView component
|
|
@@ -345,7 +345,226 @@ function createSaasHandlers(db) {
|
|
|
345
345
|
getSubscription
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
|
+
// src/visualizations/catalog.ts
|
|
349
|
+
import {
|
|
350
|
+
defineVisualization,
|
|
351
|
+
VisualizationRegistry
|
|
352
|
+
} from "@contractspec/lib.contracts-spec/visualizations";
|
|
353
|
+
var PROJECT_LIST_REF = {
|
|
354
|
+
key: "saas.project.list",
|
|
355
|
+
version: "1.0.0"
|
|
356
|
+
};
|
|
357
|
+
var META = {
|
|
358
|
+
version: "1.0.0",
|
|
359
|
+
domain: "saas",
|
|
360
|
+
stability: "experimental",
|
|
361
|
+
owners: ["@example.saas-boilerplate"],
|
|
362
|
+
tags: ["saas", "visualization", "projects"]
|
|
363
|
+
};
|
|
364
|
+
var SaasProjectUsageVisualization = defineVisualization({
|
|
365
|
+
meta: {
|
|
366
|
+
...META,
|
|
367
|
+
key: "saas-boilerplate.visualization.project-usage",
|
|
368
|
+
title: "Project Capacity",
|
|
369
|
+
description: "Current project count against the current plan limit.",
|
|
370
|
+
goal: "Show usage against the active plan allowance.",
|
|
371
|
+
context: "SaaS account overview."
|
|
372
|
+
},
|
|
373
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
374
|
+
visualization: {
|
|
375
|
+
kind: "metric",
|
|
376
|
+
measure: "totalProjects",
|
|
377
|
+
comparisonMeasure: "projectLimit",
|
|
378
|
+
measures: [
|
|
379
|
+
{
|
|
380
|
+
key: "totalProjects",
|
|
381
|
+
label: "Projects",
|
|
382
|
+
dataPath: "totalProjects",
|
|
383
|
+
format: "number"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
key: "projectLimit",
|
|
387
|
+
label: "Plan Limit",
|
|
388
|
+
dataPath: "projectLimit",
|
|
389
|
+
format: "number"
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
table: { caption: "Current project count and plan limit." }
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
var SaasProjectStatusVisualization = defineVisualization({
|
|
396
|
+
meta: {
|
|
397
|
+
...META,
|
|
398
|
+
key: "saas-boilerplate.visualization.project-status",
|
|
399
|
+
title: "Project Status",
|
|
400
|
+
description: "Distribution of project states.",
|
|
401
|
+
goal: "Show the mix of active, draft, and archived projects.",
|
|
402
|
+
context: "Project portfolio overview."
|
|
403
|
+
},
|
|
404
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
405
|
+
visualization: {
|
|
406
|
+
kind: "pie",
|
|
407
|
+
nameDimension: "status",
|
|
408
|
+
valueMeasure: "projects",
|
|
409
|
+
dimensions: [
|
|
410
|
+
{ key: "status", label: "Status", dataPath: "status", type: "category" }
|
|
411
|
+
],
|
|
412
|
+
measures: [
|
|
413
|
+
{
|
|
414
|
+
key: "projects",
|
|
415
|
+
label: "Projects",
|
|
416
|
+
dataPath: "projects",
|
|
417
|
+
format: "number"
|
|
418
|
+
}
|
|
419
|
+
],
|
|
420
|
+
table: { caption: "Project counts by status." }
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
var SaasProjectTierVisualization = defineVisualization({
|
|
424
|
+
meta: {
|
|
425
|
+
...META,
|
|
426
|
+
key: "saas-boilerplate.visualization.project-tiers",
|
|
427
|
+
title: "Tier Comparison",
|
|
428
|
+
description: "Distribution of projects across tiers.",
|
|
429
|
+
goal: "Compare how the current portfolio is distributed by tier.",
|
|
430
|
+
context: "Plan and packaging overview."
|
|
431
|
+
},
|
|
432
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
433
|
+
visualization: {
|
|
434
|
+
kind: "cartesian",
|
|
435
|
+
variant: "bar",
|
|
436
|
+
xDimension: "tier",
|
|
437
|
+
yMeasures: ["projects"],
|
|
438
|
+
dimensions: [
|
|
439
|
+
{ key: "tier", label: "Tier", dataPath: "tier", type: "category" }
|
|
440
|
+
],
|
|
441
|
+
measures: [
|
|
442
|
+
{
|
|
443
|
+
key: "projects",
|
|
444
|
+
label: "Projects",
|
|
445
|
+
dataPath: "projects",
|
|
446
|
+
format: "number",
|
|
447
|
+
color: "#1d4ed8"
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
table: { caption: "Project counts by tier." }
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
var SaasProjectActivityVisualization = defineVisualization({
|
|
454
|
+
meta: {
|
|
455
|
+
...META,
|
|
456
|
+
key: "saas-boilerplate.visualization.project-activity",
|
|
457
|
+
title: "Recent Project Activity",
|
|
458
|
+
description: "Daily project creation activity.",
|
|
459
|
+
goal: "Show recent project activity over time.",
|
|
460
|
+
context: "Project portfolio trend view."
|
|
461
|
+
},
|
|
462
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
463
|
+
visualization: {
|
|
464
|
+
kind: "cartesian",
|
|
465
|
+
variant: "line",
|
|
466
|
+
xDimension: "day",
|
|
467
|
+
yMeasures: ["projects"],
|
|
468
|
+
dimensions: [{ key: "day", label: "Day", dataPath: "day", type: "time" }],
|
|
469
|
+
measures: [
|
|
470
|
+
{
|
|
471
|
+
key: "projects",
|
|
472
|
+
label: "Projects",
|
|
473
|
+
dataPath: "projects",
|
|
474
|
+
format: "number",
|
|
475
|
+
color: "#0f766e"
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
table: { caption: "Daily project creation counts." }
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
var SaasVisualizationSpecs = [
|
|
482
|
+
SaasProjectUsageVisualization,
|
|
483
|
+
SaasProjectStatusVisualization,
|
|
484
|
+
SaasProjectTierVisualization,
|
|
485
|
+
SaasProjectActivityVisualization
|
|
486
|
+
];
|
|
487
|
+
var SaasVisualizationRegistry = new VisualizationRegistry([
|
|
488
|
+
...SaasVisualizationSpecs
|
|
489
|
+
]);
|
|
490
|
+
var SaasVisualizationRefs = SaasVisualizationSpecs.map((spec) => ({
|
|
491
|
+
key: spec.meta.key,
|
|
492
|
+
version: spec.meta.version
|
|
493
|
+
}));
|
|
494
|
+
|
|
495
|
+
// src/visualizations/selectors.ts
|
|
496
|
+
function toDayKey(value) {
|
|
497
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
498
|
+
return date.toISOString().slice(0, 10);
|
|
499
|
+
}
|
|
500
|
+
function createSaasVisualizationItems(projects, projectLimit = 10) {
|
|
501
|
+
const statusCounts = new Map;
|
|
502
|
+
const tierCounts = new Map;
|
|
503
|
+
const activityCounts = new Map;
|
|
504
|
+
for (const project of projects) {
|
|
505
|
+
statusCounts.set(project.status, (statusCounts.get(project.status) ?? 0) + 1);
|
|
506
|
+
tierCounts.set(project.tier, (tierCounts.get(project.tier) ?? 0) + 1);
|
|
507
|
+
const day = toDayKey(project.createdAt);
|
|
508
|
+
activityCounts.set(day, (activityCounts.get(day) ?? 0) + 1);
|
|
509
|
+
}
|
|
510
|
+
return [
|
|
511
|
+
{
|
|
512
|
+
key: "saas-capacity",
|
|
513
|
+
spec: SaasProjectUsageVisualization,
|
|
514
|
+
data: { data: [{ totalProjects: projects.length, projectLimit }] },
|
|
515
|
+
title: "Project Capacity",
|
|
516
|
+
description: "Current project count compared to the active limit.",
|
|
517
|
+
height: 220
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
key: "saas-status",
|
|
521
|
+
spec: SaasProjectStatusVisualization,
|
|
522
|
+
data: {
|
|
523
|
+
data: Array.from(statusCounts.entries()).map(([status, count]) => ({
|
|
524
|
+
status,
|
|
525
|
+
projects: count
|
|
526
|
+
}))
|
|
527
|
+
},
|
|
528
|
+
title: "Project Status",
|
|
529
|
+
description: "Status mix across the current project portfolio.",
|
|
530
|
+
height: 260
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
key: "saas-tier",
|
|
534
|
+
spec: SaasProjectTierVisualization,
|
|
535
|
+
data: {
|
|
536
|
+
data: Array.from(tierCounts.entries()).map(([tier, count]) => ({
|
|
537
|
+
tier,
|
|
538
|
+
projects: count
|
|
539
|
+
}))
|
|
540
|
+
},
|
|
541
|
+
title: "Tier Comparison",
|
|
542
|
+
description: "How projects are distributed across tiers."
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
key: "saas-activity",
|
|
546
|
+
spec: SaasProjectActivityVisualization,
|
|
547
|
+
data: {
|
|
548
|
+
data: Array.from(activityCounts.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([day, count]) => ({ day, projects: count }))
|
|
549
|
+
},
|
|
550
|
+
title: "Recent Project Activity",
|
|
551
|
+
description: "Daily project creation activity."
|
|
552
|
+
}
|
|
553
|
+
];
|
|
554
|
+
}
|
|
348
555
|
// src/ui/renderers/project-list.markdown.ts
|
|
556
|
+
var PROJECT_TIERS = [
|
|
557
|
+
"FREE",
|
|
558
|
+
"PRO",
|
|
559
|
+
"ENTERPRISE"
|
|
560
|
+
];
|
|
561
|
+
function toVisualizationProject(project, index) {
|
|
562
|
+
return {
|
|
563
|
+
status: project.status === "DELETED" ? "ARCHIVED" : project.status,
|
|
564
|
+
tier: PROJECT_TIERS[index % PROJECT_TIERS.length] ?? "FREE",
|
|
565
|
+
createdAt: project.createdAt
|
|
566
|
+
};
|
|
567
|
+
}
|
|
349
568
|
var projectListMarkdownRenderer = {
|
|
350
569
|
target: "markdown",
|
|
351
570
|
render: async (desc, _ctx) => {
|
|
@@ -356,7 +575,7 @@ var projectListMarkdownRenderer = {
|
|
|
356
575
|
limit: 20,
|
|
357
576
|
offset: 0
|
|
358
577
|
});
|
|
359
|
-
const items = data.projects ??
|
|
578
|
+
const items = data.projects ?? [];
|
|
360
579
|
const lines = [
|
|
361
580
|
"# Projects",
|
|
362
581
|
"",
|
|
@@ -393,6 +612,7 @@ var saasDashboardMarkdownRenderer = {
|
|
|
393
612
|
const projects = projectsData.projects ?? [];
|
|
394
613
|
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
395
614
|
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
615
|
+
const visualizations = createSaasVisualizationItems(projects.map(toVisualizationProject), 10);
|
|
396
616
|
const lines = [
|
|
397
617
|
"# SaaS Dashboard",
|
|
398
618
|
"",
|
|
@@ -407,10 +627,16 @@ var saasDashboardMarkdownRenderer = {
|
|
|
407
627
|
`| Archived Projects | ${archivedProjects} |`,
|
|
408
628
|
`| Subscription Plan | ${subscription.planName} |`,
|
|
409
629
|
`| Subscription Status | ${subscription.status} |`,
|
|
410
|
-
"",
|
|
411
|
-
"## Projects",
|
|
412
630
|
""
|
|
413
631
|
];
|
|
632
|
+
lines.push("## Visualization Overview");
|
|
633
|
+
lines.push("");
|
|
634
|
+
for (const item of visualizations) {
|
|
635
|
+
lines.push(`- **${item.title}** via \`${item.spec.meta.key}\``);
|
|
636
|
+
}
|
|
637
|
+
lines.push("");
|
|
638
|
+
lines.push("## Projects");
|
|
639
|
+
lines.push("");
|
|
414
640
|
if (projects.length === 0) {
|
|
415
641
|
lines.push("_No projects yet._");
|
|
416
642
|
} else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React renderer for SaaS Project List presentation
|
|
3
3
|
*/
|
|
4
|
-
import type { PresentationRenderer } from '@contractspec/lib.
|
|
4
|
+
import type { PresentationRenderer } from '@contractspec/lib.presentation-runtime-core/transform-engine';
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
export declare const projectListReactRenderer: PresentationRenderer<React.ReactElement>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { VisualizationRegistry } from '@contractspec/lib.contracts-spec/visualizations';
|
|
2
|
+
export declare const SaasProjectUsageVisualization: import("@contractspec/lib.contracts-spec").VisualizationSpec;
|
|
3
|
+
export declare const SaasProjectStatusVisualization: import("@contractspec/lib.contracts-spec").VisualizationSpec;
|
|
4
|
+
export declare const SaasProjectTierVisualization: import("@contractspec/lib.contracts-spec").VisualizationSpec;
|
|
5
|
+
export declare const SaasProjectActivityVisualization: import("@contractspec/lib.contracts-spec").VisualizationSpec;
|
|
6
|
+
export declare const SaasVisualizationSpecs: readonly [import("@contractspec/lib.contracts-spec").VisualizationSpec, import("@contractspec/lib.contracts-spec").VisualizationSpec, import("@contractspec/lib.contracts-spec").VisualizationSpec, import("@contractspec/lib.contracts-spec").VisualizationSpec];
|
|
7
|
+
export declare const SaasVisualizationRegistry: VisualizationRegistry;
|
|
8
|
+
export declare const SaasVisualizationRefs: {
|
|
9
|
+
key: string;
|
|
10
|
+
version: string;
|
|
11
|
+
}[];
|