Web: Skeleton-Loading (schimmernde Ladeplatzhalter)
Statt nacktem 'Lädt…'/'Wird geladen…' jetzt geformte, schimmernde Platzhalter an den zentralen Stellen: - Neuer Baustein components/Skeleton.jsx (Skeleton/SkeletonLines/CardSkeleton), Sweep-Animation, Farben aus CSS-Tokens (Dark-Mode automatisch), respektiert prefers-reduced-motion. - DataTable: bei Erstladung Platzhalterzeilen in Spaltenform (deckt alle 8 Tabellenseiten). Nachladen mit vorhandenen Zeilen laesst Daten stehen. - Dashboard-Karten: Zustand-Wrapper + Inline-Diagramm-Lader zeigen KPI-/Listen-/Diagramm-Skelette passend zur Kartenform. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { api } from "../api";
|
||||
import Icon from "../components/Icon";
|
||||
import { CardSkeleton } from "../components/Skeleton";
|
||||
import { useSettings } from "../settings";
|
||||
import {
|
||||
amountText, articlePrimary, articleSecondary, fmt, relativeExpiry,
|
||||
@@ -35,9 +36,9 @@ function useDaten(laden, abhaengig = []) {
|
||||
return { daten, fehler };
|
||||
}
|
||||
|
||||
function Zustand({ fehler, daten, children, leer = "Keine Daten." }) {
|
||||
function Zustand({ fehler, daten, children, leer = "Keine Daten.", skelett = "liste" }) {
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (daten == null) return <div className="empty">Lädt…</div>;
|
||||
if (daten == null) return <CardSkeleton variant={skelett} />;
|
||||
if (Array.isArray(daten) && daten.length === 0) return <div className="empty">{leer}</div>;
|
||||
return children;
|
||||
}
|
||||
@@ -183,7 +184,7 @@ function KarteArtikel({ props: karteProps }) {
|
||||
return <div className="empty">Kein Artikel gewählt – über „Anordnen“ einstellen.</div>;
|
||||
}
|
||||
return (
|
||||
<Zustand fehler={fehler} daten={daten}>
|
||||
<Zustand fehler={fehler} daten={daten} skelett="kpi">
|
||||
{daten && (
|
||||
<div className="card-stack">
|
||||
<div className="kpi">
|
||||
@@ -219,7 +220,7 @@ function Kennzahl({ wert, label, icon, ton }) {
|
||||
function KarteStatus() {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardStats());
|
||||
return (
|
||||
<Zustand fehler={fehler} daten={daten}>
|
||||
<Zustand fehler={fehler} daten={daten} skelett="kpi">
|
||||
<div className="kpi-row">
|
||||
<Kennzahl icon="clock" label="Bald ablaufend" wert={daten?.expiring_soon ?? 0}
|
||||
ton={daten?.expiring_soon ? "warn" : ""} />
|
||||
@@ -234,7 +235,7 @@ function KarteStatus() {
|
||||
function KarteArtikelzahl() {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardStats());
|
||||
return (
|
||||
<Zustand fehler={fehler} daten={daten}>
|
||||
<Zustand fehler={fehler} daten={daten} skelett="kpi">
|
||||
<Kennzahl icon="package" label="Artikel mit Bestand" wert={daten?.products_in_stock ?? 0} />
|
||||
</Zustand>
|
||||
);
|
||||
@@ -243,7 +244,7 @@ function KarteArtikelzahl() {
|
||||
function KarteEinheiten() {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardStats());
|
||||
return (
|
||||
<Zustand fehler={fehler} daten={daten}>
|
||||
<Zustand fehler={fehler} daten={daten} skelett="kpi">
|
||||
<Kennzahl icon="box" label="Artikeleinheiten im Lager" wert={fmt(daten?.article_units ?? 0)} />
|
||||
</Zustand>
|
||||
);
|
||||
@@ -321,7 +322,7 @@ function KarteEinkaufsliste({ props: karteProps }) {
|
||||
});
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="liste" />;
|
||||
|
||||
const orte = ortId ? daten.orte.filter((o) => o.location_id === ortId) : daten.orte;
|
||||
const gesamtLeer = !daten.produkte.length && !daten.gruppen.length;
|
||||
@@ -433,7 +434,7 @@ function KarteAblaufRing({ props: karteProps }) {
|
||||
// Ob „Ohne MHD" (Gegenstände/undatierte Chargen) mitgezählt wird – Karten-Einstellung.
|
||||
const mitOhneMhd = karteProps?.mit_ohne_mhd ?? true;
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="chart" />;
|
||||
|
||||
// Zustandsfarben, nie Serienfarben – und immer mit Beschriftung.
|
||||
const segmente = [
|
||||
@@ -452,7 +453,7 @@ function KarteKategorienRing({ props: karteProps }) {
|
||||
const tiefe = karteProps?.tiefe ? Number(karteProps.tiefe) : undefined;
|
||||
const { daten, fehler } = useDaten(() => api.dashboardByCategory(tiefe), [tiefe]);
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="chart" />;
|
||||
|
||||
// Detailansicht: jede Kategorie einzeln statt der Bündelung zu „Andere“.
|
||||
const segmente = mitFarben(
|
||||
@@ -466,7 +467,7 @@ function KarteKategorienRing({ props: karteProps }) {
|
||||
function KarteKategorienSaeulen() {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardByCategory());
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="chart" />;
|
||||
|
||||
const zeilen = daten.slice(0, 8).map((k) => ({
|
||||
name: k.name,
|
||||
@@ -513,7 +514,7 @@ function KarteBestandsverlauf({ tage = 90 }) {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardTimeline(tage), [tage]);
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="chart" />;
|
||||
|
||||
return (
|
||||
<Line
|
||||
@@ -572,7 +573,7 @@ function KarteBewegungsverlauf({ tage = 30 }) {
|
||||
const { daten, fehler } = useDaten(() => api.dashboardFlow(tage), [tage]);
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <div className="empty">Lädt…</div>;
|
||||
if (!daten) return <CardSkeleton variant="chart" />;
|
||||
|
||||
return (
|
||||
<Wasserfall
|
||||
|
||||
Reference in New Issue
Block a user