// 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.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,
});