Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import type {
IEnrichedSchema,
IHandlerProxy,
IReportDefinition,
IResolvedModelSpec,
} from '@feasibleone/blong';
import React from 'react';
/** Capitalise the first character of a string */
function capital(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
/**
* Build the report schema from the model.
*
* - `params` card fields: flat filter fields extracted from `object` properties where
* `filter: true` (or the explicit list in `reportDef.params`).
* - `result` table field: array widget using `listAction` + columns from the model or reportDef.
*
* The params are stored as flat top-level fields so they are passed directly as filter params
* to the `listAction` (e.g. `{coralName: 'Pink', paging: {...}}`). This is compatible with
* the standard `find` API which accepts flat filter keys alongside `paging` / `orderBy`.
*/
function buildReportSchema(
model: IResolvedModelSpec,
mergedSchema: IEnrichedSchema,
reportDef: IReportDefinition,
): IEnrichedSchema {
const {object, keyField} = model;
const objectProps =
(mergedSchema.properties?.[object]?.properties as Record<string, unknown> | undefined) ??
{};
// Params: either explicit list or all fields with filter: true
const paramFields =
reportDef.params ??
Object.entries(objectProps)
.filter(([, v]) => (v as {filter?: boolean}).filter)
.map(([k]) => k);
// Columns: explicit list from reportDef, or browse columns from model widget config, or nameField
const browseColumns = reportDef.columns ??
(mergedSchema.properties?.[object]?.widget?.columns as string[] | undefined) ?? [
model.nameField.split('.').pop()!,
];
// Build properties for the result table items from object schema
const resultItemProperties = Object.fromEntries(
browseColumns
.map(col => [col, objectProps[col] ?? {}] as const)
.concat([[keyField, objectProps[keyField] ?? {}]]),
);
return {
properties: {
// Flat filter fields (from the object's own properties)
...Object.fromEntries(paramFields.map(f => [f, objectProps[f] ?? {}])),
// Result table
result: {
type: 'array',
title: '',
widget: {
type: 'table',
listAction: reportDef.action ?? model.methods.find,
columns: browseColumns,
keyField,
resultSet: reportDef.resultSet ?? 'items',
// Disable row-level edit/delete actions in the result table
actions: {allowEdit: false, allowDelete: false},
},
items: {properties: resultItemProperties},
},
},
};
}
/**
* Report page factory.
*
* Builds an Editor-based report page with two cards:
* - **params**: filter fields that the user fills in before running the report.
* - **result**: a server-fetched table that populates when the user clicks "Run Report".
*
* @param model Resolved model spec.
* @param blong Handler proxy from the orchestrator context.
* @param reportId Optional report identifier. Defaults to `${subject}${Capital(object)}List`
* which generates a standard list report using `methods.find`.
*/
export async function subjectObjectReport(
model: IResolvedModelSpec,
blong: IHandlerProxy<unknown>,
reportId?: string,
) {
const {subject, object} = model;
// Resolve which report definition to use
const defaultId = `${subject}${capital(object)}List`;
const id = reportId ?? defaultId;
const reportDef: IReportDefinition = model.reports[id] ?? {};
const title = reportDef.title ?? model.report.title;
const permission = reportDef.permission ?? model.report.permission;
return async () => ({
title,
permission,
icon: 'pi pi-chart-bar',
component: async () => {
const [schemaOverride, {Editor}] = await Promise.all([
blong.handler.subjectObjectSchema<IEnrichedSchema>({subject, object}, {}),
import('../../components/Editor/Editor.js'),
]);
const mergedSchema = blong.lib.merge({}, model.schema, schemaOverride);
const reportSchema = buildReportSchema(model, mergedSchema, reportDef);
// Params card: all flat filter fields (everything except 'result')
const paramWidgets = Object.keys(reportSchema.properties ?? {}).filter(
k => k !== 'result',
);
const reportCards = {
params: {
label: '',
widgets: paramWidgets,
className: 'col-12',
fieldClass: ' md:col-4 xl:col-3',
},
result: {
label: 'Results',
widgets: ['result'],
className: 'col-12',
},
};
const reportLayouts = {report: ['params', 'result']};
// eslint-disable-next-line @eslint-react/component-hook-factories
function ReportPage(props: Record<string, unknown>) {
return (
<Editor
schema={reportSchema}
cards={reportCards}
layouts={reportLayouts}
layout="report"
queryAction={reportDef.action ?? model.methods.find}
editable={false}
{...(props as React.ComponentProps<typeof Editor>)}
/>
);
}
return ReportPage as unknown as React.ComponentType;
},
});
}
|