Files
Dramlog-Prod/src/components/StatusSwitcher.tsx
robin 334bece471 feat: implement comprehensive i18n system with German and English support
- Created type-safe i18n system with TranslationKeys interface
- Added German (de) and English (en) translations with 160+ keys
- Implemented I18nContext provider and useI18n hook
- Added LanguageSwitcher component for language selection
- Refactored all major components to use translations:
  * Home page, StatsDashboard, DramOfTheDay
  * BottleGrid, EditBottleForm, CameraCapture
  * BuddyList, SessionList, TastingNoteForm
  * StatusSwitcher and bottle management features
- Implemented locale-aware currency formatting (EUR)
- Implemented locale-aware date formatting
- Added localStorage persistence for language preference
- Added automatic browser language detection
- Organized translations into 8 main categories
- System is extensible for additional languages
2025-12-18 13:44:48 +01:00

73 lines
3.2 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { updateBottleStatus } from '@/services/update-bottle-status';
import { Loader2, Package, Play, CheckCircle, FlaskConical } from 'lucide-react';
import { useI18n } from '@/i18n/I18nContext';
interface StatusSwitcherProps {
bottleId: string;
currentStatus: 'sealed' | 'open' | 'sampled' | 'empty';
}
export default function StatusSwitcher({ bottleId, currentStatus }: StatusSwitcherProps) {
const { t } = useI18n();
const [status, setStatus] = useState(currentStatus);
const [loading, setLoading] = useState(false);
const handleStatusChange = async (newStatus: 'sealed' | 'open' | 'sampled' | 'empty') => {
if (newStatus === status || loading) return;
setLoading(true);
try {
const result = await updateBottleStatus(bottleId, newStatus);
if (result.success) {
setStatus(newStatus);
} else {
alert(result.error || t('common.error'));
}
} catch (err) {
alert(t('common.error'));
} finally {
setLoading(false);
}
};
const options = [
{ id: 'sealed', label: t('bottle.status.sealed'), icon: Package, color: 'hover:bg-blue-500' },
{ id: 'open', label: t('bottle.status.open'), icon: Play, color: 'hover:bg-amber-500' },
{ id: 'sampled', label: t('bottle.status.sampled'), icon: FlaskConical, color: 'hover:bg-purple-500' },
{ id: 'empty', label: t('bottle.status.empty'), icon: CheckCircle, color: 'hover:bg-zinc-500' },
] as const;
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-[11px] font-black text-zinc-400 uppercase tracking-widest">{t('bottle.bottleStatus')}</label>
{loading && <Loader2 className="animate-spin text-amber-600" size={14} />}
</div>
<div className="grid grid-cols-4 gap-2 p-1 bg-zinc-100 dark:bg-zinc-900/50 rounded-2xl border border-zinc-200/50 dark:border-zinc-800/50">
{options.map((opt) => {
const Icon = opt.icon;
const isActive = status === opt.id;
return (
<button
key={opt.id}
type="button"
disabled={loading}
onClick={() => handleStatusChange(opt.id)}
className={`flex flex-col items-center gap-1.5 py-3 px-1 rounded-xl text-[9px] font-black uppercase tracking-tight transition-all border-2 ${isActive
? 'bg-white dark:bg-zinc-700 border-amber-500 text-amber-600 shadow-sm ring-1 ring-black/5'
: 'border-transparent text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200'
}`}
>
<Icon size={18} className={isActive ? 'text-amber-500' : 'text-zinc-400'} />
{opt.label}
</button>
);
})}
</div>
</div>
);
}