// Completion.jsx // Time options for the End-time dropdown — hourly only (no half-hours), 6am–10pm. const COMPLETION_TIME_OPTS = (() => { const out = []; for (let h = 6; h <= 22; h++) out.push(`${String(h).padStart(2, '0')}:00`); return out; })(); // Mock GPS fix written into the completion photo stamp. const COMPLETION_GPS = { lat: '1.470892', long: '103.891765', alt: 14 }; function CompletionPage({ onSubmit, onBack, role = 'Operator', bookingId = 'BK-2026-00420' }) { // Operators & riggers complete jobs on a phone — render inside the device frame. // Office roles (Admin/Operation/Finance/Management) only view it, so they keep // the wide desktop card. const isMobile = role === 'Operator' || role === 'Rigger'; // Pull the live booking so company, location and scheduled times all flow // from how the booking was created — no hand-typed values. const booking = (typeof BOOKINGS !== 'undefined' && BOOKINGS.find(b => b.id === bookingId)) || { id: bookingId, customer: 'Ace Engineering', location: 'Pasir Gudang Industrial Park, Lot 14B', start: '08:00', end: '17:00', contact: 'Lim Wei Ming', machine: 'PLC-005 Tadano GR-500EX', tonnage: '50t', }; const canvasRef = React.useRef(null); const [drawing, setDrawing] = React.useState(false); const [hasSig, setHasSig] = React.useState(false); const [photo, setPhoto] = React.useState(null); // WhatsApp "PDF to sign" share flow. stage: 'gen' (building PDF) → 'ready' (attached) → 'sent'. const [waOpen, setWaOpen] = React.useState(false); const [waStage, setWaStage] = React.useState('gen'); // Start time follows the booking and is NOT editable. End time is hourly. const [endTime, setEndTime] = React.useState(booking.end || '17:00'); // Overtime is itemised by source. lunch = worked through 12–1pm; after5 = hours past 5pm. const [lunchOT, setLunchOT] = React.useState('no'); // 'no' | 'yes' (1 hr) // Overtime after 5pm is DERIVED from the selected End Time — never hand-entered. // Any hour past 17:00 counts; e.g. 7pm end → 2 hrs OT. const after5OT = Math.max(0, Number((endTime || '17:00').split(':')[0]) - 17); const after5Label = after5OT === 0 ? 'None' : `${after5OT} hr${after5OT > 1 ? 's' : ''}`; React.useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const dpr = window.devicePixelRatio || 1; canvas.width = canvas.offsetWidth * dpr; canvas.height = canvas.offsetHeight * dpr; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.strokeStyle = '#0B1F3A'; }, []); const pos = (e) => { const r = canvasRef.current.getBoundingClientRect(); const t = e.touches?.[0]; return { x: (t?.clientX ?? e.clientX) - r.left, y: (t?.clientY ?? e.clientY) - r.top }; }; const start = (e) => { e.preventDefault(); const ctx = canvasRef.current.getContext('2d'); const p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); setDrawing(true); }; const move = (e) => { if (!drawing) return; e.preventDefault(); const ctx = canvasRef.current.getContext('2d'); const p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); setHasSig(true); }; const end = () => setDrawing(false); const clear = () => { const c = canvasRef.current; const ctx = c.getContext('2d'); ctx.clearRect(0, 0, c.width, c.height); setHasSig(false); }; const fmtHM = (t) => { const [h, m] = (t || '00:00').split(':').map(Number); const ap = h < 12 ? 'am' : 'pm'; const h12 = ((h + 11) % 12) + 1; return `${h12}${m ? ':' + String(m).padStart(2, '0') : ''}${ap}`; }; const fmtDate = (iso) => new Date((iso || '2026-04-29') + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); // Total overtime = lunch (1 hr if worked) + hours after 5pm. const totalOT = (lunchOT === 'yes' ? 1 : 0) + Number(after5OT); const otLabel = totalOT === 0 ? 'None' : `${totalOT} hr${totalOT > 1 ? 's' : ''}`; const machineLabelStr = (typeof bookingMachineLabel !== 'undefined') ? bookingMachineLabel(booking) : booking.machine; const selectStyle = { width: '100%' }; // ===== WhatsApp share helpers ===== // Once the modal opens, briefly "render the signed sheet to PDF" before it appears attached. React.useEffect(() => { if (waOpen && waStage === 'gen') { const t = setTimeout(() => setWaStage('ready'), 1100); return () => clearTimeout(t); } }, [waOpen, waStage]); const waContact = booking.contact || booking.customer; const waPhone = booking.phone || '+60 12-345 6789'; const waInitials = (booking.customer || '?').trim().split(/\s+/).map(w => w[0]).slice(0, 2).join('').toUpperCase(); // Show the unit / body number (e.g. "9744"), not the internal fleet id (PLC-005). const waMachineNo = (typeof bookingMachineLabel === 'function' && bookingMachineLabel(booking)) || (booking.machine || '').replace(/^[A-Z]+-\d+\s+/, '') || booking.machine || ''; const waDate = booking.date || (typeof TODAY !== 'undefined' ? TODAY : ''); const waPreset = `Hi ${waContact}, attached is the Job Completion sheet for ${booking.id}${waDate ? ` — ${waDate}` : ''}${waMachineNo ? `, ${waMachineNo}` : ''}, ${booking.customer} — ${booking.location}. Kindly review and sign at the signed field, then send it back to confirm. Thank you — Pow Lee Crane.\n\nSalam boss ${waContact}, disertakan helaian Penyelesaian Kerja untuk ${booking.id}${waDate ? ` — ${waDate}` : ''}${waMachineNo ? `, ${waMachineNo}` : ''}, ${booking.customer} — ${booking.location}. Mohon semak dan tandatangan pada ruang tandatangan, kemudian hantar semula untuk pengesahan. Terima kasih — Pow Lee Crane.`; const waLink = `https://wa.me/${waPhone.replace(/[^0-9]/g, '')}?text=${encodeURIComponent(waPreset)}`; const openWhatsApp = () => { setWaStage('gen'); setWaOpen(true); }; const DoubleTick = ({ color }) => ( ); // The WhatsApp compose sheet — represents WhatsApp opened with the PDF attached + message pre-filled. const WhatsAppShareModal = waOpen ? (