import React, { useState, useEffect, useMemo } from "react"; import { Plus, Search, Trash2, ArrowLeft, Printer, Clock, CheckCircle2, FileText, Loader2, AlertCircle, Mail, MessageCircle, Tag, Copy, } from "lucide-react"; // ---------- styling (custom tokens layered under Tailwind utilities) ---------- const STYLES = ` .wo-root { --paper: #EFE6D2; --paper-deep: #DBCBA2; --ink: #2A241C; --ink-soft: #6B6152; --rust: #C1502B; --rust-deep: #8F3A1E; --bench: #1B232C; --bench-panel: #232D38; --bench-line: #344150; --bench-text: #E8E3D8; --bench-label: #93A4B2; --amber: #E8A33D; --ok: #5B8C3E; --ok-soft: #DCEAD2; --cream: #F7F2E6; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: var(--ink); background: var(--cream); min-height: 100vh; } .wo-mono { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; } .wo-header { background: var(--ink); color: var(--cream); border-bottom: 4px solid var(--rust); } .wo-card { background: var(--paper); border: 1px solid var(--paper-deep); border-left: 6px solid var(--rust); } .wo-card:hover { border-left-color: var(--rust-deep); } .wo-panel-paper { background: var(--paper); border: 1px solid var(--paper-deep); } .wo-panel-paper input, .wo-panel-paper textarea { background: var(--cream); border: 1px solid var(--paper-deep); color: var(--ink); } .wo-panel-paper input::placeholder, .wo-panel-paper textarea::placeholder { color: #B5A98A; } .wo-panel-paper label { color: var(--ink-soft); } .wo-panel-paper input:focus, .wo-panel-paper textarea:focus { outline: none; border-color: var(--rust); box-shadow: 0 0 0 3px rgba(193,80,43,0.15); } .wo-panel-bench { background: var(--bench); color: var(--bench-text); } .wo-panel-bench input, .wo-panel-bench textarea { background: var(--bench-panel); border: 1px solid var(--bench-line); color: var(--bench-text); } .wo-panel-bench input::placeholder, .wo-panel-bench textarea::placeholder { color: #5C6A78; } .wo-panel-bench label { color: var(--bench-label); } .wo-panel-bench input:focus, .wo-panel-bench textarea:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px rgba(232,163,61,0.18); } .wo-divider { position: relative; height: 0; border-top: 3px dashed var(--paper-deep); margin: 0 1.5rem; } .wo-divider::before, .wo-divider::after { content: ""; position: absolute; width: 16px; height: 16px; background: var(--cream); border-radius: 50%; top: -9px; } .wo-divider::before { left: -1.5rem; } .wo-divider::after { right: -1.5rem; } .wo-badge { font-size: 0.65rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; padding: 0.2rem 0.55rem; border-radius: 999px; display: inline-flex; align-items: center; gap: 0.3rem; } .wo-badge-new { background: var(--paper-deep); color: var(--ink); } .wo-badge-progress { background: #F4D8AE; color: #7A4A12; } .wo-badge-done { background: var(--ok-soft); color: var(--ok); } .wo-btn-primary { background: var(--rust); color: var(--cream); } .wo-btn-primary:hover { background: var(--rust-deep); } .wo-fade-in { animation: woFadeIn 0.25s ease-out; } @keyframes woFadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .wo-label { display: none; } @media print { .no-print { display: none !important; } .wo-panel-bench { background: white !important; color: black !important; border: 1px solid #999 !important; } .wo-panel-bench input, .wo-panel-bench textarea, .wo-panel-bench label { background: white !important; color: black !important; border-color: #999 !important; } .wo-root { background: white !important; } } `; const STORAGE_KEY = "workorders-data"; function blankOrder(number) { const year = new Date().getFullYear(); return { id: `WO-${year}-${String(number).padStart(4, "0")}`, number, createdAt: new Date().toISOString(), contactPerson: "", address: "", email: "", phone: "", serial: "", osVersion: "", familyNumber: "", specs: "", model: "", password: "", itemsSent: "", problemType: "", otherNotes: "", signature: "", dateSigned: "", dateReceived: "", dateCompleted: "", techNotes: "", problemsSolution: "", partsOrdered: "", calledContact: "", poNumber: "", dateTime: "", costs: "", }; } function getStatus(order) { if (order.dateCompleted) return "done"; if (order.dateReceived) return "progress"; return "new"; } function statusLabel(status) { if (status === "done") return "Completed"; if (status === "progress") return "In repair"; return "New"; } function buildMessage(order, kind) { const name = order.contactPerson || "there"; const device = order.model || "computer"; if (kind === "approval") { const parts = order.partsOrdered ? ` (${order.partsOrdered})` : ""; const cost = order.costs ? `, estimated cost ${order.costs}` : ""; return `Hi ${name}, this is Jaks Systems. We need your approval before ordering parts for ticket ${order.id}${parts}${cost}. Please confirm and we'll go ahead.`; } if (kind === "ready") { return `Hi ${name}, good news — your ${device} (ticket ${order.id}) is repaired and ready for pickup at Jaks Systems, Christ Church.`; } return `Hi ${name}, this is Jaks Systems. We've received your ${device} for repair (ticket ${order.id}). We'll be in touch with updates. Thanks!`; } function defaultTemplateKind(order) { if (getStatus(order) === "done") return "ready"; if (order.partsOrdered) return "approval"; return "received"; } function formatWhatsAppNumber(phone) { const digits = (phone || "").replace(/\D/g, ""); if (!digits) return ""; if (digits.length === 10) return `1${digits}`; return digits; } function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function printableShell(title, bodyHtml, pageStyle) { return ` ${escapeHtml(title)} ${bodyHtml} `; } function buildFullPrintDoc(order) { const field = (label, value, full) => `
${escapeHtml(label)}
${escapeHtml(value) || " "}
`; const style = ` h1 { font-size: 19px; margin: 0 0 2px; letter-spacing: 0.02em; } .sub { font-size: 11px; color: #666; margin-bottom: 18px; } .section-title { font-size: 12px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid #999; margin: 18px 0 10px; padding-bottom: 4px; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 18px; } .po-field { font-size: 12px; padding: 3px 0; border-bottom: 1px dotted #ccc; } .po-field.full { grid-column: 1 / -1; } .po-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.04em; color: #777; } .po-value { margin-top: 1px; min-height: 14px; } .note { font-size: 10px; color: #777; font-style: italic; margin-bottom: 10px; } @page { margin: 0.5in; } `; const body = `

Jaks Systems

Christ Church · 246-257-3181 · Ticket ${escapeHtml(order.id)}
Customer intake
No parts will be ordered without contact person's permission.
${field("Contact person", order.contactPerson)} ${field("Phone / cell", order.phone)} ${field("Address", order.address)} ${field("E-mail", order.email)}
Computer information
${field("Serial number", order.serial)} ${field("OS version", order.osVersion)} ${field("Family number", order.familyNumber)} ${field("Processor / RAM / harddrive", order.specs)} ${field("Model", order.model)} ${field("Password", order.password)} ${field("Items sent with computer", order.itemsSent, true)} ${field("Type of problem", order.problemType, true)} ${field("Other notes", order.otherNotes, true)} ${field("Signature", order.signature)} ${field("Date", order.dateSigned)}
Repair center
${field("Date received", order.dateReceived)} ${field("Date completed and sent", order.dateCompleted)} ${field("Tech notes", order.techNotes, true)} ${field("Problems / solution", order.problemsSolution, true)} ${field("Parts ordered", order.partsOrdered)} ${field("Called contact person", order.calledContact)} ${field("PO #", order.poNumber)} ${field("Date / time", order.dateTime)} ${field("Costs", order.costs)}
`; return printableShell(`Work order ${order.id}`, body, style); } function buildLabelPrintDoc(order) { const style = ` body { padding: 0.15in; width: 3in; } .brand { font-size: 9px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid #000; padding-bottom: 4px; margin-bottom: 6px; } .id { font-family: "Courier New", monospace; font-size: 18px; font-weight: 800; margin-bottom: 6px; } .row { font-size: 11px; line-height: 1.35; margin-bottom: 2px; } .date { color: #555; font-size: 9px; margin-top: 4px; } @page { size: 4in 2in; margin: 0; } `; const device = [order.model, order.serial ? `SN ${order.serial}` : ""].filter(Boolean).join(" · ") || "Device not specified"; const dropped = order.dateSigned || order.createdAt; const body = `
Jaks Systems · 246-257-3181
${escapeHtml(order.id)}
${escapeHtml(order.contactPerson) || "Unnamed customer"}
${escapeHtml(order.phone) || "No phone on file"}
${device}
${escapeHtml(order.problemType) || "No problem noted"}
Dropped off ${dropped ? escapeHtml(new Date(dropped).toLocaleDateString()) : ""}
`; return printableShell(`Label ${order.id}`, body, style); } function StatusBadge({ status }) { const cls = status === "done" ? "wo-badge-done" : status === "progress" ? "wo-badge-progress" : "wo-badge-new"; const Icon = status === "done" ? CheckCircle2 : status === "progress" ? Clock : FileText; return ( {statusLabel(status)} ); } function Field({ label, value, onChange, type = "text", mono = false, placeholder, full = false }) { return ( ); } function TextArea({ label, value, onChange, rows = 3, full = true }) { return (