fix(build): split custom-report registry into client-safe metadata + server query module
The custom-report builder (client component) imported the registry which pulls in @/lib/db (postgres -> tls), breaking the production build. Extract ENTITY_META/ENTITY_KEYS/column defs into registry-meta.ts (no DB imports); registry.ts keeps runQuery + composes ENTITY_REGISTRY. Pre-existing blocker surfaced during pre-merge build validation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ import {
|
|||||||
import { ReportTemplatesButton } from '@/components/reports/shared/report-templates-button';
|
import { ReportTemplatesButton } from '@/components/reports/shared/report-templates-button';
|
||||||
import { apiFetch } from '@/lib/api/client';
|
import { apiFetch } from '@/lib/api/client';
|
||||||
import { toastError } from '@/lib/api/toast-error';
|
import { toastError } from '@/lib/api/toast-error';
|
||||||
import { ENTITY_KEYS, ENTITY_REGISTRY, type EntityKey } from '@/lib/reports/custom/registry';
|
import { ENTITY_KEYS, ENTITY_META, type EntityKey } from '@/lib/reports/custom/registry-meta';
|
||||||
import { formatMoney, formatNumber } from '@/lib/reports/format-currency';
|
import { formatMoney, formatNumber } from '@/lib/reports/format-currency';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +67,7 @@ interface CustomTemplateConfig extends Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultColumnsFor(entity: EntityKey): string[] {
|
function defaultColumnsFor(entity: EntityKey): string[] {
|
||||||
return ENTITY_REGISTRY[entity].columns.filter((c) => c.defaultSelected).map((c) => c.key);
|
return ENTITY_META[entity].columns.filter((c) => c.defaultSelected).map((c) => c.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CustomReportBuilder({ portSlug: _portSlug }: { portSlug: string }) {
|
export function CustomReportBuilder({ portSlug: _portSlug }: { portSlug: string }) {
|
||||||
@@ -187,7 +187,7 @@ export function CustomReportBuilder({ portSlug: _portSlug }: { portSlug: string
|
|||||||
toast.success(`Downloaded ${filename}`);
|
toast.success(`Downloaded ${filename}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const def = ENTITY_REGISTRY[entity];
|
const def = ENTITY_META[entity];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -224,7 +224,7 @@ export function CustomReportBuilder({ portSlug: _portSlug }: { portSlug: string
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{ENTITY_KEYS.map((k) => (
|
{ENTITY_KEYS.map((k) => (
|
||||||
<SelectItem key={k} value={k}>
|
<SelectItem key={k} value={k}>
|
||||||
{ENTITY_REGISTRY[k].label}
|
{ENTITY_META[k].label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
144
src/lib/reports/custom/registry-meta.ts
Normal file
144
src/lib/reports/custom/registry-meta.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* Custom-report entity registry — client-safe metadata.
|
||||||
|
*
|
||||||
|
* This module holds ONLY the pure-data parts of the custom-report
|
||||||
|
* registry: entity keys, the filter/column type contracts, the
|
||||||
|
* per-entity column allowlists, and the per-entity metadata
|
||||||
|
* (label/description/dateAxis/columns). It has NO `@/lib/db`, drizzle,
|
||||||
|
* or schema imports, so it is safe to import from client components
|
||||||
|
* (e.g. the column-picker UI in the custom report builder).
|
||||||
|
*
|
||||||
|
* The server-only query logic (`runQuery` + drizzle) lives in
|
||||||
|
* `registry.ts`, which imports the column arrays + meta from here and
|
||||||
|
* composes the full `ENTITY_REGISTRY` consumed by the run endpoint.
|
||||||
|
*
|
||||||
|
* Adding a new entity:
|
||||||
|
* 1. Append it to ENTITY_KEYS.
|
||||||
|
* 2. Add its column array + an ENTITY_META entry here.
|
||||||
|
* 3. Add the matching `runQuery` + ENTITY_REGISTRY entry in
|
||||||
|
* `registry.ts`.
|
||||||
|
* 4. The UI's entity-picker reads ENTITY_META directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ENTITY_KEYS = ['clients', 'interests', 'berths', 'tenancies'] as const;
|
||||||
|
export type EntityKey = (typeof ENTITY_KEYS)[number];
|
||||||
|
|
||||||
|
export interface CustomFilter {
|
||||||
|
/** ISO 8601 — inclusive lower bound on the entity's "date" column
|
||||||
|
* (createdAt or equivalent — see entity definition). */
|
||||||
|
from?: Date;
|
||||||
|
/** ISO 8601 — inclusive upper bound. */
|
||||||
|
to?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnDefinition {
|
||||||
|
/** Stable key. Persisted in saved-template configs. */
|
||||||
|
key: string;
|
||||||
|
/** Human-readable column header used in CSV/PDF output + the UI
|
||||||
|
* multi-select. */
|
||||||
|
label: string;
|
||||||
|
/** Default selection in the UI. Reps can uncheck. */
|
||||||
|
defaultSelected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client-safe metadata for an entity — everything except the
|
||||||
|
* server-only `runQuery`. The full `CustomEntityDefinition` (meta +
|
||||||
|
* runQuery) lives in `registry.ts`.
|
||||||
|
*/
|
||||||
|
export interface CustomEntityMeta {
|
||||||
|
key: EntityKey;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
/** Friendly name for the date filter — different entities anchor
|
||||||
|
* the date range to different timestamps. */
|
||||||
|
dateAxis: string;
|
||||||
|
columns: ColumnDefinition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Clients ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const CLIENTS_COLUMNS: ColumnDefinition[] = [
|
||||||
|
{ key: 'fullName', label: 'Full name', defaultSelected: true },
|
||||||
|
{ key: 'nationalityIso', label: 'Nationality', defaultSelected: false },
|
||||||
|
{ key: 'preferredLanguage', label: 'Preferred language' },
|
||||||
|
{ key: 'preferredContactMethod', label: 'Preferred contact', defaultSelected: false },
|
||||||
|
{ key: 'source', label: 'Source', defaultSelected: true },
|
||||||
|
{ key: 'createdAt', label: 'Created', defaultSelected: true },
|
||||||
|
{ key: 'archivedAt', label: 'Archived at' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Interests ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const INTERESTS_COLUMNS: ColumnDefinition[] = [
|
||||||
|
{ key: 'clientName', label: 'Client', defaultSelected: true },
|
||||||
|
{ key: 'primaryBerth', label: 'Primary berth', defaultSelected: true },
|
||||||
|
{ key: 'pipelineStage', label: 'Stage', defaultSelected: true },
|
||||||
|
{ key: 'leadCategory', label: 'Lead category' },
|
||||||
|
{ key: 'outcome', label: 'Outcome', defaultSelected: true },
|
||||||
|
{ key: 'source', label: 'Source', defaultSelected: false },
|
||||||
|
{ key: 'depositExpectedAmount', label: 'Deposit expected (amt)', defaultSelected: false },
|
||||||
|
{ key: 'depositExpectedCurrency', label: 'Deposit expected (ccy)' },
|
||||||
|
{ key: 'dateFirstContact', label: 'First contact', defaultSelected: false },
|
||||||
|
{ key: 'dateLastContact', label: 'Last contact', defaultSelected: false },
|
||||||
|
{ key: 'createdAt', label: 'Created', defaultSelected: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Berths ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const BERTHS_COLUMNS: ColumnDefinition[] = [
|
||||||
|
{ key: 'mooringNumber', label: 'Mooring', defaultSelected: true },
|
||||||
|
{ key: 'area', label: 'Area' },
|
||||||
|
{ key: 'status', label: 'Status', defaultSelected: true },
|
||||||
|
{ key: 'length', label: 'Length (m)' },
|
||||||
|
{ key: 'width', label: 'Width (m)' },
|
||||||
|
{ key: 'draft', label: 'Draft (m)' },
|
||||||
|
{ key: 'price', label: 'Price', defaultSelected: true },
|
||||||
|
{ key: 'priceCurrency', label: 'Currency' },
|
||||||
|
{ key: 'createdAt', label: 'Created' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Tenancies ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const TENANCIES_COLUMNS: ColumnDefinition[] = [
|
||||||
|
{ key: 'clientName', label: 'Client', defaultSelected: true },
|
||||||
|
{ key: 'mooringNumber', label: 'Berth', defaultSelected: true },
|
||||||
|
{ key: 'tenureType', label: 'Tenure type', defaultSelected: true },
|
||||||
|
{ key: 'startDate', label: 'Start', defaultSelected: true },
|
||||||
|
{ key: 'endDate', label: 'End', defaultSelected: true },
|
||||||
|
{ key: 'status', label: 'Status', defaultSelected: true },
|
||||||
|
{ key: 'createdAt', label: 'Created' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Metadata registry ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const ENTITY_META: Record<EntityKey, CustomEntityMeta> = {
|
||||||
|
clients: {
|
||||||
|
key: 'clients',
|
||||||
|
label: 'Clients',
|
||||||
|
description: 'People in your CRM: name, source, contact preferences.',
|
||||||
|
dateAxis: 'Created',
|
||||||
|
columns: CLIENTS_COLUMNS,
|
||||||
|
},
|
||||||
|
interests: {
|
||||||
|
key: 'interests',
|
||||||
|
label: 'Interests / deals',
|
||||||
|
description: 'Sales pipeline: stage, outcome, value, deposit details.',
|
||||||
|
dateAxis: 'Created',
|
||||||
|
columns: INTERESTS_COLUMNS,
|
||||||
|
},
|
||||||
|
berths: {
|
||||||
|
key: 'berths',
|
||||||
|
label: 'Berths',
|
||||||
|
description: 'Mooring inventory: dimensions, status, price.',
|
||||||
|
dateAxis: 'Created',
|
||||||
|
columns: BERTHS_COLUMNS,
|
||||||
|
},
|
||||||
|
tenancies: {
|
||||||
|
key: 'tenancies',
|
||||||
|
label: 'Tenancies',
|
||||||
|
description: 'Berth leases / annual contracts: dates, tenure type, status.',
|
||||||
|
dateAxis: 'Created',
|
||||||
|
columns: TENANCIES_COLUMNS,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Custom-report entity registry.
|
* Custom-report entity registry — server query layer.
|
||||||
*
|
*
|
||||||
* The custom builder is the catch-all for slices the four canonical
|
* The custom builder is the catch-all for slices the four canonical
|
||||||
* reports don't cover — pick an entity, pick columns, optionally
|
* reports don't cover — pick an entity, pick columns, optionally
|
||||||
@@ -8,19 +8,27 @@
|
|||||||
* from the launch-readiness scope (companies, yachts, invoices,
|
* from the launch-readiness scope (companies, yachts, invoices,
|
||||||
* payments, deals, sends) layer in as their schemas are wired.
|
* payments, deals, sends) layer in as their schemas are wired.
|
||||||
*
|
*
|
||||||
|
* This module is SERVER-ONLY: it pulls in `@/lib/db` + drizzle to run
|
||||||
|
* the underlying queries. The client-safe metadata (entity keys,
|
||||||
|
* column allowlists, labels/descriptions, the filter/column type
|
||||||
|
* contracts) lives in `registry-meta.ts` and is imported here. Client
|
||||||
|
* components (e.g. the column-picker UI) MUST import from
|
||||||
|
* `registry-meta.ts`, never this file, or they drag the DB layer into
|
||||||
|
* the browser bundle.
|
||||||
|
*
|
||||||
* Each entity defines:
|
* Each entity defines:
|
||||||
* - `columns`: an allowlist of column keys + human labels + a
|
* - `columns`: an allowlist of column keys + human labels (sourced
|
||||||
* resolver that extracts the value from a fetched row. The
|
* from `registry-meta.ts`). The allowlist gates which fields a rep
|
||||||
* allowlist matters: it gates which fields a rep can pull into a
|
* can pull into a CSV, so PII columns can be opt-in per role later.
|
||||||
* CSV, so PII columns can be opt-in per role later.
|
|
||||||
* - `runQuery`: a Drizzle select that joins whatever the columns
|
* - `runQuery`: a Drizzle select that joins whatever the columns
|
||||||
* need, applies the port filter + optional date range, and
|
* need, applies the port filter + optional date range, and
|
||||||
* returns raw rows.
|
* returns raw rows.
|
||||||
*
|
*
|
||||||
* Adding a new entity:
|
* Adding a new entity:
|
||||||
* 1. Append it to ENTITY_KEYS.
|
* 1. Append it to ENTITY_KEYS (in registry-meta.ts).
|
||||||
* 2. Add a CustomEntityDefinition entry to ENTITY_REGISTRY.
|
* 2. Add its column array + ENTITY_META entry in registry-meta.ts.
|
||||||
* 3. Update the UI's entity-picker (it reads ENTITY_REGISTRY directly).
|
* 3. Add the matching `runQuery` + ENTITY_REGISTRY entry here.
|
||||||
|
* 4. The UI's entity-picker reads ENTITY_META directly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { and, asc, desc, eq, gte, lte, sql, type SQL } from 'drizzle-orm';
|
import { and, asc, desc, eq, gte, lte, sql, type SQL } from 'drizzle-orm';
|
||||||
@@ -32,35 +40,25 @@ import { interests, interestBerths } from '@/lib/db/schema/interests';
|
|||||||
import { berthTenancies as tenancies } from '@/lib/db/schema/tenancies';
|
import { berthTenancies as tenancies } from '@/lib/db/schema/tenancies';
|
||||||
import { STAGE_LABELS, type PipelineStage } from '@/lib/constants';
|
import { STAGE_LABELS, type PipelineStage } from '@/lib/constants';
|
||||||
|
|
||||||
export const ENTITY_KEYS = ['clients', 'interests', 'berths', 'tenancies'] as const;
|
import {
|
||||||
export type EntityKey = (typeof ENTITY_KEYS)[number];
|
ENTITY_KEYS,
|
||||||
|
ENTITY_META,
|
||||||
|
type ColumnDefinition,
|
||||||
|
type CustomEntityMeta,
|
||||||
|
type CustomFilter,
|
||||||
|
type EntityKey,
|
||||||
|
} from './registry-meta';
|
||||||
|
|
||||||
export interface CustomFilter {
|
// Re-export the client-safe contracts so existing SERVER imports of
|
||||||
/** ISO 8601 — inclusive lower bound on the entity's "date" column
|
// this module keep working unchanged.
|
||||||
* (createdAt or equivalent — see entity definition). */
|
export { ENTITY_KEYS };
|
||||||
from?: Date;
|
export type { ColumnDefinition, CustomFilter, EntityKey };
|
||||||
/** ISO 8601 — inclusive upper bound. */
|
|
||||||
to?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ColumnDefinition {
|
/**
|
||||||
/** Stable key. Persisted in saved-template configs. */
|
* Full server-side entity definition: the client-safe metadata plus
|
||||||
key: string;
|
* the server-only `runQuery`.
|
||||||
/** Human-readable column header used in CSV/PDF output + the UI
|
*/
|
||||||
* multi-select. */
|
export interface CustomEntityDefinition extends CustomEntityMeta {
|
||||||
label: string;
|
|
||||||
/** Default selection in the UI. Reps can uncheck. */
|
|
||||||
defaultSelected?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomEntityDefinition {
|
|
||||||
key: EntityKey;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
/** Friendly name for the date filter — different entities anchor
|
|
||||||
* the date range to different timestamps. */
|
|
||||||
dateAxis: string;
|
|
||||||
columns: ColumnDefinition[];
|
|
||||||
/** Execute the underlying query and return raw rows keyed by column
|
/** Execute the underlying query and return raw rows keyed by column
|
||||||
* key. The runner is responsible for the joins + port scoping;
|
* key. The runner is responsible for the joins + port scoping;
|
||||||
* callers only pass which columns they want + the filter. */
|
* callers only pass which columns they want + the filter. */
|
||||||
@@ -82,16 +80,6 @@ function applyDateRange(column: ReturnType<typeof sql<Date>>, filter: CustomFilt
|
|||||||
|
|
||||||
// ─── Clients ─────────────────────────────────────────────────────────────────
|
// ─── Clients ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const CLIENTS_COLUMNS: ColumnDefinition[] = [
|
|
||||||
{ key: 'fullName', label: 'Full name', defaultSelected: true },
|
|
||||||
{ key: 'nationalityIso', label: 'Nationality', defaultSelected: false },
|
|
||||||
{ key: 'preferredLanguage', label: 'Preferred language' },
|
|
||||||
{ key: 'preferredContactMethod', label: 'Preferred contact', defaultSelected: false },
|
|
||||||
{ key: 'source', label: 'Source', defaultSelected: true },
|
|
||||||
{ key: 'createdAt', label: 'Created', defaultSelected: true },
|
|
||||||
{ key: 'archivedAt', label: 'Archived at' },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function runClientsQuery({
|
async function runClientsQuery({
|
||||||
portId,
|
portId,
|
||||||
filter,
|
filter,
|
||||||
@@ -120,20 +108,6 @@ async function runClientsQuery({
|
|||||||
|
|
||||||
// ─── Interests ───────────────────────────────────────────────────────────────
|
// ─── Interests ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const INTERESTS_COLUMNS: ColumnDefinition[] = [
|
|
||||||
{ key: 'clientName', label: 'Client', defaultSelected: true },
|
|
||||||
{ key: 'primaryBerth', label: 'Primary berth', defaultSelected: true },
|
|
||||||
{ key: 'pipelineStage', label: 'Stage', defaultSelected: true },
|
|
||||||
{ key: 'leadCategory', label: 'Lead category' },
|
|
||||||
{ key: 'outcome', label: 'Outcome', defaultSelected: true },
|
|
||||||
{ key: 'source', label: 'Source', defaultSelected: false },
|
|
||||||
{ key: 'depositExpectedAmount', label: 'Deposit expected (amt)', defaultSelected: false },
|
|
||||||
{ key: 'depositExpectedCurrency', label: 'Deposit expected (ccy)' },
|
|
||||||
{ key: 'dateFirstContact', label: 'First contact', defaultSelected: false },
|
|
||||||
{ key: 'dateLastContact', label: 'Last contact', defaultSelected: false },
|
|
||||||
{ key: 'createdAt', label: 'Created', defaultSelected: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function runInterestsQuery({
|
async function runInterestsQuery({
|
||||||
portId,
|
portId,
|
||||||
filter,
|
filter,
|
||||||
@@ -182,18 +156,6 @@ async function runInterestsQuery({
|
|||||||
|
|
||||||
// ─── Berths ──────────────────────────────────────────────────────────────────
|
// ─── Berths ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const BERTHS_COLUMNS: ColumnDefinition[] = [
|
|
||||||
{ key: 'mooringNumber', label: 'Mooring', defaultSelected: true },
|
|
||||||
{ key: 'area', label: 'Area' },
|
|
||||||
{ key: 'status', label: 'Status', defaultSelected: true },
|
|
||||||
{ key: 'length', label: 'Length (m)' },
|
|
||||||
{ key: 'width', label: 'Width (m)' },
|
|
||||||
{ key: 'draft', label: 'Draft (m)' },
|
|
||||||
{ key: 'price', label: 'Price', defaultSelected: true },
|
|
||||||
{ key: 'priceCurrency', label: 'Currency' },
|
|
||||||
{ key: 'createdAt', label: 'Created' },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function runBerthsQuery({
|
async function runBerthsQuery({
|
||||||
portId,
|
portId,
|
||||||
filter,
|
filter,
|
||||||
@@ -224,16 +186,6 @@ async function runBerthsQuery({
|
|||||||
|
|
||||||
// ─── Tenancies ───────────────────────────────────────────────────────────────
|
// ─── Tenancies ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const TENANCIES_COLUMNS: ColumnDefinition[] = [
|
|
||||||
{ key: 'clientName', label: 'Client', defaultSelected: true },
|
|
||||||
{ key: 'mooringNumber', label: 'Berth', defaultSelected: true },
|
|
||||||
{ key: 'tenureType', label: 'Tenure type', defaultSelected: true },
|
|
||||||
{ key: 'startDate', label: 'Start', defaultSelected: true },
|
|
||||||
{ key: 'endDate', label: 'End', defaultSelected: true },
|
|
||||||
{ key: 'status', label: 'Status', defaultSelected: true },
|
|
||||||
{ key: 'createdAt', label: 'Created' },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function runTenanciesQuery({
|
async function runTenanciesQuery({
|
||||||
portId,
|
portId,
|
||||||
filter,
|
filter,
|
||||||
@@ -267,37 +219,21 @@ async function runTenanciesQuery({
|
|||||||
|
|
||||||
// ─── Registry ────────────────────────────────────────────────────────────────
|
// ─── Registry ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const ENTITY_REGISTRY: Record<EntityKey, CustomEntityDefinition> = {
|
const RUN_QUERIES: Record<EntityKey, CustomEntityDefinition['runQuery']> = {
|
||||||
clients: {
|
clients: runClientsQuery,
|
||||||
key: 'clients',
|
interests: runInterestsQuery,
|
||||||
label: 'Clients',
|
berths: runBerthsQuery,
|
||||||
description: 'People in your CRM: name, source, contact preferences.',
|
tenancies: runTenanciesQuery,
|
||||||
dateAxis: 'Created',
|
|
||||||
columns: CLIENTS_COLUMNS,
|
|
||||||
runQuery: runClientsQuery,
|
|
||||||
},
|
|
||||||
interests: {
|
|
||||||
key: 'interests',
|
|
||||||
label: 'Interests / deals',
|
|
||||||
description: 'Sales pipeline: stage, outcome, value, deposit details.',
|
|
||||||
dateAxis: 'Created',
|
|
||||||
columns: INTERESTS_COLUMNS,
|
|
||||||
runQuery: runInterestsQuery,
|
|
||||||
},
|
|
||||||
berths: {
|
|
||||||
key: 'berths',
|
|
||||||
label: 'Berths',
|
|
||||||
description: 'Mooring inventory: dimensions, status, price.',
|
|
||||||
dateAxis: 'Created',
|
|
||||||
columns: BERTHS_COLUMNS,
|
|
||||||
runQuery: runBerthsQuery,
|
|
||||||
},
|
|
||||||
tenancies: {
|
|
||||||
key: 'tenancies',
|
|
||||||
label: 'Tenancies',
|
|
||||||
description: 'Berth leases / annual contracts: dates, tenure type, status.',
|
|
||||||
dateAxis: 'Created',
|
|
||||||
columns: TENANCIES_COLUMNS,
|
|
||||||
runQuery: runTenanciesQuery,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full server registry: client-safe metadata (from `registry-meta.ts`)
|
||||||
|
* composed with the matching `runQuery` for each entity.
|
||||||
|
*/
|
||||||
|
export const ENTITY_REGISTRY: Record<EntityKey, CustomEntityDefinition> = ENTITY_KEYS.reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
acc[key] = { ...ENTITY_META[key], runQuery: RUN_QUERIES[key] };
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<EntityKey, CustomEntityDefinition>,
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user