1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 06:39:34 +01:00
effigenix/frontend/apps/scanner/src/pages/ConsumeFlowPage.tsx
Sebastian Frick bf09e3b747 feat(scanner): Mobile Scanner App mit echten Produktionsaufträgen
Scanner-App  Tauri v2 Android App mit
Login, Barcode-Scanner, Consume/Move/Book-Flows und Aufgabenliste.

TasksPage lädt echte RELEASED/IN_PROGRESS Produktionsaufträge via API,
Consume-Flow nutzt den konkreten Auftrag für korrekte Rezept-Skalierung
statt blind den ersten IN_PROGRESS-Auftrag zu nehmen.

Backend: FindStockByBatchId Use Case + REST-Endpoint für Stock-Lookup
per Chargennummer.
2026-03-27 15:25:17 +01:00

119 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { FlaskConical, ScanLine, Check, PackageSearch } from 'lucide-react';
import { Card, Button } from '@effigenix/ui';
import { PageActions } from '../components/PageActions';
import { PageHeader } from '../components/PageHeader';
import { StepIndicator } from '../components/StepIndicator';
import { useNavigation } from '../navigation';
import { useFlowState } from '../flow-state';
export function ConsumeFlowPage() {
const { navigate, setFlowType } = useNavigation();
const { consumeItems, productionOrder } = useFlowState();
const hasItems = consumeItems.length > 0;
if (!productionOrder) {
navigate('tasks');
return null;
}
function startScan() {
setFlowType('consume');
navigate('scan');
}
return (
<div className="flex-1 flex flex-col animate-fade-in">
<PageHeader
title="Verbrauch erfassen"
subtitle={
<span>
Produktionscharge:{' '}
<span className="font-mono font-medium text-brand-600">{productionOrder.batchNumber ?? ''}</span>
</span>
}
/>
<StepIndicator
steps={[
{ label: 'Prod.-Charge', state: 'done' },
{ label: 'Materialien', state: hasItems ? 'done' : 'active' },
{ label: 'Bestätigen', state: hasItems ? 'active' : 'pending' },
]}
/>
{/* Production batch */}
<div className="px-5 mb-2">
<Card className="p-2.5 flex items-center gap-3 border-brand-200 bg-brand-50">
<div className="w-8 h-8 rounded-lg bg-brand-100 flex items-center justify-center">
<FlaskConical className="w-4 h-4 text-brand-600" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-warm-800">
{productionOrder.batchNumber ?? ''} · {productionOrder.articleName}
</div>
<div className="text-xs text-warm-500">
Soll: {productionOrder.plannedQuantity} {productionOrder.plannedQuantityUnit} · In Produktion
</div>
</div>
</Card>
</div>
{/* Scanned materials scrollable area */}
<div className="px-5 flex-1 min-h-0 overflow-y-auto space-y-2">
<h2 className="text-xs font-bold text-warm-500 uppercase tracking-wider mb-2">
Gescannte Materialien ({consumeItems.length})
</h2>
{consumeItems.length > 0 ? (
<div className="space-y-2">
{consumeItems.map((item, i) => (
<Card key={i} className="p-2.5 flex items-center gap-3 animate-fade-in">
<div className="w-2 h-10 rounded-full bg-brand-400 shrink-0" />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-warm-800">{item.name}</div>
<div className="text-xs text-warm-500">
{item.batch} · {item.location}
</div>
</div>
<div className="text-right shrink-0">
<div className="font-mono font-semibold text-sm text-warm-800">
{item.quantity} {item.unit}
</div>
{item.required != null && (
<div className="text-2xs text-warm-400">
von {item.required} {item.requiredUnit ?? item.unit}
</div>
)}
</div>
</Card>
))}
</div>
) : (
<Card className="p-5 text-center">
<div className="inline-flex items-center justify-center w-11 h-11 rounded-xl bg-warm-100 mb-2">
<PackageSearch className="w-5 h-5 text-warm-400" />
</div>
<p className="text-sm text-warm-500 mb-0.5">Noch keine Materialien erfasst</p>
<p className="text-xs text-warm-400">
Chargen-Etiketten der Eingangsmaterialien scannen.
</p>
</Card>
)}
<Button variant="secondary" size='lg' className="w-full" onClick={startScan}>
<ScanLine className="w-4 h-4" />
{consumeItems.length === 0 ? 'Erste Zutat scannen' : 'Nächste Zutat scannen'}
</Button>
</div>
<PageActions>
{hasItems && (
<Button variant="primary" size='lg' className="w-full" onClick={() => navigate('consume-confirm')}>
<Check className="w-5 h-5" />
{consumeItems.length} Verbrauch{consumeItems.length !== 1 ? 'e' : ''} buchen
</Button>
)}
</PageActions>
</div>
);
}