forked from jappel/leistungsbilanz-ts
Add print view, board info card and document logo upload
- Seitenansicht (/projects/:projectId/print): tabular cover page, board overview with supply structure and power sums, one plan per board with a repeating title block; PDF via the browser print dialog with named @page rules (A4 portrait cover, A4/A3 landscape plans, page x of y). - Distribution-board info card (board number, location, fed-from, remark) in the board edit dialog, persisted through the additively extended distribution-board.update command (migration 0008, snapshot schema 6, legacy subtree commands normalized). - Global document logo upload (PNG/JPEG/SVG, max 2 MB) outside project revisions and snapshots. - fed-from stays free text; the overview hierarchy and cumulated sums are preliminary until the feed link is modeled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2e3e85a8e5
commit
4779e4a1a7
40 changed files with 5297 additions and 33 deletions
|
|
@ -1,14 +1,21 @@
|
|||
import {
|
||||
distributionBoardInfoFieldMaxLengths,
|
||||
distributionBoardInfoFields,
|
||||
distributionBoardSupplyTypes,
|
||||
type DistributionBoardInfoField,
|
||||
type DistributionBoardSupplyType,
|
||||
} from "../../shared/constants/distribution-board.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
|
||||
export const distributionBoardUpdateCommandType =
|
||||
"distribution-board.update" as const;
|
||||
// The info-card fields were added additively to schema version 1: every
|
||||
// previously stored command remains valid, and the inverse is still derived
|
||||
// from the persisted row for exactly the changed fields.
|
||||
export const distributionBoardUpdateCommandSchemaVersion = 1 as const;
|
||||
|
||||
export interface DistributionBoardUpdateValues {
|
||||
export interface DistributionBoardUpdateValues
|
||||
extends Record<DistributionBoardInfoField, string | null> {
|
||||
floorId: string | null;
|
||||
supplyType: DistributionBoardSupplyType | null;
|
||||
simultaneityFactor: number;
|
||||
|
|
@ -82,16 +89,20 @@ export function assertDistributionBoardUpdateProjectCommand(
|
|||
for (const change of changes) {
|
||||
if (
|
||||
!isPlainObject(change) ||
|
||||
typeof change.field !== "string" ||
|
||||
(change.field !== "floorId" &&
|
||||
change.field !== "supplyType" &&
|
||||
change.field !== "simultaneityFactor") ||
|
||||
change.field !== "simultaneityFactor" &&
|
||||
!isInfoField(change.field)) ||
|
||||
seen.has(change.field)
|
||||
) {
|
||||
throw new Error(
|
||||
"Distribution-board update contains an invalid or duplicate field."
|
||||
);
|
||||
}
|
||||
if (change.field === "floorId") {
|
||||
if (isInfoField(change.field)) {
|
||||
assertInfoFieldValue(change.field, change.value);
|
||||
} else if (change.field === "floorId") {
|
||||
if (
|
||||
change.value !== null &&
|
||||
(typeof change.value !== "string" || !change.value.trim())
|
||||
|
|
@ -121,6 +132,29 @@ export function assertDistributionBoardUpdateProjectCommand(
|
|||
}
|
||||
}
|
||||
|
||||
function isInfoField(field: string): field is DistributionBoardInfoField {
|
||||
return (distributionBoardInfoFields as readonly string[]).includes(field);
|
||||
}
|
||||
|
||||
function assertInfoFieldValue(
|
||||
field: DistributionBoardInfoField,
|
||||
value: unknown
|
||||
) {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
typeof value !== "string" ||
|
||||
!value.trim() ||
|
||||
value !== value.trim() ||
|
||||
value.length > distributionBoardInfoFieldMaxLengths[field]
|
||||
) {
|
||||
throw new Error(
|
||||
`${field} must be a trimmed, non-empty string of at most ${distributionBoardInfoFieldMaxLengths[field]} characters or null.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue