/* Week Board (Kanban) view for Daily Queue — 7 day columns with capacity headers */ // ---- helpers ---- function weekStart(isoStr){ // Monday-start week containing the date const d = new Date(isoStr+'T00:00:00'); const dow = (d.getDay()+6)%7; // 0=Mon d.setDate(d.getDate()-dow); return CK.iso(d); } function statusBigColor(status){ return { open:'#16a34a', almost:'#d97706', full:'#dc2626', closed:'#64748b' }[status] || '#64748b'; } // ===== compact job card (board) ===== function BoardJobCard({ job, seq, onOpen }){ const b = CK.branch(job.branch); const jt = CK.jobType(job.jobType); const it = job.items[0]; const firstSlot = (job.slot||'').startsWith('คิวแรก'); const prod = CK.product(it?.p); return ( ); } // ===== capacity headers (4 styles) ===== function MeterHeader({ d, date, isToday, onToggleClosed, onOpenDay }){ const color = statusBigColor(d.status); const segs = 12; const filled = Math.round(d.pct/100*segs); return (
{CK.DAYSTATUS[d.status].th} {d.pct}%
{Array.from({length:segs}).map((_,i)=>( ))}
ใช้ไป {d.used} คิวเต็มที่ {d.max}
); } function GaugeHeader({ d, date, isToday, onToggleClosed, onOpenDay }){ const color = statusBigColor(d.status); const r=40, c=Math.PI*r, off=c*(1-Math.min(100,d.pct)/100); return (
{d.pct}%
{CK.DAYSTATUS[d.status].th} · {d.used}/{d.max} คิว
); } function BlueprintHeader({ d, date, isToday, onToggleClosed, onOpenDay }){ const color = statusBigColor(d.status); const ticks=20; const filled=Math.round(d.pct/100*ticks); return (
USED {d.used.toFixed(1)}CAP {d.max.toFixed(1)}
{Array.from({length:ticks}).map((_,i)=>( ))}
{ {open:'OPEN',almost:'NEAR',full:'FULL',closed:'CLOSED'}[d.status] } {d.pct}%
); } function CompactHeader({ d, date, isToday, onToggleClosed, onOpenDay }){ const color = statusBigColor(d.status); return (
); } const CAP_HEADERS = { meter:MeterHeader, gauge:GaugeHeader, blueprint:BlueprintHeader, compact:CompactHeader }; // ===== Week Board ===== function WeekBoard({ jobs, filters, onOpenJob, onOpenDay, isMobile }){ const [start,setStart] = React.useState(CK.TODAY); const [capStyle,setCapStyle] = React.useState('meter'); const [, force] = React.useReducer(x=>x+1,0); const days = Array.from({length:7},(_,i)=>CK.addDays(start,i)); const Header = CAP_HEADERS[capStyle]; // week stats const weekJobs = jobs.filter(j=>days.includes(j.date) && j.status!=='cancelled'); const filteredAll = applyFilters(weekJobs, {...filters, date:undefined}); const fullDays = days.filter(d=>computeDayStatus(jobs,d).status==='full').length; const freeUnits = Math.round(days.reduce((s,d)=>{ const x=computeDayStatus(jobs,d); return s+Math.max(0,x.max-x.used); },0)*10)/10; function toggleClosed(d){ const c=CK.CAPACITY[d]||{max:10,closed:false}; CK.CAPACITY[d]={...c,closed:!c.closed}; force(); } const capOpts = [['meter','เมเตอร์'],['gauge','เกจ'],['blueprint','บลูพรินต์'],['compact','กระชับ']]; const VISIBLE = 4; return (
{/* board controls */}
{CK.thDate(days[0])} – {CK.thDate(days[6])}
มุมมอง
{capOpts.map(([k,l])=>( ))}
{/* legend + week stats */}
{Object.values(CK.DAYSTATUS).map(s=>( {s.th} ))}
งานสัปดาห์นี้
{filteredAll.length}
คิวที่เต็ม
{fullDays} วัน
ว่างรับเพิ่ม
{freeUnits} คิว
{/* columns */}
{days.map(d=>{ const ds = computeDayStatus(jobs,d); const isToday = d===CK.TODAY; const colJobs = sortQueue(applyFilters(jobs.filter(j=>j.date===d && j.status!=='cancelled'), {...filters, date:undefined})); const shown = colJobs.slice(0,VISIBLE); const extra = colJobs.length - shown.length; return (
toggleClosed(d)} onOpenDay={()=>onOpenDay(d)} /> {colJobs.length===0 ? (
ไม่มีงาน
) : ( <> {shown.map((j,i)=>onOpenJob(j.id)} />)} {extra>0 && } )}
); })}
); } Object.assign(window, { WeekBoard, weekStart });