Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/app-crm/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as objects from './src/objects/index.js';
import * as views from './src/views/index.js';
import * as apps from './src/apps/index.js';
import * as dashboards from './src/dashboards/index.js';
import * as datasets from './src/datasets/index.js';
import * as reports from './src/reports/index.js';
import * as pages from './src/pages/index.js';
import * as actions from './src/actions/index.js';
Expand Down Expand Up @@ -98,6 +99,7 @@ export default defineStack({
views: Object.values(views),
pages: Object.values(pages),
dashboards: Object.values(dashboards),
datasets: Object.values(datasets),
reports: Object.values(reports),
actions: Object.values(actions),
themes: [CrmLightTheme, CrmDarkTheme],
Expand Down
15 changes: 15 additions & 0 deletions examples/app-crm/src/dashboards/pipeline.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const PipelineDashboard: Dashboard = {
aggregate: 'sum',
valueField: 'amount',
filter: { stage: { $nin: ['closed_won', 'closed_lost'] } },
dataset: 'opportunity_metrics',
values: ['total_amount'],
options: { format: 'currency', currency: 'USD' },
layout: { x: 0, y: 0, w: 4, h: 2 },
},
Expand All @@ -57,6 +59,8 @@ export const PipelineDashboard: Dashboard = {
},
},
compareTo: 'previousPeriod',
dataset: 'opportunity_metrics',
values: ['total_amount'],
options: { format: 'currency', currency: 'USD' },
layout: { x: 4, y: 0, w: 4, h: 2 },
},
Expand All @@ -76,6 +80,8 @@ export const PipelineDashboard: Dashboard = {
},
},
compareTo: 'previousYear',
dataset: 'opportunity_metrics',
values: ['avg_amount'],
options: { format: 'currency', currency: 'USD' },
layout: { x: 8, y: 0, w: 4, h: 2 },
},
Expand All @@ -94,6 +100,9 @@ export const PipelineDashboard: Dashboard = {
close_date: { $gte: '{1_years_ago}', $lte: '{today}' },
},
compareTo: 'previousYear',
dataset: 'opportunity_metrics',
dimensions: ['close_date'],
values: ['opp_count'],
layout: { x: 0, y: 2, w: 8, h: 4 },
},
{
Expand All @@ -111,6 +120,9 @@ export const PipelineDashboard: Dashboard = {
},
},
compareTo: 'previousPeriod',
dataset: 'opportunity_metrics',
dimensions: ['stage'],
values: ['opp_count'],
layout: { x: 8, y: 2, w: 4, h: 4 },
},

Expand All @@ -125,6 +137,9 @@ export const PipelineDashboard: Dashboard = {
valueField: 'amount',
categoryField: 'stage',
filter: { stage: { $nin: ['closed_won', 'closed_lost'] } },
dataset: 'opportunity_metrics',
dimensions: ['stage'],
values: ['total_amount'],
layout: { x: 0, y: 6, w: 6, h: 4 },
},
],
Expand Down
3 changes: 3 additions & 0 deletions examples/app-crm/src/datasets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

export { OpportunityDataset } from './opportunity.dataset.js';
29 changes: 29 additions & 0 deletions examples/app-crm/src/datasets/opportunity.dataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { defineDataset } from '@objectstack/spec/ui';

/**
* Opportunity analytics dataset (ADR-0021).
*
* The single semantic source of truth for pipeline metrics. The Pipeline
* dashboard binds every widget to this dataset and selects measures BY NAME
* (`total_amount`, `avg_amount`, `opp_count`) so "pipeline revenue" means the
* same thing on every tile. Single-object dataset (no `include`).
*/
export const OpportunityDataset = defineDataset({
name: 'opportunity_metrics',
label: 'Opportunity Metrics',
description: 'Semantic layer for sales-pipeline counts and amounts',
object: 'crm_opportunity',

dimensions: [
{ name: 'stage', label: 'Stage', field: 'stage', type: 'string' },
{ name: 'close_date', label: 'Close Date', field: 'close_date', type: 'date' },
],

measures: [
{ name: 'opp_count', label: 'Opportunities', aggregate: 'count' },
{ name: 'total_amount', label: 'Total Amount', aggregate: 'sum', field: 'amount', format: '$0,0' },
{ name: 'avg_amount', label: 'Avg Deal Size', aggregate: 'avg', field: 'amount', format: '$0,0' },
],
});
13 changes: 11 additions & 2 deletions examples/app-crm/src/reports/sales-by-stage.report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import type { UI } from '@objectstack/spec';

/**
* Example report — total opportunity amount grouped by stage.
*
* ADR-0021 Phase 2: bound to the `opportunity_metrics` dataset (rows = stage,
* values = total_amount) alongside the legacy inline query. The legacy form was
* also corrected to actually group + sum (it previously listed rows flat despite
* its "grouped by stage" label), so both forms compute the same number and the
* reconciliation harness can verify them.
*/
export const SalesByStageReport: UI.Report = {
name: 'crm_sales_by_stage',
Expand All @@ -13,7 +19,10 @@ export const SalesByStageReport: UI.Report = {
type: 'summary',
columns: [
{ field: 'stage', label: 'Stage' },
{ field: 'name', label: 'Opportunity' },
{ field: 'amount', label: 'Amount' },
{ field: 'amount', label: 'Amount', aggregate: 'sum' },
],
groupingsDown: [{ field: 'stage', sortOrder: 'asc' }],
dataset: 'opportunity_metrics',
rows: ['stage'],
values: ['total_amount'],
};
2 changes: 2 additions & 0 deletions examples/app-showcase/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as objects from './src/objects/index.js';
import { TaskViews, ProjectViews } from './src/views/index.js';
import { ShowcaseApp } from './src/apps/index.js';
import { ChartGalleryDashboard } from './src/dashboards/index.js';
import { ShowcaseTaskDataset, ShowcaseProjectDataset } from './src/datasets/index.js';
import { allReports } from './src/reports/index.js';
import { allActions } from './src/actions/index.js';
import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage } from './src/pages/index.js';
Expand Down Expand Up @@ -115,6 +116,7 @@ export default defineStack({
views: [TaskViews, ProjectViews],
pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage],
dashboards: [ChartGalleryDashboard],
datasets: [ShowcaseTaskDataset, ShowcaseProjectDataset],
reports: allReports,
actions: allActions,
themes: allThemes,
Expand Down
2 changes: 1 addition & 1 deletion examples/app-showcase/src/apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ShowcaseApp = App.create({
icon: 'chart-bar',
children: [
{ id: 'nav_charts', type: 'dashboard', dashboardName: 'showcase_chart_gallery', label: 'Chart Gallery', icon: 'layout-dashboard' },
{ id: 'nav_report_tabular', type: 'report', reportName: 'showcase_task_list', label: 'Task List', icon: 'table' },
{ id: 'nav_report_tabular', type: 'object', objectName: 'showcase_task', viewName: 'tabular', label: 'Task List', icon: 'table' },
{ id: 'nav_report_summary', type: 'report', reportName: 'showcase_hours_by_status', label: 'Hours by Status', icon: 'sigma' },
{ id: 'nav_report_matrix', type: 'report', reportName: 'showcase_status_priority_matrix', label: 'Status × Priority', icon: 'grid-3x3' },
{ id: 'nav_report_joined', type: 'report', reportName: 'showcase_task_overview', label: 'Task Overview', icon: 'layers' },
Expand Down
Loading
Loading