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