Added Config add page.

pull/55/head
s8613 2025-06-05 21:45:13 +02:00
parent 521e6918bc
commit 509413f994
5 changed files with 502 additions and 205 deletions

View File

@ -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<Kennzahl>) => Promise<void>;
onCancel: () => void;
loading?: boolean;
}
const emptyKPI: Partial<Kennzahl> = {
name: '',
description: '',
mandatory: false,
type: 'string',
translation: '',
example: '',
active: true
};
export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }: KPIFormProps) {
const [formData, setFormData] = useState<Partial<Kennzahl>>(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 (
<Box
display="flex"
justifyContent="center"
alignItems="center"
minHeight="400px"
flexDirection="column"
>
<CircularProgress sx={{ color: '#383838', mb: 2 }} />
<Typography>
{mode === 'edit' ? 'Lade KPI Details...' : 'Laden...'}
</Typography>
</Box>
);
}
return (
<Paper
elevation={2}
sx={{
width: "90%",
maxWidth: 800,
p: 4,
borderRadius: 2,
backgroundColor: "white"
}}
>
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Kennzahl
</Typography>
<TextField
fullWidth
label="Name *"
value={formData.name || ''}
onChange={(e) => updateField('name', e.target.value)}
sx={{ mb: 2 }}
required
error={!formData.name?.trim()}
helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''}
/>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beschreibung
</Typography>
<TextField
fullWidth
multiline
rows={3}
label="Beschreibung"
value={formData.description || ''}
onChange={(e) => updateField('description', e.target.value)}
helperText="Beschreibung der Kennzahl"
/>
<Box mt={3}>
<FormControlLabel
control={
<Checkbox
checked={formData.mandatory || false}
onChange={(e) => updateField('mandatory', e.target.checked)}
sx={{ color: '#383838' }}
/>
}
label="Erforderlich"
/>
<Typography variant="body2" color="text.secondary" ml={4}>
Die Kennzahl erlaubt keine leeren Werte
</Typography>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type}
</Typography>
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Typ</InputLabel>
<Select
value={formData.type || 'string'}
label="Typ"
onChange={(e) => updateField('type', e.target.value)}
>
<MenuItem value="string">Text</MenuItem>
<MenuItem value="number">Zahl</MenuItem>
<MenuItem value="date">Datum</MenuItem>
<MenuItem value="boolean">Ja/Nein</MenuItem>
<MenuItem value="array">Liste (mehrfach)</MenuItem>
</Select>
</FormControl>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Synonyme & Übersetzungen
</Typography>
<TextField
fullWidth
label="Übersetzung"
value={formData.translation || ''}
onChange={(e) => updateField('translation', e.target.value)}
helperText="z.B. Englische Übersetzung der Kennzahl"
/>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beispiele von Kennzahl
</Typography>
<TextField
fullWidth
multiline
rows={2}
label="Beispiel"
value={formData.example || ''}
onChange={(e) => updateField('example', e.target.value)}
helperText="Beispielwerte für diese Kennzahl"
/>
</Box>
{mode === 'add' && (
<>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<FormControlLabel
control={
<Checkbox
checked={formData.active !== false}
onChange={(e) => updateField('active', e.target.checked)}
sx={{ color: '#383838' }}
/>
}
label="Aktiv"
/>
<Typography variant="body2" color="text.secondary" ml={4}>
Die Kennzahl ist aktiv und wird angezeigt
</Typography>
</Box>
</>
)}
<Box display="flex" justifyContent="flex-end" gap={2} mt={4}>
<Button
variant="outlined"
onClick={handleCancel}
disabled={isSaving}
sx={{
borderColor: "#383838",
color: "#383838",
"&:hover": { borderColor: "#2e2e2e", backgroundColor: "#f5f5f5" }
}}
>
Abbrechen
</Button>
<Button
variant="contained"
onClick={handleSave}
disabled={isSaving || !formData.name?.trim()}
sx={{
backgroundColor: "#383838",
"&:hover": { backgroundColor: "#2e2e2e" },
}}
>
{isSaving ? (
<>
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
{mode === 'add' ? 'Hinzufügen...' : 'Speichern...'}
</>
) : (
mode === 'add' ? 'Hinzufügen' : 'Speichern'
)}
</Button>
</Box>
</Paper>
);
}

View File

@ -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"
},

View File

@ -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<Kennzahl>) => {
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 (
<Box
minHeight="100vh"
width="100vw"
bgcolor="#f5f5f5"
display="flex"
flexDirection="column"
alignItems="center"
pt={3}
pb={4}
>
<Box
width="100%"
display="flex"
justifyContent="flex-start"
alignItems="center"
px={4}
mb={4}
>
<Box display="flex" alignItems="center">
<IconButton onClick={() => navigate({ to: "/config" })}>
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
</IconButton>
<Typography variant="h5" fontWeight="bold" ml={3}>
Neue Kennzahl hinzufügen
</Typography>
</Box>
</Box>
<KPIForm
mode="add"
onSave={handleSave}
onCancel={handleCancel}
/>
</Box>
);
}

View File

@ -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<Kennzahl | null>(null);
const [isEditing, setIsEditing] = useState(false);
const [editedKennzahl, setEditedKennzahl] = useState<Kennzahl | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<Kennzahl>) => {
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"
>
<Typography variant="h6" color="error" mb={2}>
<Typography variant="h6" mb={2}>
{error || 'KPI nicht gefunden'}
</Typography>
<Button
@ -135,7 +117,117 @@ function KPIDetailPage() {
);
}
const currentKennzahl = isEditing ? editedKennzahl! : kennzahl;
if (!isEditing) {
return (
<Box
minHeight="100vh"
width="100vw"
bgcolor="#f5f5f5"
display="flex"
flexDirection="column"
alignItems="center"
pt={3}
pb={4}
>
<Box
width="100%"
display="flex"
justifyContent="space-between"
alignItems="center"
px={4}
mb={4}
>
<Box display="flex" alignItems="center">
<IconButton onClick={() => navigate({ to: "/config" })}>
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
</IconButton>
<Typography variant="h5" fontWeight="bold" ml={3}>
Detailansicht
</Typography>
</Box>
<Button
variant="contained"
sx={{
backgroundColor: "#383838",
"&:hover": { backgroundColor: "#2e2e2e" },
}}
onClick={() => setIsEditing(true)}
>
Bearbeiten
</Button>
</Box>
<Paper
elevation={2}
sx={{
width: "90%",
maxWidth: 800,
p: 4,
borderRadius: 2,
backgroundColor: "white"
}}
>
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Kennzahl
</Typography>
<Typography variant="body1" sx={{ mb: 2, fontSize: 16 }}>
{kennzahl.name}
</Typography>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beschreibung
</Typography>
<Typography variant="body1" color="text.secondary">
{kennzahl.description || "Zurzeit ist die Beschreibung der Kennzahl leer. Klicken Sie auf den Bearbeiten-Button, um die Beschreibung zu ergänzen."}
</Typography>
<Box mt={2}>
<Typography variant="body2" color="text.secondary">
<strong>Erforderlich:</strong> {kennzahl.mandatory ? 'Ja' : 'Nein'}
</Typography>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Format
</Typography>
<Typography variant="body1" color="text.secondary">
{typeDisplayMapping[kennzahl.type] || kennzahl.type}
</Typography>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Synonyme & Übersetzungen
</Typography>
<Typography variant="body1" color="text.secondary">
{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."}
</Typography>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beispiele von Kennzahl
</Typography>
<Typography variant="body1" color="text.secondary">
{kennzahl.example || "Zurzeit gibt es keine Beispiele der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."}
</Typography>
</Box>
</Paper>
</Box>
);
}
return (
<Box
@ -151,7 +243,7 @@ function KPIDetailPage() {
<Box
width="100%"
display="flex"
justifyContent="space-between"
justifyContent="flex-start"
alignItems="center"
px={4}
mb={4}
@ -161,185 +253,17 @@ function KPIDetailPage() {
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
</IconButton>
<Typography variant="h5" fontWeight="bold" ml={3}>
Detailansicht
Kennzahl bearbeiten
</Typography>
</Box>
<Button
variant="contained"
sx={{
backgroundColor: "#383838",
"&:hover": { backgroundColor: "#2e2e2e" },
}}
onClick={isEditing ? undefined : () => setIsEditing(true)}
disabled={isEditing}
>
Bearbeiten
</Button>
</Box>
<Paper
elevation={2}
sx={{
width: "90%",
maxWidth: 800,
p: 4,
borderRadius: 2,
backgroundColor: "white"
}}
>
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Kennzahl
</Typography>
{isEditing ? (
<TextField
fullWidth
label="Name"
value={currentKennzahl.name}
onChange={(e) => setEditedKennzahl(prev => prev ? {...prev, name: e.target.value} : null)}
sx={{ mb: 2 }}
/>
) : (
<Typography variant="body1" sx={{ mb: 2, fontSize: 16 }}>
{currentKennzahl.name}
</Typography>
)}
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beschreibung
</Typography>
{isEditing ? (
<TextField
fullWidth
multiline
rows={3}
label="Beschreibung"
value={currentKennzahl.description}
onChange={(e) => setEditedKennzahl(prev => prev ? {...prev, description: e.target.value} : null)}
helperText="Beschreibung der Kennzahl"
/>
) : (
<Typography variant="body1" color="text.secondary">
{currentKennzahl.description || "Zurzeit ist die Beschreibung der Kennzahl leer. Klicken Sie auf den Bearbeiten-Button, um die Beschreibung zu ergänzen."}
</Typography>
)}
<Box mt={3}>
<FormControlLabel
control={
<Checkbox
checked={currentKennzahl.mandatory}
onChange={(e) => isEditing && setEditedKennzahl(prev => prev ? {...prev, mandatory: e.target.checked} : null)}
disabled={!isEditing}
sx={{ color: '#383838' }}
/>
}
label="Erforderlich"
/>
<Typography variant="body2" color="text.secondary" ml={4}>
Die Kennzahl erlaubt keine leeren Werte
</Typography>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Format: {typeDisplayMapping[currentKennzahl.type] || currentKennzahl.type}
</Typography>
{isEditing ? (
<FormControl fullWidth sx={{ mb: 2 }}>
<InputLabel>Typ</InputLabel>
<Select
value={currentKennzahl.type}
label="Typ"
onChange={(e) => setEditedKennzahl(prev => prev ? {...prev, type: e.target.value} : null)}
>
<MenuItem value="string">Text</MenuItem>
<MenuItem value="number">Zahl</MenuItem>
<MenuItem value="date">Datum</MenuItem>
<MenuItem value="boolean">Ja/Nein</MenuItem>
<MenuItem value="array">Liste (mehrfach)</MenuItem>
</Select>
</FormControl>
) : null}
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Synonyme & Übersetzungen
</Typography>
{isEditing ? (
<TextField
fullWidth
label="Übersetzung"
value={currentKennzahl.translation}
onChange={(e) => setEditedKennzahl(prev => prev ? {...prev, translation: e.target.value} : null)}
helperText="z.B. Englische Übersetzung der Kennzahl"
/>
) : (
<Typography variant="body1" color="text.secondary">
{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."}
</Typography>
)}
</Box>
<Divider sx={{ my: 3 }} />
<Box mb={4}>
<Typography variant="h6" fontWeight="bold" mb={2}>
Beispiele von Kennzahl
</Typography>
{isEditing ? (
<TextField
fullWidth
multiline
rows={2}
label="Beispiel"
value={currentKennzahl.example}
onChange={(e) => setEditedKennzahl(prev => prev ? {...prev, example: e.target.value} : null)}
helperText="Beispielwerte für diese Kennzahl"
/>
) : (
<Typography variant="body1" color="text.secondary">
{currentKennzahl.example || "Zurzeit gibt es keine Beispiele der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."}
</Typography>
)}
</Box>
{isEditing && (
<Box display="flex" justifyContent="flex-end" gap={2} mt={4}>
<Button
variant="outlined"
onClick={handleCancel}
sx={{
borderColor: "#383838",
color: "#383838",
"&:hover": { borderColor: "#2e2e2e", backgroundColor: "#f5f5f5" }
}}
>
Abbrechen
</Button>
<Button
variant="contained"
onClick={handleSave}
sx={{
backgroundColor: "#383838",
"&:hover": { backgroundColor: "#2e2e2e" },
}}
>
Speichern
</Button>
</Box>
)}
</Paper>
<KPIForm
mode="edit"
initialData={kennzahl}
onSave={handleSave}
onCancel={handleCancel}
/>
</Box>
);
}

View File

@ -8,10 +8,13 @@ export const Route = createFileRoute("/config")({
component: ConfigPage,
});
function ConfigPage() {
const navigate = useNavigate();
const handleAddNewKPI = () => {
navigate({ to: "/config-add" });
};
return (
<Box
minHeight="100vh"
@ -40,6 +43,7 @@ function ConfigPage() {
</Box>
<Button
variant="contained"
onClick={handleAddNewKPI}
sx={{
backgroundColor: "#383838",
"&:hover": { backgroundColor: "#2e2e2e" },