mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 06:39:34 +01:00
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.
119 lines
4.5 KiB
TypeScript
119 lines
4.5 KiB
TypeScript
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>
|
||
);
|
||
}
|