feat: report editor

commit bfcf271a64c33a60f61f511cec2198d9c8a9c51a
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Wed Nov 26 12:32:40 2025 +0100

    wip

commit 8cd3b89fa3
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Tue Nov 25 22:33:58 2025 +0100

    funnel

commit 95af86dc44
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Tue Nov 25 22:23:25 2025 +0100

    wip

commit 727a218e6b
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Tue Nov 25 10:18:26 2025 +0100

    conversion wip

commit 958ba535d6
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Tue Nov 25 10:18:20 2025 +0100

    wip

commit 3bbeb927cc
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Tue Nov 25 09:18:48 2025 +0100

    wip

commit d99335e2f4
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Mon Nov 24 18:08:10 2025 +0100

    wip

commit 1fa61b1ae9
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Mon Nov 24 15:50:28 2025 +0100

    ts

commit 548747d826
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Mon Nov 24 13:17:01 2025 +0100

    fix typecheck events -> series

commit 7b18544085
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Mon Nov 24 13:06:46 2025 +0100

    fix report table

commit 57697a5a39
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Sat Nov 22 00:05:13 2025 +0100

    wip

commit 06fb6c4f3c
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Fri Nov 21 11:21:17 2025 +0100

    wip

commit dd71fd4e11
Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com>
Date:   Thu Nov 20 13:56:58 2025 +0100

    formulas
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-26 12:33:41 +01:00
parent 828c8c4f91
commit b421474616
70 changed files with 6867 additions and 1918 deletions

View File

@@ -57,12 +57,70 @@ export const zChartEvent = z.object({
.default([])
.describe('Filters applied specifically to this event'),
});
export const zChartFormula = z.object({
id: z
.string()
.optional()
.describe('Unique identifier for the formula configuration'),
type: z.literal('formula'),
formula: z.string().describe('The formula expression (e.g., A+B, A/B)'),
displayName: z
.string()
.optional()
.describe('A user-friendly name for display purposes'),
});
// Event with type field for discriminated union
export const zChartEventWithType = zChartEvent.extend({
type: z.literal('event'),
});
export const zChartEventItem = z.discriminatedUnion('type', [
zChartEventWithType,
zChartFormula,
]);
export const zChartBreakdown = z.object({
id: z.string().optional(),
name: z.string(),
});
export const zChartEvents = z.array(zChartEvent);
// Support both old format (array of events without type) and new format (array of event/formula items)
// Preprocess to normalize: if item has 'type' field, use discriminated union; otherwise, add type: 'event'
export const zChartSeries = z.preprocess((val) => {
if (!val) return val;
let processedVal = val;
// If the input is an object with numeric keys, convert it to an array
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
const keys = Object.keys(val).sort(
(a, b) => Number.parseInt(a) - Number.parseInt(b),
);
processedVal = keys.map((key) => (val as any)[key]);
}
if (!Array.isArray(processedVal)) return processedVal;
return processedVal.map((item: any) => {
// If item already has type field, return as-is
if (item && typeof item === 'object' && 'type' in item) {
return item;
}
// Otherwise, add type: 'event' for backward compatibility
if (item && typeof item === 'object' && 'name' in item) {
return { ...item, type: 'event' };
}
return item;
});
}, z
.array(zChartEventItem)
.describe(
'Array of series (events or formulas) to be tracked and displayed in the chart',
));
// Keep zChartEvents as an alias for backward compatibility during migration
export const zChartEvents = zChartSeries;
export const zChartBreakdowns = z.array(zChartBreakdown);
export const zChartType = z.enum(objectToZodEnums(chartTypes));
@@ -77,7 +135,7 @@ export const zRange = z.enum(objectToZodEnums(timeWindows));
export const zCriteria = z.enum(['on_or_after', 'on']);
export const zChartInput = z.object({
export const zChartInputBase = z.object({
chartType: zChartType
.default('linear')
.describe('What type of chart should be displayed'),
@@ -86,8 +144,8 @@ export const zChartInput = z.object({
.describe(
'The time interval for data aggregation (e.g., day, week, month)',
),
events: zChartEvents.describe(
'Array of events to be tracked and displayed in the chart',
series: zChartSeries.describe(
'Array of series (events or formulas) to be tracked and displayed in the chart',
),
breakdowns: zChartBreakdowns
.default([])
@@ -144,7 +202,15 @@ export const zChartInput = z.object({
.describe('Time window in hours for funnel analysis'),
});
export const zReportInput = zChartInput.extend({
export const zChartInput = z.preprocess((val) => {
if (val && typeof val === 'object' && 'events' in val && !('series' in val)) {
// Migrate old 'events' field to 'series'
return { ...val, series: val.events };
}
return val;
}, zChartInputBase);
export const zReportInput = zChartInputBase.extend({
name: z.string().describe('The user-defined name for the report'),
lineType: zLineType.describe('The visual style of the line in the chart'),
unit: z