mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:19:35 +01:00
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.
This commit is contained in:
parent
72979c9537
commit
bf09e3b747
93 changed files with 8977 additions and 60 deletions
119
frontend/apps/scanner/src/pages/ConsumeFlowPage.tsx
Normal file
119
frontend/apps/scanner/src/pages/ConsumeFlowPage.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue