From 509413f9948acd43248e3f6f29a23c29080d265c Mon Sep 17 00:00:00 2001 From: s8613 Date: Thu, 5 Jun 2025 21:45:13 +0200 Subject: [PATCH] Added Config add page. --- project/frontend/src/components/KPIForm.tsx | 252 +++++++++++++ project/frontend/src/routeTree.gen.ts | 32 +- project/frontend/src/routes/config-add.tsx | 87 +++++ .../src/routes/config-detail.$kpiId.tsx | 330 +++++++----------- project/frontend/src/routes/config.tsx | 6 +- 5 files changed, 502 insertions(+), 205 deletions(-) create mode 100644 project/frontend/src/components/KPIForm.tsx create mode 100644 project/frontend/src/routes/config-add.tsx diff --git a/project/frontend/src/components/KPIForm.tsx b/project/frontend/src/components/KPIForm.tsx new file mode 100644 index 0000000..e493495 --- /dev/null +++ b/project/frontend/src/components/KPIForm.tsx @@ -0,0 +1,252 @@ +import { Box, Typography, Button, Paper, TextField, FormControlLabel, + Checkbox, Select, MenuItem, FormControl, InputLabel, Divider, CircularProgress } from "@mui/material"; +import { useState, useEffect } from "react"; +import type { Kennzahl } from "../types/kpi"; +import { typeDisplayMapping } from "../types/kpi"; + +interface KPIFormProps { + mode: 'add' | 'edit'; + initialData?: Kennzahl | null; + onSave: (data: Partial) => Promise; + onCancel: () => void; + loading?: boolean; +} + +const emptyKPI: Partial = { + name: '', + description: '', + mandatory: false, + type: 'string', + translation: '', + example: '', + active: true +}; + +export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }: KPIFormProps) { + const [formData, setFormData] = useState>(emptyKPI); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + if (mode === 'edit' && initialData) { + setFormData(initialData); + } else { + setFormData(emptyKPI); + } + }, [mode, initialData]); + + const handleSave = async () => { + if (!formData.name?.trim()) { + alert('Name ist erforderlich'); + return; + } + + setIsSaving(true); + try { + await onSave(formData); + } catch (error) { + console.error('Error saving KPI:', error); + } finally { + setIsSaving(false); + } + }; + + const handleCancel = () => { + if (mode === 'edit' && initialData) { + setFormData(initialData); + } else { + setFormData(emptyKPI); + } + onCancel(); + }; + + const updateField = (field: keyof Kennzahl, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + if (loading) { + return ( + + + + {mode === 'edit' ? 'Lade KPI Details...' : 'Laden...'} + + + ); + } + + return ( + + + + Kennzahl + + updateField('name', e.target.value)} + sx={{ mb: 2 }} + required + error={!formData.name?.trim()} + helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''} + /> + + + + + + + Beschreibung + + updateField('description', e.target.value)} + helperText="Beschreibung der Kennzahl" + /> + + + updateField('mandatory', e.target.checked)} + sx={{ color: '#383838' }} + /> + } + label="Erforderlich" + /> + + Die Kennzahl erlaubt keine leeren Werte + + + + + + + + + Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type} + + + Typ + + + + + + + + + Synonyme & Übersetzungen + + updateField('translation', e.target.value)} + helperText="z.B. Englische Übersetzung der Kennzahl" + /> + + + + + + + Beispiele von Kennzahl + + updateField('example', e.target.value)} + helperText="Beispielwerte für diese Kennzahl" + /> + + + {mode === 'add' && ( + <> + + + updateField('active', e.target.checked)} + sx={{ color: '#383838' }} + /> + } + label="Aktiv" + /> + + Die Kennzahl ist aktiv und wird angezeigt + + + + )} + + + + + + + ); +} \ No newline at end of file diff --git a/project/frontend/src/routeTree.gen.ts b/project/frontend/src/routeTree.gen.ts index 021e778..b426ec2 100644 --- a/project/frontend/src/routeTree.gen.ts +++ b/project/frontend/src/routeTree.gen.ts @@ -11,6 +11,7 @@ // Import Routes import { Route as rootRoute } from './routes/__root' +import { Route as ConfigAddImport } from './routes/config-add' import { Route as ConfigImport } from './routes/config' import { Route as IndexImport } from './routes/index' import { Route as ExtractedResultPitchBookImport } from './routes/extractedResult.$pitchBook' @@ -18,6 +19,12 @@ import { Route as ConfigDetailKpiIdImport } from './routes/config-detail.$kpiId' // Create/Update Routes +const ConfigAddRoute = ConfigAddImport.update({ + id: '/config-add', + path: '/config-add', + getParentRoute: () => rootRoute, +} as any) + const ConfigRoute = ConfigImport.update({ id: '/config', path: '/config', @@ -60,6 +67,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ConfigImport parentRoute: typeof rootRoute } + '/config-add': { + id: '/config-add' + path: '/config-add' + fullPath: '/config-add' + preLoaderRoute: typeof ConfigAddImport + parentRoute: typeof rootRoute + } '/config-detail/$kpiId': { id: '/config-detail/$kpiId' path: '/config-detail/$kpiId' @@ -82,6 +96,7 @@ declare module '@tanstack/react-router' { export interface FileRoutesByFullPath { '/': typeof IndexRoute '/config': typeof ConfigRoute + '/config-add': typeof ConfigAddRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute } @@ -89,6 +104,7 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/': typeof IndexRoute '/config': typeof ConfigRoute + '/config-add': typeof ConfigAddRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute } @@ -97,6 +113,7 @@ export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute '/config': typeof ConfigRoute + '/config-add': typeof ConfigAddRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute } @@ -106,14 +123,21 @@ export interface FileRouteTypes { fullPaths: | '/' | '/config' + | '/config-add' | '/config-detail/$kpiId' | '/extractedResult/$pitchBook' fileRoutesByTo: FileRoutesByTo - to: '/' | '/config' | '/config-detail/$kpiId' | '/extractedResult/$pitchBook' + to: + | '/' + | '/config' + | '/config-add' + | '/config-detail/$kpiId' + | '/extractedResult/$pitchBook' id: | '__root__' | '/' | '/config' + | '/config-add' | '/config-detail/$kpiId' | '/extractedResult/$pitchBook' fileRoutesById: FileRoutesById @@ -122,6 +146,7 @@ export interface FileRouteTypes { export interface RootRouteChildren { IndexRoute: typeof IndexRoute ConfigRoute: typeof ConfigRoute + ConfigAddRoute: typeof ConfigAddRoute ConfigDetailKpiIdRoute: typeof ConfigDetailKpiIdRoute ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute } @@ -129,6 +154,7 @@ export interface RootRouteChildren { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, ConfigRoute: ConfigRoute, + ConfigAddRoute: ConfigAddRoute, ConfigDetailKpiIdRoute: ConfigDetailKpiIdRoute, ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute, } @@ -145,6 +171,7 @@ export const routeTree = rootRoute "children": [ "/", "/config", + "/config-add", "/config-detail/$kpiId", "/extractedResult/$pitchBook" ] @@ -155,6 +182,9 @@ export const routeTree = rootRoute "/config": { "filePath": "config.tsx" }, + "/config-add": { + "filePath": "config-add.tsx" + }, "/config-detail/$kpiId": { "filePath": "config-detail.$kpiId.tsx" }, diff --git a/project/frontend/src/routes/config-add.tsx b/project/frontend/src/routes/config-add.tsx new file mode 100644 index 0000000..b90d7b1 --- /dev/null +++ b/project/frontend/src/routes/config-add.tsx @@ -0,0 +1,87 @@ +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { Box, Typography, IconButton } from "@mui/material"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import { KPIForm } from "../components/KPIForm"; +import type { Kennzahl } from "../types/kpi"; + +export const Route = createFileRoute("/config-add")({ + component: ConfigAddPage, +}); + +function ConfigAddPage() { + const navigate = useNavigate(); + + const handleSave = async (formData: Partial) => { + try { + const existingKPIsResponse = await fetch('http://localhost:5050/api/kpi_setting/'); + const existingKPIs = await existingKPIsResponse.json(); + const maxPosition = existingKPIs.length > 0 + ? Math.max(...existingKPIs.map((kpi: Kennzahl) => kpi.position)) + : 0; + + const kpiData = { + ...formData, + position: maxPosition + 1, + active: formData.active !== false + }; + + const response = await fetch('http://localhost:5050/api/kpi_setting/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(kpiData), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + navigate({ to: "/config" }); + } catch (error) { + console.error('Error creating KPI:', error); + throw error; + } + }; + + const handleCancel = () => { + navigate({ to: "/config" }); + }; + + return ( + + + + navigate({ to: "/config" })}> + + + + Neue Kennzahl hinzufügen + + + + + + + ); +} \ No newline at end of file diff --git a/project/frontend/src/routes/config-detail.$kpiId.tsx b/project/frontend/src/routes/config-detail.$kpiId.tsx index e4cabc8..16ddce9 100644 --- a/project/frontend/src/routes/config-detail.$kpiId.tsx +++ b/project/frontend/src/routes/config-detail.$kpiId.tsx @@ -1,23 +1,10 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { - Box, - Typography, - IconButton, - Button, - Paper, - TextField, - FormControlLabel, - Checkbox, - Select, - MenuItem, - FormControl, - InputLabel, - Divider, - CircularProgress +import { Box, Typography, IconButton, Button, CircularProgress, Paper, Divider } from "@mui/material"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useEffect, useState } from "react"; import type { Kennzahl } from "../types/kpi"; +import { KPIForm } from "../components/KPIForm"; import { typeDisplayMapping } from "../types/kpi"; export const Route = createFileRoute("/config-detail/$kpiId")({ @@ -29,7 +16,6 @@ function KPIDetailPage() { const navigate = useNavigate(); const [kennzahl, setKennzahl] = useState(null); const [isEditing, setIsEditing] = useState(false); - const [editedKennzahl, setEditedKennzahl] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -47,7 +33,6 @@ function KPIDetailPage() { } const data = await response.json(); setKennzahl(data); - setEditedKennzahl(data); setError(null); } catch (err) { console.error('Error fetching KPI:', err); @@ -60,16 +45,14 @@ function KPIDetailPage() { fetchKennzahl(); }, [kpiId]); - const handleSave = async () => { - if (!editedKennzahl) return; - + const handleSave = async (formData: Partial) => { try { const response = await fetch(`http://localhost:5050/api/kpi_setting/${kpiId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(editedKennzahl), + body: JSON.stringify(formData), }); if (!response.ok) { @@ -78,15 +61,14 @@ function KPIDetailPage() { const updatedKennzahl = await response.json(); setKennzahl(updatedKennzahl); - setEditedKennzahl(updatedKennzahl); setIsEditing(false); - } catch (err) { - console.error('Error saving KPI:', err); + } catch (error) { + console.error('Error saving KPI:', error); + throw error; } }; const handleCancel = () => { - setEditedKennzahl(kennzahl); setIsEditing(false); }; @@ -118,7 +100,7 @@ function KPIDetailPage() { alignItems="center" flexDirection="column" > - + {error || 'KPI nicht gefunden'} + + + + + Kennzahl + + + {kennzahl.name} + + + + + + + + Beschreibung + + + {kennzahl.description || "Zurzeit ist die Beschreibung der Kennzahl leer. Klicken Sie auf den Bearbeiten-Button, um die Beschreibung zu ergänzen."} + + + + + Erforderlich: {kennzahl.mandatory ? 'Ja' : 'Nein'} + + + + + + + + + Format + + + {typeDisplayMapping[kennzahl.type] || kennzahl.type} + + + + + + + + Synonyme & Übersetzungen + + + {kennzahl.translation || "Zurzeit gibt es keine Einträge für Synonyme und Übersetzungen der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."} + + + + + + + + Beispiele von Kennzahl + + + {kennzahl.example || "Zurzeit gibt es keine Beispiele der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."} + + + + + ); + } return ( - Detailansicht + Kennzahl bearbeiten - - - - - Kennzahl - - {isEditing ? ( - setEditedKennzahl(prev => prev ? {...prev, name: e.target.value} : null)} - sx={{ mb: 2 }} - /> - ) : ( - - {currentKennzahl.name} - - )} - - - - - - Beschreibung - - - {isEditing ? ( - setEditedKennzahl(prev => prev ? {...prev, description: e.target.value} : null)} - helperText="Beschreibung der Kennzahl" - /> - ) : ( - - {currentKennzahl.description || "Zurzeit ist die Beschreibung der Kennzahl leer. Klicken Sie auf den Bearbeiten-Button, um die Beschreibung zu ergänzen."} - - )} - - - isEditing && setEditedKennzahl(prev => prev ? {...prev, mandatory: e.target.checked} : null)} - disabled={!isEditing} - sx={{ color: '#383838' }} - /> - } - label="Erforderlich" - /> - - Die Kennzahl erlaubt keine leeren Werte - - - - - - - - - Format: {typeDisplayMapping[currentKennzahl.type] || currentKennzahl.type} - - - {isEditing ? ( - - Typ - - - ) : null} - - - - - - Synonyme & Übersetzungen - - - {isEditing ? ( - setEditedKennzahl(prev => prev ? {...prev, translation: e.target.value} : null)} - helperText="z.B. Englische Übersetzung der Kennzahl" - /> - ) : ( - - {currentKennzahl.translation || "Zurzeit gibt es keine Einträge für Synonyme und Übersetzungen der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."} - - )} - - - - - - Beispiele von Kennzahl - - - {isEditing ? ( - setEditedKennzahl(prev => prev ? {...prev, example: e.target.value} : null)} - helperText="Beispielwerte für diese Kennzahl" - /> - ) : ( - - {currentKennzahl.example || "Zurzeit gibt es keine Beispiele der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."} - - )} - - {isEditing && ( - - - - - )} - + ); } \ No newline at end of file diff --git a/project/frontend/src/routes/config.tsx b/project/frontend/src/routes/config.tsx index a30f3ab..eddf16e 100644 --- a/project/frontend/src/routes/config.tsx +++ b/project/frontend/src/routes/config.tsx @@ -8,10 +8,13 @@ export const Route = createFileRoute("/config")({ component: ConfigPage, }); - function ConfigPage() { const navigate = useNavigate(); + const handleAddNewKPI = () => { + navigate({ to: "/config-add" }); + }; + return (