/* App root: store, context, router, tweaks, render */ const DEFAULT_TWEAKS = { calendarStyle: 'badge', // badge | dot | heat queueLayout: 'card', // card | table assignMethod: 'select', // select | drag dashboardStyle:'charts', // charts | cards headerTheme: 'red', // red | dark deviceFrame: true, // show phone bezel in mobile view accent: '#dc2626', }; function useStore() { const [jobs, setJobs] = React.useState(() => CK.JOBS.map(j => ({ ...j }))); const [toasts, setToasts] = React.useState([]); const toast = React.useCallback((msg, kind='info') => { const id = Math.random().toString(36).slice(2); setToasts(t => [...t, { id, msg, kind }]); setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 3200); }, []); const addJob = React.useCallback((job) => { setJobs(j => [{ ...job }, ...j]); toast('บันทึกงานใหม่เรียบร้อย • แจ้งสำนักงานใหญ่แบบ real-time', 'success'); }, [toast]); const updateJob = React.useCallback((id, patch) => { setJobs(j => j.map(x => x.id === id ? { ...x, ...patch } : x)); }, []); const setStatus = React.useCallback((id, status, note='', by='ผู้ใช้ปัจจุบัน') => { setJobs(j => j.map(x => { if (x.id !== id) return x; const entry = { by, from: x.status, to: status, note, at: new Date().toLocaleString('th-TH', { day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit' }) }; return { ...x, status, log: [...(x.log||[]), entry] }; })); toast('อัปเดตสถานะเป็น "' + CK.STATUS[status].th + '"', 'success'); }, [toast]); const assign = React.useCallback((id, teamId) => { setJobs(j => j.map(x => x.id === id ? { ...x, team: teamId, status: x.status==='cancelled'?x.status:'assigned' } : x)); }, []); return { jobs, addJob, updateJob, setStatus, assign, toast, toasts }; } function Toasts() { const { toasts } = useCK(); return (
{toasts.map(t => (
{t.msg}
))}
); } const SCREENS = { dashboard: () => , addjob: () => , queue: () => , calendar: () => , cover: () => , assign: () => , mytasks: () => , manage: () => , }; function App() { const store = useStore(); const [role, setRoleRaw] = React.useState('hq'); const [device, setDevice] = React.useState('desktop'); const [route, setRoute] = React.useState('queue'); const [nav, setNav] = React.useState({}); // extra params for the route (date, jobId...) const [tweaks, setTweaks] = React.useState(() => { try { const saved = JSON.parse(localStorage.getItem('ck_tweaks') || '{}'); return { ...DEFAULT_TWEAKS, ...(saved && typeof saved === 'object' && !Array.isArray(saved) ? saved : {}) }; } catch { return DEFAULT_TWEAKS; } }); React.useEffect(() => { localStorage.setItem('ck_tweaks', JSON.stringify(tweaks)); }, [tweaks]); const setRole = React.useCallback((r) => { setRoleRaw(r); setDevice(CK.ROLES[r].device); setRoute(ROLE_NAV[r][0]); setNav({}); }, []); const navigate = React.useCallback((r, params={}) => { setRoute(r); setNav(params); }, []); // keep route valid for role React.useEffect(() => { if (!ROLE_NAV[role].includes(route)) setRoute(ROLE_NAV[role][0]); }, [role]); // eslint-disable-line const ctx = { ...store, role, setRole, device, setDevice, route, navigate, nav, tweaks, setTweaks }; const Screen = SCREENS[route] || (() => ); return ( {window.TweaksPanelCK && } ); } const style = document.createElement('style'); style.textContent = '@keyframes fadein{from{opacity:0;transform:translateY(6px)}to{opacity:1}}'; document.head.appendChild(style); ReactDOM.createRoot(document.getElementById('root')).render();