Add project transfer controls

This commit is contained in:
Julian Appel 2026-07-28 19:36:26 +02:00
parent da8115fe1d
commit feec814dc5
5 changed files with 353 additions and 8 deletions

View file

@ -16,6 +16,11 @@ export interface ProjectSettingsInput {
interface ProjectSettingsModalProps {
isSaving: boolean;
onClose: () => void;
onExport: () => Promise<void>;
onImport: (
transfer: unknown,
mode: "replace" | "duplicate"
) => Promise<void>;
onSave: (input: ProjectSettingsInput) => Promise<void>;
project: ProjectDto;
}
@ -23,6 +28,8 @@ interface ProjectSettingsModalProps {
export function ProjectSettingsModal({
isSaving,
onClose,
onExport,
onImport,
onSave,
project,
}: ProjectSettingsModalProps) {
@ -45,6 +52,13 @@ export function ProjectSettingsModal({
const [threePhaseVoltageV, setThreePhaseVoltageV] = useState(
String(project.threePhaseVoltageV)
);
const [transfer, setTransfer] = useState<unknown>(null);
const [transferFilename, setTransferFilename] = useState("");
const [importMode, setImportMode] = useState<"replace" | "duplicate">(
"duplicate"
);
const [replaceConfirmed, setReplaceConfirmed] = useState(false);
const [fileError, setFileError] = useState<string | null>(null);
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
@ -64,6 +78,21 @@ export function ProjectSettingsModal({
Number(singlePhaseVoltageV) > 0 &&
Number(threePhaseVoltageV) > 0;
async function handleTransferFile(file: File | undefined) {
setTransfer(null);
setTransferFilename("");
setFileError(null);
if (!file) {
return;
}
try {
setTransfer(JSON.parse(await file.text()) as unknown);
setTransferFilename(file.name);
} catch {
setFileError("Die ausgewählte Datei enthält kein gültiges JSON.");
}
}
return (
<>
<div
@ -193,6 +222,99 @@ export function ProjectSettingsModal({
value={threePhaseVoltageV}
/>
</div>
<div className="col-12">
<hr className="my-2" />
<h3 className="h6">Projekt importieren oder exportieren</h3>
<p className="text-secondary small">
Der Export enthält den vollständigen unterstützten
Projektzustand. Beim Duplizieren bleibt dieses Projekt
unverändert.
</p>
<button
className="btn btn-outline-primary mb-3"
disabled={isSaving}
onClick={() => void onExport()}
type="button"
>
Projektdatei herunterladen
</button>
<label className="form-label d-block" htmlFor="project-import">
Projektdatei auswählen
</label>
<input
accept=".json,application/json"
className="form-control"
id="project-import"
onChange={(event) =>
void handleTransferFile(event.target.files?.[0])
}
type="file"
/>
{transferFilename ? (
<div className="form-text">{transferFilename} ist bereit.</div>
) : null}
{fileError ? (
<div className="text-danger small mt-1">{fileError}</div>
) : null}
<div className="d-flex flex-wrap gap-3 mt-3">
<label className="form-check">
<input
checked={importMode === "duplicate"}
className="form-check-input"
name="import-mode"
onChange={() => setImportMode("duplicate")}
type="radio"
/>
<span className="form-check-label">
Als neues Projekt duplizieren
</span>
</label>
<label className="form-check">
<input
checked={importMode === "replace"}
className="form-check-input"
name="import-mode"
onChange={() => setImportMode("replace")}
type="radio"
/>
<span className="form-check-label">
Dieses Projekt ersetzen
</span>
</label>
</div>
{importMode === "replace" ? (
<label className="form-check mt-2">
<input
checked={replaceConfirmed}
className="form-check-input"
onChange={(event) =>
setReplaceConfirmed(event.target.checked)
}
type="checkbox"
/>
<span className="form-check-label text-danger">
Ich bestätige, dass der aktuelle Projektstand ersetzt
wird.
</span>
</label>
) : null}
<button
className="btn btn-outline-danger mt-3"
disabled={
isSaving ||
transfer === null ||
(importMode === "replace" && !replaceConfirmed)
}
onClick={() =>
transfer === null
? undefined
: void onImport(transfer, importMode)
}
type="button"
>
Projektdatei importieren
</button>
</div>
</div>
</div>
<div className="modal-footer">