PWA: Passt-Button, Fortschrittsanzeige, Ablaufdatum-Farbcodierung in Kontrolle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -65,6 +65,25 @@ export function KontrollPage() {
|
||||
return (
|
||||
<main className="page">
|
||||
<h1>{objekt?.name ?? "Kontrolle"}</h1>
|
||||
{positionen.length > 0 && (
|
||||
<div className="card" style={{ marginBottom: "1rem" }}>
|
||||
<div className="card-subtitle">
|
||||
{positionen.filter((p) => p.status === "gespeichert").length} von {positionen.length} Positionen bestätigt
|
||||
</div>
|
||||
<div style={{ height: "6px", background: "var(--color-border, #e2e8f0)", borderRadius: "3px", marginTop: "0.35rem" }}>
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
borderRadius: "3px",
|
||||
background: "var(--color-primary, #2563eb)",
|
||||
width: `${Math.round(
|
||||
(positionen.filter((p) => p.status === "gespeichert").length / positionen.length) * 100
|
||||
)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!online && (
|
||||
<div className="alert alert-warning">
|
||||
Kein Netz – Eingaben werden lokal gespeichert und automatisch übertragen, sobald wieder Verbindung besteht.
|
||||
|
||||
@@ -13,8 +13,17 @@ interface Props {
|
||||
* die Menge - kein separater Admin-Schritt. Felder erscheinen nur, wenn der
|
||||
* Materialtyp sie verlangt (ablauf_charge -> Ablauf+Charge, geraet_sn -> SN).
|
||||
*/
|
||||
function ablaufFarbe(ablaufdatum: string): string | undefined {
|
||||
if (!ablaufdatum) return undefined;
|
||||
const tage = (new Date(ablaufdatum).getTime() - Date.now()) / (1000 * 60 * 60 * 24);
|
||||
if (tage < 0) return "var(--color-danger)";
|
||||
if (tage < 30) return "var(--color-warning, #b45309)";
|
||||
return "var(--color-success, #15803d)";
|
||||
}
|
||||
|
||||
export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVersuchen }: Props) {
|
||||
const materialtyp = zustand.material?.materialtyp;
|
||||
const passt = zustand.eingabe === zustand.position.sollmenge_effektiv;
|
||||
|
||||
return (
|
||||
<li className="card">
|
||||
@@ -34,11 +43,11 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers
|
||||
style={{ width: "6rem" }}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
className={passt ? "btn btn-primary" : "btn btn-secondary"}
|
||||
style={{ flex: 1 }}
|
||||
onClick={() => onSenden(zustand.position.material_id, zustand)}
|
||||
>
|
||||
Bestätigen
|
||||
{passt ? "Passt" : "Bestätigen"}
|
||||
</button>
|
||||
</div>
|
||||
{materialtyp === "ablauf_charge" && (
|
||||
@@ -48,6 +57,7 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers
|
||||
className="input"
|
||||
value={zustand.ablaufdatum}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, "ablaufdatum", e.target.value)}
|
||||
style={{ borderColor: ablaufFarbe(zustand.ablaufdatum), color: ablaufFarbe(zustand.ablaufdatum) }}
|
||||
/>
|
||||
<input
|
||||
className="input"
|
||||
|
||||
Reference in New Issue
Block a user