feat(dashboard): echtes Drag & Drop statt Pfeil-Buttons, moderneres Kachel-Design
@dnd-kit statt ↑/↓-Buttons zum Anordnen (Griff-Icon, Tastatur-Fallback inklusive). Card-Komponente auf kleineren Radius umgestellt (weniger "Bubble"-Look). Kennzahlen/Mängel-Kachel zeigen jetzt eine große, fette Kopfzahl mit kleinem Label statt reiner Text-Liste - Inspiration: Resgrid Big Board (Statusfarben statt Deko) und moderne SaaS-Dashboards (Linear/Vercel: große Zahlen, dünne Border, wenig Schatten). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -6342,3 +6342,78 @@ Keine Commits in dieser Session.
|
|||||||
- frontend/src/settings/startseite.ts | 30 -
|
- frontend/src/settings/startseite.ts | 30 -
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 2026-09-07 12:25 – 12:30 (5m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** frontend
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
- a468975 feat(dashboard): Tailwind CSS + shadcn/ui eingeführt, Dashboard modernisiert
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 31 ++++
|
||||||
|
- frontend/DEVLOG.md | 62 +++++++
|
||||||
|
- frontend/package-lock.json | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||||
|
- frontend/package.json | 9 +-
|
||||||
|
- frontend/src/components/status/ReadinessBadge.tsx | 32 ++--
|
||||||
|
- frontend/src/components/ui/badge.tsx | 25 +++
|
||||||
|
- frontend/src/components/ui/button.tsx | 42 +++++
|
||||||
|
- frontend/src/components/ui/card.tsx | 29 +++
|
||||||
|
- frontend/src/lib/utils.ts | 6 +
|
||||||
|
- frontend/src/main.tsx | 1 +
|
||||||
|
- frontend/src/pages/dashboard/DashboardPage.tsx | 105 ++++++++---
|
||||||
|
- frontend/src/pages/dashboard/TileFrame.tsx | 69 +++++--
|
||||||
|
- frontend/src/pages/dashboard/layout.ts | 41 ++++-
|
||||||
|
- frontend/src/styles/tailwind.css | 23 +++
|
||||||
|
- frontend/vite.config.ts | 2 +
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-09-07 12:38 – 12:39 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** asb-material
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 31 ++++
|
||||||
|
- frontend/DEVLOG.md | 62 +++++++
|
||||||
|
- frontend/package-lock.json | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||||
|
- frontend/package.json | 9 +-
|
||||||
|
- frontend/src/components/status/ReadinessBadge.tsx | 32 ++--
|
||||||
|
- frontend/src/components/ui/badge.tsx | 25 +++
|
||||||
|
- frontend/src/components/ui/button.tsx | 42 +++++
|
||||||
|
- frontend/src/components/ui/card.tsx | 29 +++
|
||||||
|
- frontend/src/lib/utils.ts | 6 +
|
||||||
|
- frontend/src/main.tsx | 1 +
|
||||||
|
- frontend/src/pages/dashboard/DashboardPage.tsx | 105 ++++++++---
|
||||||
|
- frontend/src/pages/dashboard/TileFrame.tsx | 69 +++++--
|
||||||
|
- frontend/src/pages/dashboard/layout.ts | 41 ++++-
|
||||||
|
- frontend/src/styles/tailwind.css | 23 +++
|
||||||
|
- frontend/vite.config.ts | 2 +
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2026-09-07 12:40 – 12:40 (0m)
|
||||||
|
**Beschreibung:** Claude Code Session
|
||||||
|
**Projekt:** asb-material
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Keine Commits in dieser Session.
|
||||||
|
|
||||||
|
### Geänderte Dateien
|
||||||
|
- DEVLOG.md | 31 ++++
|
||||||
|
- frontend/DEVLOG.md | 62 +++++++
|
||||||
|
- frontend/package-lock.json | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
|
||||||
|
- frontend/package.json | 9 +-
|
||||||
|
- frontend/src/components/status/ReadinessBadge.tsx | 32 ++--
|
||||||
|
- frontend/src/components/ui/badge.tsx | 25 +++
|
||||||
|
- frontend/src/components/ui/button.tsx | 42 +++++
|
||||||
|
- frontend/src/components/ui/card.tsx | 29 +++
|
||||||
|
- frontend/src/lib/utils.ts | 6 +
|
||||||
|
- frontend/src/main.tsx | 1 +
|
||||||
|
- frontend/src/pages/dashboard/DashboardPage.tsx | 105 ++++++++---
|
||||||
|
- frontend/src/pages/dashboard/TileFrame.tsx | 69 +++++--
|
||||||
|
- frontend/src/pages/dashboard/layout.ts | 41 ++++-
|
||||||
|
- frontend/src/styles/tailwind.css | 23 +++
|
||||||
|
- frontend/vite.config.ts | 2 +
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
Generated
+62
@@ -8,6 +8,9 @@
|
|||||||
"name": "mabea-frontend",
|
"name": "mabea-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@radix-ui/react-slot": "^1.3.3",
|
"@radix-ui/react-slot": "^1.3.3",
|
||||||
"@zxing/browser": "^0.1.5",
|
"@zxing/browser": "^0.1.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -1622,6 +1625,59 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dnd-kit/accessibility": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/core": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/accessibility": "^3.1.1",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/sortable": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.0",
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dnd-kit/utilities": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
@@ -6272,6 +6328,12 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-fest": {
|
"node_modules/type-fest": {
|
||||||
"version": "0.16.0",
|
"version": "0.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
"test:e2e:ui": "playwright test --ui"
|
"test:e2e:ui": "playwright test --ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@radix-ui/react-slot": "^1.3.3",
|
"@radix-ui/react-slot": "^1.3.3",
|
||||||
"@zxing/browser": "^0.1.5",
|
"@zxing/browser": "^0.1.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { cn } from "../../lib/utils";
|
|||||||
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("rounded-xl border border-border bg-surface text-foreground shadow-sm", className)}
|
className={cn("rounded-lg border border-border bg-surface text-foreground shadow-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import type { DragEndEvent } from "@dnd-kit/core";
|
||||||
|
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
||||||
|
import { arrayMove, rectSortingStrategy, SortableContext, sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { apiRequest } from "../../api/client";
|
import { apiRequest } from "../../api/client";
|
||||||
@@ -86,13 +89,18 @@ export function DashboardPage() {
|
|||||||
return () => document.removeEventListener("fullscreenchange", fullscreenGeaendert);
|
return () => document.removeEventListener("fullscreenchange", fullscreenGeaendert);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function tileVerschieben(id: DashboardTileId, richtung: -1 | 1) {
|
const sensoren = useSensors(
|
||||||
const index = layout.reihenfolge.indexOf(id);
|
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
||||||
const zielIndex = index + richtung;
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||||
if (zielIndex < 0 || zielIndex >= layout.reihenfolge.length) return;
|
);
|
||||||
const neueReihenfolge = [...layout.reihenfolge];
|
|
||||||
[neueReihenfolge[index], neueReihenfolge[zielIndex]] = [neueReihenfolge[zielIndex], neueReihenfolge[index]];
|
function dragEnde(event: DragEndEvent) {
|
||||||
const neuesLayout = { ...layout, reihenfolge: neueReihenfolge };
|
const { active, over } = event;
|
||||||
|
if (!over || active.id === over.id) return;
|
||||||
|
const von = layout.reihenfolge.indexOf(active.id as DashboardTileId);
|
||||||
|
const nach = layout.reihenfolge.indexOf(over.id as DashboardTileId);
|
||||||
|
if (von === -1 || nach === -1) return;
|
||||||
|
const neuesLayout = { ...layout, reihenfolge: arrayMove(layout.reihenfolge, von, nach) };
|
||||||
setLayout(neuesLayout);
|
setLayout(neuesLayout);
|
||||||
layoutSpeichern(neuesLayout);
|
layoutSpeichern(neuesLayout);
|
||||||
}
|
}
|
||||||
@@ -242,8 +250,10 @@ export function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!laedt && bereitschaft && (
|
{!laedt && bereitschaft && (
|
||||||
|
<DndContext sensors={sensoren} onDragEnd={dragEnde}>
|
||||||
|
<SortableContext items={layout.reihenfolge} strategy={rectSortingStrategy}>
|
||||||
<div className={cn("card-grid mt-4", monitorModus && "text-lg")}>
|
<div className={cn("card-grid mt-4", monitorModus && "text-lg")}>
|
||||||
{layout.reihenfolge.map((id, index) => {
|
{layout.reihenfolge.map((id) => {
|
||||||
const ausgeblendet = layout.ausgeblendet.includes(id);
|
const ausgeblendet = layout.ausgeblendet.includes(id);
|
||||||
if (ausgeblendet && !bearbeitenModus) return null;
|
if (ausgeblendet && !bearbeitenModus) return null;
|
||||||
const inhalt = tileInhalt(id);
|
const inhalt = tileInhalt(id);
|
||||||
@@ -252,24 +262,23 @@ export function DashboardPage() {
|
|||||||
return (
|
return (
|
||||||
<TileFrame
|
<TileFrame
|
||||||
key={id}
|
key={id}
|
||||||
|
id={id}
|
||||||
title={TITEL[id]}
|
title={TITEL[id]}
|
||||||
flexBasis={flexBasisFuerGroesse(groesse)}
|
flexBasis={flexBasisFuerGroesse(groesse)}
|
||||||
groesse={groesse}
|
groesse={groesse}
|
||||||
akzent={tileAkzent(id)}
|
akzent={tileAkzent(id)}
|
||||||
bearbeitenAktiv={bearbeitenModus}
|
bearbeitenAktiv={bearbeitenModus}
|
||||||
ausgeblendet={ausgeblendet}
|
ausgeblendet={ausgeblendet}
|
||||||
kannHochVerschieben={index > 0}
|
|
||||||
kannRunterVerschieben={index < layout.reihenfolge.length - 1}
|
|
||||||
onAusblendenUmschalten={() => ausblendenUmschalten(id)}
|
onAusblendenUmschalten={() => ausblendenUmschalten(id)}
|
||||||
onGroesseAendern={() => groesseAendern(id)}
|
onGroesseAendern={() => groesseAendern(id)}
|
||||||
onHoch={() => tileVerschieben(id, -1)}
|
|
||||||
onRunter={() => tileVerschieben(id, 1)}
|
|
||||||
>
|
>
|
||||||
{inhalt}
|
{inhalt}
|
||||||
</TileFrame>
|
</TileFrame>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
import { GROESSE_LABEL, type DashboardTileGroesse } from "./layout";
|
import { GROESSE_LABEL, type DashboardTileGroesse, type DashboardTileId } from "./layout";
|
||||||
|
|
||||||
export type TileAkzent = "neutral" | "warning" | "danger";
|
export type TileAkzent = "neutral" | "warning" | "danger";
|
||||||
|
|
||||||
@@ -14,65 +16,63 @@ const AKZENT_KLASSE: Record<TileAkzent, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
id: DashboardTileId;
|
||||||
title: string;
|
title: string;
|
||||||
flexBasis: string;
|
flexBasis: string;
|
||||||
groesse: DashboardTileGroesse;
|
groesse: DashboardTileGroesse;
|
||||||
akzent?: TileAkzent;
|
akzent?: TileAkzent;
|
||||||
bearbeitenAktiv: boolean;
|
bearbeitenAktiv: boolean;
|
||||||
ausgeblendet: boolean;
|
ausgeblendet: boolean;
|
||||||
kannHochVerschieben: boolean;
|
|
||||||
kannRunterVerschieben: boolean;
|
|
||||||
onAusblendenUmschalten: () => void;
|
onAusblendenUmschalten: () => void;
|
||||||
onGroesseAendern: () => void;
|
onGroesseAendern: () => void;
|
||||||
onHoch: () => void;
|
|
||||||
onRunter: () => void;
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TileFrame({
|
export function TileFrame({
|
||||||
|
id,
|
||||||
title,
|
title,
|
||||||
flexBasis,
|
flexBasis,
|
||||||
groesse,
|
groesse,
|
||||||
akzent = "neutral",
|
akzent = "neutral",
|
||||||
bearbeitenAktiv,
|
bearbeitenAktiv,
|
||||||
ausgeblendet,
|
ausgeblendet,
|
||||||
kannHochVerschieben,
|
|
||||||
kannRunterVerschieben,
|
|
||||||
onAusblendenUmschalten,
|
onAusblendenUmschalten,
|
||||||
onGroesseAendern,
|
onGroesseAendern,
|
||||||
onHoch,
|
|
||||||
onRunter,
|
|
||||||
children,
|
children,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
|
id,
|
||||||
|
disabled: !bearbeitenAktiv,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{ flexBasis }}
|
ref={setNodeRef}
|
||||||
className={cn("border-l-4 transition-opacity duration-300", AKZENT_KLASSE[akzent], ausgeblendet && "opacity-50")}
|
style={{ flexBasis, transform: CSS.Transform.toString(transform), transition }}
|
||||||
|
className={cn(
|
||||||
|
"border-l-4 transition-opacity duration-300",
|
||||||
|
AKZENT_KLASSE[akzent],
|
||||||
|
ausgeblendet && "opacity-50",
|
||||||
|
isDragging && "z-10 shadow-lg"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{bearbeitenAktiv && (
|
||||||
|
<button
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
className="min-h-11 min-w-11 cursor-grab touch-none text-muted-foreground active:cursor-grabbing"
|
||||||
|
title="Ziehen zum Anordnen"
|
||||||
|
aria-label={`${title} verschieben`}
|
||||||
|
>
|
||||||
|
⠿
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<CardTitle>{title}</CardTitle>
|
<CardTitle>{title}</CardTitle>
|
||||||
|
</div>
|
||||||
{bearbeitenAktiv && (
|
{bearbeitenAktiv && (
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="min-h-11 min-w-11"
|
|
||||||
onClick={onHoch}
|
|
||||||
disabled={!kannHochVerschieben}
|
|
||||||
title="Nach oben"
|
|
||||||
>
|
|
||||||
↑
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="min-h-11 min-w-11"
|
|
||||||
onClick={onRunter}
|
|
||||||
disabled={!kannRunterVerschieben}
|
|
||||||
title="Nach unten"
|
|
||||||
>
|
|
||||||
↓
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="sm" className="min-h-11" onClick={onGroesseAendern} title="Größe ändern">
|
<Button variant="secondary" size="sm" className="min-h-11" onClick={onGroesseAendern} title="Größe ändern">
|
||||||
{GROESSE_LABEL[groesse]}
|
{GROESSE_LABEL[groesse]}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ interface Props {
|
|||||||
|
|
||||||
export function KennzahlenTile({ kennzahlen }: Props) {
|
export function KennzahlenTile({ kennzahlen }: Props) {
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="text-4xl font-bold tabular-nums leading-none">{kennzahlen.anzahl_offener_fehlbestaende}</p>
|
||||||
|
<p className="mb-3 text-xs uppercase tracking-wide text-muted-foreground">Offene Fehlbestände</p>
|
||||||
<ul className="card-list">
|
<ul className="card-list">
|
||||||
<li style={{ padding: "0.3rem 0" }}>Offene Fehlbestände: {kennzahlen.anzahl_offener_fehlbestaende}</li>
|
|
||||||
<li style={{ padding: "0.3rem 0" }}>Genehmigte Mindermengen: {kennzahlen.anzahl_genehmigter_mindermengen}</li>
|
<li style={{ padding: "0.3rem 0" }}>Genehmigte Mindermengen: {kennzahlen.anzahl_genehmigter_mindermengen}</li>
|
||||||
<li style={{ padding: "0.3rem 0" }}>
|
<li style={{ padding: "0.3rem 0" }}>
|
||||||
Kürzlich erledigt (7 Tage): {kennzahlen.anzahl_kuerzlich_erledigter_fehlbestaende}
|
Kürzlich erledigt (7 Tage): {kennzahlen.anzahl_kuerzlich_erledigter_fehlbestaende}
|
||||||
@@ -15,5 +17,6 @@ export function KennzahlenTile({ kennzahlen }: Props) {
|
|||||||
<li style={{ padding: "0.3rem 0" }}>Problematische Objekte: {kennzahlen.anzahl_problematischer_objekte}</li>
|
<li style={{ padding: "0.3rem 0" }}>Problematische Objekte: {kennzahlen.anzahl_problematischer_objekte}</li>
|
||||||
<li style={{ padding: "0.3rem 0" }}>Objekte gesamt: {kennzahlen.anzahl_objekte_gesamt}</li>
|
<li style={{ padding: "0.3rem 0" }}>Objekte gesamt: {kennzahlen.anzahl_objekte_gesamt}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,13 @@ export function MaengelTile({ maengel, maengelNeuAnzahl, maengelKritischAnzahl }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="stat-number">{maengelOffenListe.length} offen</p>
|
<p className="text-4xl font-bold tabular-nums leading-none">{maengelOffenListe.length}</p>
|
||||||
|
<p className="mb-2 text-xs uppercase tracking-wide text-muted-foreground">Offen</p>
|
||||||
{maengelNeuAnzahl > 0 && (
|
{maengelNeuAnzahl > 0 && (
|
||||||
<p className="text-muted" style={{ color: "var(--color-warning)", fontWeight: 600 }}>
|
<p className="font-semibold text-warning">{maengelNeuAnzahl} neu gemeldet</p>
|
||||||
{maengelNeuAnzahl} neu gemeldet
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
{maengelKritischAnzahl > 0 && (
|
{maengelKritischAnzahl > 0 && (
|
||||||
<p className="text-muted" style={{ color: "var(--color-danger)" }}>
|
<p className="text-danger">{maengelKritischAnzahl} kritisch (blockiert Einsatzbereitschaft)</p>
|
||||||
{maengelKritischAnzahl} kritisch (blockiert Einsatzbereitschaft)
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
{maengelOffenListe.length > 0 && (
|
{maengelOffenListe.length > 0 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user