Files
speiseplan/frontend/src/components/AllergenPicker.tsx
clawd df9e7c5541 v0.2.1 — Code Review Fixes
Fixes:
- CRITICAL: compareVersions used string comparison, fails for 1.10 vs 1.9 (now numeric)
- CRITICAL: Double CloseDatabase() in main.go (defer + OnShutdown)
- CRITICAL: Tailwind v4 in package.json but v3 config/syntax (downgraded to v3)
- CRITICAL: react-router-dom v7 with v5 types (switched to v6, removed deprecated types)
- IMPORTANT: UpdatePlanEntry hook signature mismatch (7 args vs Go's 4)
- IMPORTANT: AllergenPicker hidden checkbox inaccessible to screenreaders (sr-only)
- IMPORTANT: weekHelper getWeekFromDate returned wrong ISO year for edge cases
- IMPORTANT: getWeeksInYear bug for years where Dec 31 is in week 1 of next year
- IMPORTANT: getDateFromWeek off-by-one for some years (use Jan 4 anchor)
- IMPORTANT: ProductSearch click-outside missed dropdown (use container ref)
- IMPORTANT: seed.go LastInsertId=0 on INSERT OR IGNORE skip
- IMPORTANT: SQLite missing PRAGMA foreign_keys=ON and WAL mode
- IMPORTANT: AdditivePicker ADDITIVE_NAMES used numeric IDs but data uses letters
- IMPORTANT: Missing role=dialog/aria-modal on all modal dialogs
- IMPORTANT: Missing Escape key handler on ProductForm modal
- IMPORTANT: Sidebar NavLink aria-current used function instead of string
- IMPORTANT: useProducts searchProducts null safety for allergens/additives
- NICE-TO-HAVE: Added aria-live=polite to WeekPlanner for dynamic updates
- NICE-TO-HAVE: Added postcss.config.js for Tailwind v3
- NICE-TO-HAVE: Updated model comments to match actual day/meal conventions
- NICE-TO-HAVE: Modernized vite/typescript/plugin versions
2026-02-20 10:11:54 +00:00

106 lines
3.2 KiB
TypeScript

import { Allergen } from '../types';
interface AllergenPickerProps {
allergens: Allergen[];
selectedIds: string[];
onChange: (selectedIds: string[]) => void;
className?: string;
}
// Deutsche Namen für Allergene (nach EU-Verordnung)
const ALLERGEN_NAMES: Record<string, string> = {
'a': 'Glutenhaltige Getreide',
'b': 'Krebstiere',
'c': 'Eier',
'd': 'Fisch',
'e': 'Erdnüsse',
'f': 'Soja',
'g': 'Milch/Laktose',
'h': 'Schalenfrüchte',
'i': 'Sellerie',
'j': 'Senf',
'k': 'Sesam',
'l': 'Schwefeldioxid',
'm': 'Lupinen',
'n': 'Weichtiere'
};
export function AllergenPicker({ allergens, selectedIds, onChange, className = '' }: AllergenPickerProps) {
const handleToggle = (allergenId: string) => {
if (selectedIds.includes(allergenId)) {
onChange(selectedIds.filter(id => id !== allergenId));
} else {
onChange([...selectedIds, allergenId]);
}
};
// Sortierte Allergene (a-n)
const sortedAllergens = [...allergens].sort((a, b) => a.id.localeCompare(b.id));
return (
<fieldset className={`border border-gray-300 rounded-md p-4 ${className}`}>
<legend className="text-sm font-medium text-gray-900 px-2">
Allergene auswählen
</legend>
<div
className="grid grid-cols-2 gap-3 mt-3"
role="group"
aria-labelledby="allergen-picker-legend"
>
{sortedAllergens.map(allergen => {
const isSelected = selectedIds.includes(allergen.id);
const displayName = ALLERGEN_NAMES[allergen.id] || allergen.name;
return (
<label
key={allergen.id}
className={`flex items-center space-x-3 p-3 rounded-md border cursor-pointer transition-colors min-h-[44px] ${
isSelected
? 'bg-primary text-white border-primary'
: 'bg-white hover:bg-gray-50 border-gray-200'
}`}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => handleToggle(allergen.id)}
className="sr-only"
aria-describedby={`allergen-${allergen.id}-description`}
/>
<span
className={`flex-shrink-0 w-6 h-6 flex items-center justify-center text-xs font-bold rounded ${
isSelected ? 'bg-white text-primary' : 'bg-danger text-white'
}`}
aria-hidden="true"
>
{allergen.id}
</span>
<span className="flex-1 text-sm font-medium">
{displayName}
</span>
<span
id={`allergen-${allergen.id}-description`}
className="sr-only"
>
Allergen {allergen.id}: {displayName}
</span>
</label>
);
})}
</div>
{selectedIds.length > 0 && (
<div className="mt-4 p-3 bg-gray-50 rounded-md">
<p className="text-sm text-gray-600">
<strong>Ausgewählte Allergene:</strong>{' '}
{selectedIds.sort().join(', ')}
</p>
</div>
)}
</fieldset>
);
}