Add estimate row project device handoff
This commit is contained in:
parent
8b1923e8ec
commit
a6500e2b26
9 changed files with 434 additions and 21 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { FormEvent, useState } from "react";
|
||||
import React, { FormEvent, type ReactNode, useState } from "react";
|
||||
import type {
|
||||
CreateProjectDeviceInput,
|
||||
GlobalDeviceDto,
|
||||
|
|
@ -14,10 +14,20 @@ import {
|
|||
} from "../../shared/constants/circuit-group";
|
||||
import { GzfPickerModal } from "./gzf-picker-modal";
|
||||
|
||||
export type ProjectDeviceModalInitialValues = Omit<
|
||||
Partial<CreateProjectDeviceInput>,
|
||||
"quantity" | "powerPerUnit"
|
||||
> & {
|
||||
quantity?: number | null;
|
||||
powerPerUnit?: number | null;
|
||||
};
|
||||
|
||||
interface ProjectDeviceModalProps {
|
||||
context?: ReactNode;
|
||||
existingDevices?: ReadonlyArray<Pick<ProjectDeviceDto, "name" | "displayName">>;
|
||||
globalDevices: GlobalDeviceDto[];
|
||||
initialDevice?: ProjectDeviceDto;
|
||||
initialValues?: Partial<CreateProjectDeviceInput>;
|
||||
initialValues?: ProjectDeviceModalInitialValues;
|
||||
isSaving: boolean;
|
||||
onClose: () => void;
|
||||
onImportGlobal: (globalDeviceId: string) => Promise<void>;
|
||||
|
|
@ -25,6 +35,8 @@ interface ProjectDeviceModalProps {
|
|||
}
|
||||
|
||||
export function ProjectDeviceModal({
|
||||
context,
|
||||
existingDevices = [],
|
||||
globalDevices,
|
||||
initialDevice,
|
||||
initialValues,
|
||||
|
|
@ -36,6 +48,11 @@ export function ProjectDeviceModal({
|
|||
const [values, setValues] = useState(() => toFormValues(initialDevice, initialValues));
|
||||
const [selectedGlobalDeviceId, setSelectedGlobalDeviceId] = useState("");
|
||||
const [isGzfPickerOpen, setIsGzfPickerOpen] = useState(false);
|
||||
const hasDuplicateName = hasExistingProjectDeviceName(
|
||||
values.name,
|
||||
values.displayName,
|
||||
existingDevices
|
||||
);
|
||||
|
||||
function update(key: keyof typeof values, value: string) {
|
||||
setValues((current) => ({ ...current, [key]: value }));
|
||||
|
|
@ -79,6 +96,17 @@ export function ProjectDeviceModal({
|
|||
submitLabel={initialDevice ? "Änderungen speichern" : "Gerät anlegen"}
|
||||
title={initialDevice ? "Projektgerät bearbeiten" : "Projektgerät hinzufügen"}
|
||||
>
|
||||
{context ? (
|
||||
<div className="alert alert-info small" role="note">
|
||||
{context}
|
||||
</div>
|
||||
) : null}
|
||||
{hasDuplicateName ? (
|
||||
<div className="alert alert-warning small" role="status">
|
||||
Ein Projektgerät mit gleichem internen Namen oder Anzeigenamen ist bereits vorhanden.
|
||||
Das Anlegen bleibt möglich.
|
||||
</div>
|
||||
) : null}
|
||||
{!initialDevice && globalDevices.length > 0 ? (
|
||||
<div className="border rounded p-3 mb-4">
|
||||
<label className="form-label" htmlFor="global-project-device">
|
||||
|
|
@ -287,7 +315,7 @@ function NumberField({
|
|||
|
||||
function toFormValues(
|
||||
device?: ProjectDeviceDto,
|
||||
initialValues?: Partial<CreateProjectDeviceInput>
|
||||
initialValues?: ProjectDeviceModalInitialValues
|
||||
) {
|
||||
return {
|
||||
name: device?.name ?? initialValues?.name ?? "",
|
||||
|
|
@ -295,14 +323,24 @@ function toFormValues(
|
|||
connectionKind: device?.connectionKind ?? initialValues?.connectionKind ?? "",
|
||||
costGroup: device?.costGroup ?? initialValues?.costGroup ?? "",
|
||||
category: device?.category ?? initialValues?.category ?? "single_phase",
|
||||
quantity: String(device?.quantity ?? initialValues?.quantity ?? 1),
|
||||
powerPerUnit: String(device?.powerPerUnit ?? initialValues?.powerPerUnit ?? 0.1),
|
||||
quantity: toInitialNumber(device?.quantity, initialValues?.quantity, 1),
|
||||
powerPerUnit: toInitialNumber(device?.powerPerUnit, initialValues?.powerPerUnit, 0.1),
|
||||
simultaneityFactor: String(device?.simultaneityFactor ?? initialValues?.simultaneityFactor ?? 1),
|
||||
cosPhi: String(device?.cosPhi ?? initialValues?.cosPhi ?? 1),
|
||||
remark: device?.remark ?? initialValues?.remark ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function toInitialNumber(
|
||||
deviceValue: number | undefined,
|
||||
initialValue: number | null | undefined,
|
||||
fallback: number
|
||||
) {
|
||||
if (deviceValue !== undefined) return String(deviceValue);
|
||||
if (initialValue === null) return "";
|
||||
return String(initialValue ?? fallback);
|
||||
}
|
||||
|
||||
function optionalString(value: string) {
|
||||
return value.trim() || undefined;
|
||||
}
|
||||
|
|
@ -315,7 +353,29 @@ function parseDecimal(value: string) {
|
|||
return Number(value.trim().replace(",", "."));
|
||||
}
|
||||
|
||||
function isNumberInRange(value: string, min: number, max = Number.POSITIVE_INFINITY) {
|
||||
export function isProjectDeviceNumberInRange(
|
||||
value: string,
|
||||
min: number,
|
||||
max = Number.POSITIVE_INFINITY
|
||||
) {
|
||||
if (!value.trim()) return false;
|
||||
const parsed = parseDecimal(value);
|
||||
return Number.isFinite(parsed) && parsed >= min && parsed <= max;
|
||||
}
|
||||
|
||||
const isNumberInRange = isProjectDeviceNumberInRange;
|
||||
|
||||
export function hasExistingProjectDeviceName(
|
||||
name: string,
|
||||
displayName: string,
|
||||
existingDevices: ReadonlyArray<Pick<ProjectDeviceDto, "name" | "displayName">>
|
||||
) {
|
||||
const candidates = new Set(
|
||||
[name, displayName].map((value) => value.trim().toLocaleLowerCase("de-DE")).filter(Boolean)
|
||||
);
|
||||
return existingDevices.some((device) =>
|
||||
[device.name, device.displayName].some((value) =>
|
||||
candidates.has(value.trim().toLocaleLowerCase("de-DE"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue