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/cli/src/components/masterdata/customers/SetPreferencesScreen.tsx
Sebastian Frick 6c1e6c24bc feat(production): articleId für Rezepte, TUI-Verbesserungen mit UoM-Carousel, ArticlePicker und Zutaten-Reorder
Backend:
- articleId als Pflichtfeld im Recipe-Aggregate (Domain, Application, Infrastructure)
- Liquibase-Migration 015 mit defaultValue für bestehende Daten
- Alle Tests angepasst (Unit, Integration)

Frontend:
- UoM-Carousel-Selektor in RecipeCreateScreen, AddBatchScreen, AddIngredientScreen
- ArticlePicker-Komponente mit Typeahead-Suche für Artikelauswahl
- Auto-Position bei Zutatenzugabe (kein manuelles Feld mehr)
- Automatische subRecipeId-Erkennung bei Artikelauswahl
- Zutaten-Reorder per Drag im RecipeDetailScreen (Remove + Re-Add)
- Artikelnamen statt UUIDs in der Rezept-Detailansicht
- Navigation-Context: replace()-Methode ergänzt
2026-02-20 01:15:34 +01:00

89 lines
3.5 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react';
import { Box, Text, useInput } from 'ink';
import type { CustomerPreference } from '@effigenix/api-client';
import { CUSTOMER_PREFERENCE_LABELS } from '@effigenix/api-client';
import { useNavigation } from '../../../state/navigation-context.js';
import { useCustomers } from '../../../hooks/useCustomers.js';
import { LoadingSpinner } from '../../shared/LoadingSpinner.js';
import { ErrorDisplay } from '../../shared/ErrorDisplay.js';
import { client } from '../../../utils/api-client.js';
const ALL_PREFERENCES: CustomerPreference[] = [
'BIO', 'REGIONAL', 'TIERWOHL', 'HALAL', 'KOSHER', 'GLUTENFREI', 'LAKTOSEFREI',
];
function errorMessage(err: unknown): string {
return err instanceof Error ? err.message : 'Unbekannter Fehler';
}
export function SetPreferencesScreen() {
const { params, replace, back } = useNavigation();
const customerId = params['customerId'] ?? '';
const { setPreferences, loading, error, clearError } = useCustomers();
const [selectedIndex, setSelectedIndex] = useState(0);
const [checked, setChecked] = useState<Set<CustomerPreference>>(new Set());
const [initLoading, setInitLoading] = useState(true);
const [initError, setInitError] = useState<string | null>(null);
useEffect(() => {
client.customers.getById(customerId)
.then((c) => { setChecked(new Set(c.preferences)); setInitLoading(false); })
.catch((err: unknown) => { setInitError(errorMessage(err)); setInitLoading(false); });
}, [customerId]);
useInput((_input, key) => {
if (initLoading || loading) return;
if (key.upArrow) setSelectedIndex((i) => Math.max(0, i - 1));
if (key.downArrow) setSelectedIndex((i) => Math.min(ALL_PREFERENCES.length - 1, i + 1));
if (_input === ' ') {
const pref = ALL_PREFERENCES[selectedIndex];
if (pref) {
setChecked((prev) => {
const next = new Set(prev);
if (next.has(pref)) next.delete(pref);
else next.add(pref);
return next;
});
}
}
if (key.return) void handleSave();
if (key.escape) back();
});
const handleSave = useCallback(async () => {
const updated = await setPreferences(customerId, Array.from(checked));
if (updated) replace('customer-detail', { customerId });
}, [customerId, checked, setPreferences, replace]);
if (initLoading) return <LoadingSpinner label="Lade Präferenzen..." />;
if (initError) return <ErrorDisplay message={initError} onDismiss={back} />;
if (loading) return <LoadingSpinner label="Speichere Präferenzen..." />;
return (
<Box flexDirection="column" gap={1}>
<Text color="cyan" bold>Präferenzen setzen</Text>
{error && <ErrorDisplay message={error} onDismiss={clearError} />}
<Box flexDirection="column" borderStyle="round" borderColor="gray" paddingX={2} paddingY={1} gap={0}>
{ALL_PREFERENCES.map((pref, i) => {
const isSelected = i === selectedIndex;
const isChecked = checked.has(pref);
return (
<Box key={pref} gap={1}>
<Text color={isSelected ? 'cyan' : 'gray'}>{isSelected ? '▶' : ' '}</Text>
<Text color={isChecked ? 'green' : 'gray'}>{isChecked ? '[✓]' : '[ ]'}</Text>
<Text color={isSelected ? 'cyan' : 'white'}>{CUSTOMER_PREFERENCE_LABELS[pref]}</Text>
</Box>
);
})}
</Box>
<Box marginTop={1}>
<Text color="gray" dimColor>
navigieren · Leertaste togglen · Enter speichern · Escape Abbrechen
</Text>
</Box>
</Box>
);
}