// Tabi — language selection & menu history additions const TABI_LANGS = [ { code: 'JA', name: '日本語', zh: '日文', flag: '🇯🇵' }, { code: 'EN', name: 'English', zh: '英文', flag: '🇬🇧' }, { code: 'KO', name: '한국어', zh: '韓文', flag: '🇰🇷' }, { code: 'TH', name: 'ภาษาไทย', zh: '泰文', flag: '🇹🇭' }, { code: 'VI', name: 'Tiếng Việt', zh: '越南文', flag: '🇻🇳' }, { code: 'FR', name: 'Français', zh: '法文', flag: '🇫🇷' }, { code: 'ES', name: 'Español', zh: '西班牙文', flag: '🇪🇸' }, { code: 'IT', name: 'Italiano', zh: '義大利文', flag: '🇮🇹' }, { code: 'DE', name: 'Deutsch', zh: '德文', flag: '🇩🇪' }, ]; // Compact language pill — clickable, opens picker sheet function TabiLangPill({ from = 'JA', to = 'ZH', onClick, small }) { return ( ); } // Bottom-sheet language picker function TabiLangSheet({ open, current, onSelect, onClose }) { if (!open) return null; return (
e.stopPropagation()} style={{ width: '100%', background: TABI_COLORS.bg, borderRadius: '20px 20px 0 0', padding: '12px 0 28px', maxHeight: '78%', overflow: 'auto', }}>
選擇翻譯語言
從中文翻譯到 / Translate Chinese to
{TABI_LANGS.map(l => { const on = l.code === current; return ( ); })}
); } function _fmtDate(iso) { const d = new Date(iso); const now = new Date(); const diffDays = Math.floor((now - d) / 86400000); const hhmm = d.toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', hour12: false }); if (diffDays === 0) return `今天 ${hhmm}`; if (diffDays === 1) return `昨天 ${hhmm}`; return `${d.getMonth()+1}/${d.getDate()} ${hhmm}`; } // ─────────── MENU HISTORY ─────────── function MenuHistoryScreen({ go, onOpen }) { const [list, setList] = React.useState(null); const [confirmDel, setConfirmDel] = React.useState(null); const [deleting, setDeleting] = React.useState(false); const load = () => { fetch('/api/menus', { headers: { Authorization: `Bearer ${window.tabiToken()}` } }) .then(r => r.json()) .then(data => setList(Array.isArray(data) ? data : [])) .catch(() => setList([])); }; React.useEffect(() => { load(); }, []); const remove = async (id) => { setDeleting(true); await fetch(`/api/menus/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${window.tabiToken()}` }, }).catch(() => {}); setDeleting(false); setConfirmDel(null); load(); }; const COVER_PALETTES = [ ['#E8C794','#D9A441','#C8553D'], ['#3B5A7A','#5B7553','#A0412F'], ['#5B7553','#8C6F4A','#D9A441'], ['#A0412F','#C8553D','#E8C794'], ['#8C6F4A','#3B5A7A','#5B7553'], ]; return (
go('home')} right={ } />
{list === null ? (
) : list.length === 0 ? (
🍱
還沒有儲存的菜單
) : ( <>
{list.length}
張已儲存的菜單
{list.map((h, idx) => { const cover = COVER_PALETTES[idx % COVER_PALETTES.length]; return (
onOpen(h.id)} style={{ width: 64, height: 64, borderRadius: 12, overflow: 'hidden', flexShrink: 0, background: TABI_COLORS.line2, cursor: 'pointer', position: 'relative', }}> {h.image_url ? ( ) : (
4 ? 16 : 20, color: 'rgba(255,255,255,0.92)', textShadow: '0 1px 6px rgba(0,0,0,0.25)', letterSpacing: '-0.01em', textAlign: 'center', padding: '0 4px', lineHeight: 1.2, }}> {(h.name || '?').slice(0, 4)}
)}
onOpen(h.id)} style={{ flex: 1, minWidth: 0, cursor: 'pointer' }}>
{h.source_lang}→ZH
{h.name}
{_fmtDate(h.created_at)}
); })} )}
{confirmDel && (
setConfirmDel(null)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, }}>
e.stopPropagation()} style={{ background: TABI_COLORS.bg, borderRadius: 18, padding: '22px', width: '100%', maxWidth: 280, }}>
刪除這份菜單?
刪除後將無法復原。
)}
); } Object.assign(window, { TABI_LANGS, TabiLangPill, TabiLangSheet, MenuHistoryScreen, });