Files
docs/悦/PDA(海外)/index.html
T
2026-08-24 19:10:22 +08:00

792 lines
160 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Overseas Warehouse PDA</title>
<link rel="icon" href="data:,">
<style>
:root{--blue:#1668dc;--blue-dark:#0f4da8;--blue-soft:#eaf2ff;--ink:#17324d;--muted:#6f8194;--neutral-0:#fff;--neutral-50:#f8fafc;--neutral-100:#f3f6fa;--neutral-200:#edf1f5;--neutral-300:#dfe7ef;--neutral-500:#6f8194;--neutral-900:#17324d;--bg:var(--neutral-100);--surface:var(--neutral-0);--line:var(--neutral-300);--green:#10856f;--green-soft:#e2f6f0;--orange:#b86612;--orange-soft:#fff1dd;--red:#c63d3d;--red-soft:#fde8e8;--shadow-soft:0 2px 10px rgba(16,24,40,.05);--shadow:0 8px 24px rgba(16,24,40,.08);--shadow-modal:0 12px 40px rgba(16,24,40,.18);--focus-ring:rgba(22,104,220,.28);--tap-highlight:rgba(22,104,220,.12);--safe-top:env(safe-area-inset-top,0px);--safe-right:env(safe-area-inset-right,0px);--safe-bottom:env(safe-area-inset-bottom,0px);--safe-left:env(safe-area-inset-left,0px);--radius:14px}
*{box-sizing:border-box;-webkit-tap-highlight-color:var(--tap-highlight)}html,body{margin:0;min-height:100%;background:var(--bg);color:var(--ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Noto Sans SC",Arial,sans-serif}body{max-width:480px;margin:0 auto;padding:var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left)}button,input,select{font:inherit;min-height:44px}button{cursor:pointer;border:0}button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}.hidden{display:none!important}
.app{min-height:100vh;background:var(--bg)}
.topbar{position:sticky;top:0;z-index:20;background:#fff;border-bottom:1px solid var(--line);padding:12px 18px;box-shadow:var(--shadow-soft)}.topbar-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.brand{display:flex;align-items:center;gap:10px}.brand-mark{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:var(--blue);color:white;font-weight:800}.brand-title{font-size:16px;font-weight:800;letter-spacing:.01em}.brand-sub{font-size:11px;color:var(--muted);margin-top:2px}.top-actions{display:flex;align-items:center;gap:8px}.lang-pill{display:flex;gap:2px;padding:3px;background:#f1f5f9;border-radius:9px}.lang-pill button{background:transparent;color:var(--muted);padding:7px 8px;border-radius:7px;font-size:12px}.lang-pill button.active{background:#fff;color:var(--blue);font-weight:700;box-shadow:var(--shadow-soft)}.avatar{width:44px;height:44px;border-radius:50%;background:var(--blue-soft);color:var(--blue);font-weight:800}.back-btn{width:44px;height:44px;flex:0 0 44px;border-radius:9px;background:#f2f6fb;color:var(--ink);font-size:20px;line-height:1}.page-heading{display:flex;align-items:center;gap:10px;min-width:0}.page-heading h1{font-size:21px;margin:0}.page-heading p{font-size:12px;color:var(--muted);margin:4px 0 0}.content{padding:20px;max-width:980px;margin:0 auto}.content.narrow{max-width:640px}
.login-wrap{min-height:100vh;display:grid;place-items:center;padding:22px}.login-card{width:min(430px,100%);background:#fff;border:1px solid var(--line);border-radius:20px;padding:30px 26px;box-shadow:var(--shadow)}.login-card .brand{margin-bottom:26px}.login-card h1{font-size:25px;margin:0 0 8px}.login-card .intro{color:var(--muted);font-size:13px;line-height:1.6;margin:0 0 22px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:12px;font-weight:700;color:#496176}.field input,.field select{width:100%;height:46px;border:1px solid var(--line);border-radius:10px;padding:0 13px;background:#fff;color:var(--ink);outline:none}.field input:focus,.field select:focus{border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-soft)}.login-error{min-height:18px;color:var(--red);font-size:12px;margin:4px 0 8px}.login-btn{width:100%;height:48px;border-radius:11px;background:var(--blue);color:#fff;font-weight:800}.demo-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:15px}.demo-row button{padding:8px 10px;border:1px solid var(--line);background:#fff;border-radius:8px;color:var(--muted);font-size:12px}.demo-tip{color:var(--muted);font-size:11px;line-height:1.5;margin-top:16px}
.welcome{background:var(--blue);color:#fff;border-radius:16px;padding:20px;display:flex;justify-content:space-between;gap:12px;align-items:flex-start;box-shadow:0 9px 22px rgba(16,24,40,.12)}.welcome-kicker{font-size:12px;opacity:.78}.welcome-name{font-size:24px;font-weight:800;margin-top:4px}.welcome-meta{font-size:12px;opacity:.86;margin-top:8px}.shift{padding:8px 10px;border-radius:8px;background:rgba(255,255,255,.15);font-size:11px;white-space:nowrap}.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:16px 0}.metric{background:#fff;border:1px solid var(--line);border-radius:13px;padding:14px 12px;min-width:0}.metric-label{font-size:12px;color:var(--muted);line-height:1.35}.metric-value{font-size:26px;font-weight:800;margin-top:8px;color:var(--ink)}.metric.warn .metric-value{color:var(--orange)}.metric.good .metric-value{color:var(--green)}.metric.blue .metric-value{color:var(--blue)}.section-title{display:flex;justify-content:space-between;align-items:center;margin:24px 0 10px}.section-title h2{font-size:15px;margin:0}.section-title span{font-size:11px;color:var(--muted)}.entry-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.entry{position:relative;text-align:left;background:#fff;border:1px solid var(--line);border-radius:13px;padding:15px;min-height:110px;box-shadow:0 3px 10px rgba(16,24,40,.04)}.entry:hover{border-color:#aac7f5}.entry .entry-icon{font-size:22px}.entry h3{font-size:14px;margin:11px 0 4px}.entry p{font-size:11px;color:var(--muted);line-height:1.4;margin:0}.entry .badge{position:absolute;right:10px;top:10px;padding:4px 6px;border-radius:6px;background:var(--orange-soft);color:var(--orange);font-size:10px}.entry.disabled{opacity:.6;background:#f9fbfd}.entry.disabled:after{content:"";position:absolute;inset:0;background:transparent}.task-strip{margin-top:18px;border-top:1px solid var(--line);padding-top:16px}.task-row{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid var(--line);border-radius:11px;padding:12px;margin-bottom:8px}.task-dot{width:9px;height:9px;border-radius:50%;background:var(--orange);flex:0 0 auto}.task-main{flex:1;min-width:0}.task-main strong{font-size:13px}.task-main p{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:4px 0 0}.task-row button{background:#f0f5fc;color:var(--blue);border-radius:7px;padding:8px 10px;font-size:11px}
.toolbar{display:flex;justify-content:space-between;gap:10px;align-items:center;margin:18px 0 12px}.toolbar .subtle{font-size:12px;color:var(--muted)}.search-row{display:flex;gap:8px;margin:12px 0}.search-row input{flex:1;min-width:0;height:44px;border:1px solid var(--line);border-radius:10px;padding:0 12px;background:#fff;outline:none}.search-row button{height:44px;padding:0 14px;border-radius:10px;background:var(--blue);color:#fff;font-weight:700;font-size:12px}.list{display:grid;gap:10px}.list-card{background:#fff;border:1px solid var(--line);border-radius:13px;padding:15px}.list-top{display:flex;justify-content:space-between;gap:10px}.list-title{font-size:14px;font-weight:800}.list-meta{font-size:11px;color:var(--muted);margin-top:5px;line-height:1.45}.status{display:inline-flex;align-items:center;padding:4px 7px;border-radius:6px;font-size:10px;font-weight:700;white-space:nowrap}.status.good{color:var(--green);background:var(--green-soft)}.status.wait{color:var(--orange);background:var(--orange-soft)}.status.bad{color:var(--red);background:var(--red-soft)}.status.blue{color:var(--blue);background:var(--blue-soft)}.list-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:13px 0}.list-grid div{background:#f7f9fc;border-radius:8px;padding:8px}.list-grid span{display:block;color:var(--muted);font-size:10px}.list-grid strong{display:block;font-size:12px;margin-top:3px}.card-actions{display:flex;justify-content:flex-end;gap:8px;border-top:1px solid #edf1f5;padding-top:11px}.btn{min-height:44px;border-radius:9px;padding:0 14px;font-size:12px;font-weight:700}.btn.primary{background:var(--blue);color:#fff}.btn.secondary{background:#eef4fb;color:var(--blue)}.btn.ghost{background:#fff;border:1px solid var(--line);color:var(--muted)}.btn.danger{background:var(--red-soft);color:var(--red)}.btn:disabled{opacity:.45;cursor:not-allowed}
.work-card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:16px}.work-card+.work-card{margin-top:10px}.work-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.work-head h2{font-size:16px;margin:0}.work-head p{font-size:11px;color:var(--muted);margin:5px 0 0;line-height:1.5}.progress{height:8px;background:#edf2f7;border-radius:99px;overflow:hidden;margin:14px 0 8px}.progress span{display:block;height:100%;background:var(--green);border-radius:99px}.progress-line{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}.scan-box{display:flex;gap:8px;margin:14px 0}.scan-box input{flex:1;min-width:0;height:48px;border:2px solid #cbd8e6;border-radius:10px;padding:0 13px;outline:none}.scan-box input:focus{border-color:var(--blue)}.scan-box button{width:50px;border-radius:10px;background:var(--blue);color:#fff;font-size:20px}.chips{display:flex;flex-wrap:wrap;gap:6px}.chip{font-size:10px;background:#f1f5f8;color:var(--muted);padding:6px 8px;border-radius:7px}.chip.blue{background:var(--blue-soft);color:var(--blue)}.chip.green{background:var(--green-soft);color:var(--green)}.chip.orange{background:var(--orange-soft);color:var(--orange)}.chip.red{background:var(--red-soft);color:var(--red)}.detail-list{display:grid;gap:8px;margin-top:12px}.detail-row{display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid #edf1f5;padding-bottom:8px;font-size:12px}.detail-row span{color:var(--muted)}.detail-row strong{text-align:right;word-break:break-word}.scan-result{margin-top:10px;padding:10px 11px;border-radius:9px;background:var(--green-soft);color:var(--green);font-size:12px;line-height:1.45}.scan-result.error{background:var(--red-soft);color:var(--red)}.scan-result.info{background:var(--blue-soft);color:var(--blue)}.table-wrap{overflow:auto;margin-top:12px;border:1px solid var(--line);border-radius:10px}.table{width:100%;border-collapse:collapse;min-width:680px}.table th,.table td{font-size:11px;text-align:left;padding:10px;border-bottom:1px solid #edf1f5;white-space:nowrap}.table th{color:var(--muted);font-weight:700;background:#f8fafc}.table tr:last-child td{border-bottom:0}.empty{text-align:center;padding:34px 14px;color:var(--muted);font-size:12px}.bottom-bar{position:sticky;bottom:0;background:rgba(243,246,250,.96);padding:12px 0 2px;margin-top:16px}.bottom-bar .btn{width:100%;height:48px}.modal-backdrop{position:fixed;inset:0;background:rgba(16,24,40,.42);display:grid;place-items:end center;z-index:30;padding:14px}.modal{background:#fff;width:min(520px,100%);border-radius:16px;padding:20px;box-shadow:var(--shadow-modal);max-height:88vh;overflow:auto}.modal h2{font-size:17px;margin:0 0 13px}.modal p{font-size:12px;color:var(--muted);line-height:1.5}.modal-actions{display:flex;gap:8px;margin-top:18px}.modal-actions .btn{flex:1}.toast{position:fixed;left:50%;bottom:24px;z-index:50;transform:translateX(-50%);background:#17324d;color:#fff;padding:11px 14px;border-radius:9px;font-size:12px;box-shadow:var(--shadow);max-width:min(92vw,420px);text-align:center}.toast.good{background:var(--green)}.toast.bad{background:var(--red)}.toast.info{background:var(--blue-dark)}
.profile-grid{display:grid;gap:10px}.profile-line{display:flex;justify-content:space-between;padding:13px 0;border-bottom:1px solid var(--line);font-size:13px}.profile-line span{color:var(--muted)}.profile-line strong{text-align:right}.lang-select{display:flex;gap:6px}.lang-select button{padding:7px 9px;border-radius:7px;background:#f0f4f8;color:var(--muted);font-size:11px}.lang-select button.active{background:var(--blue-soft);color:var(--blue);font-weight:700}
@media(max-width:640px){body{max-width:100%}.content{padding:14px}.topbar{padding:10px 14px}.brand-sub{display:none}.welcome{padding:17px}.welcome-name{font-size:21px}.metrics{gap:7px}.metric{padding:11px 9px}.metric-label{font-size:10px}.metric-value{font-size:23px}.entry-grid{grid-template-columns:repeat(2,1fr)}.entry{min-height:102px;padding:13px}.entry h3{font-size:13px;margin-top:9px}.list-grid{grid-template-columns:repeat(2,1fr)}.toolbar{align-items:flex-start;flex-direction:column}.toolbar .btn{width:100%}.page-heading h1{font-size:19px}.table-wrap{margin-left:-2px;margin-right:-2px}}
@media(min-width:641px){.content{padding-top:26px}.login-wrap{padding:40px}.modal-backdrop{align-items:center}}
.password-wrap{position:relative}.password-wrap input{padding-right:68px}.password-wrap button{position:absolute;right:6px;top:6px;height:34px;padding:0 9px;border-radius:7px;background:#eef4fb;color:var(--blue);font-size:11px;font-weight:700}
.photo-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:14px}.photo-step{min-height:126px;border:1px solid var(--line);border-radius:11px;background:#f8fafc;padding:12px;display:flex;flex-direction:column;justify-content:space-between}.photo-step.done{background:var(--green-soft);border-color:#a9dfd1}.photo-preview{height:52px;border-radius:8px;background:#e8eef5;display:grid;place-items:center;color:var(--muted);font-size:20px;overflow:hidden}.photo-preview img{width:100%;height:100%;object-fit:cover}.photo-step strong{font-size:12px;margin:8px 0}.photo-step label{position:relative;display:grid;place-items:center;min-height:44px;text-align:center;padding:8px;border-radius:7px;background:var(--blue);color:#fff;font-size:11px;font-weight:700;cursor:pointer}.photo-step label:focus-within{outline:3px solid var(--focus-ring);outline-offset:2px}.photo-step input{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer}@media(max-width:360px){.photo-grid{grid-template-columns:1fr}}
</style>
<style id="ios-work-surface">
:root{
--blue:#0a66d8;
--blue-dark:#084faa;
--blue-soft:#eaf3ff;
--ink:#475467;
--muted:#65758b;
--bg:#f2f4f7;
--surface:#fff;
--line:#e4e7ec;
--green:#087a62;
--green-soft:#e8f7f2;
--orange:#a75b0b;
--orange-soft:#fff3e3;
--red:#b42318;
--red-soft:#fff0ee;
--shadow-soft:0 2px 8px rgba(16,24,40,.06);
--shadow:0 8px 24px rgba(16,24,40,.08);
--shadow-modal:0 20px 48px rgba(16,24,40,.18);
--radius:14px;
}
html{background:#e7ebf0}
body{width:100%;max-width:520px;font-size:15px;line-height:1.45;overflow-x:hidden}
.app{min-height:calc(100dvh - var(--safe-top) - var(--safe-bottom));background:var(--bg)}
button,input,select{font-size:16px}
button{transition:background-color .16s ease,transform .16s ease,opacity .16s ease}
button:active:not(:disabled){transform:scale(.98)}
button:disabled{opacity:.42}
.topbar{top:0;min-height:60px;padding:8px 16px;background:rgba(255,255,255,.96);border-color:var(--line);box-shadow:none;backdrop-filter:saturate(160%) blur(16px)}
.topbar-row{min-height:44px;gap:10px}
.page-heading{flex:1;gap:8px;overflow:hidden}
.page-heading>div{min-width:0}
.page-heading h1{font-size:19px;line-height:1.2;font-weight:750;letter-spacing:0;white-space:normal;overflow-wrap:anywhere}
.page-heading p{font-size:12px;line-height:1.25;white-space:normal;overflow-wrap:anywhere}
.back-btn{display:grid;place-items:center;background:transparent;color:var(--blue);font-size:31px;font-weight:400}
.top-actions{flex:0 0 auto;gap:6px}
.avatar{border:1px solid #d8e8ff;background:var(--blue-soft);font-size:14px}
.lang-pill{gap:0;padding:2px;border-radius:10px;background:#eef1f5}
.lang-pill button{min-width:44px;padding:0 7px;border-radius:8px;color:#596579;font-size:12px}
.lang-pill button.active{color:var(--blue);background:var(--surface);box-shadow:0 1px 3px rgba(16,24,40,.09)}
.content{width:100%;max-width:680px;padding:18px 16px calc(24px + var(--safe-bottom))}
.login-wrap{min-height:calc(100dvh - var(--safe-top) - var(--safe-bottom));padding:24px 18px}
.login-card{width:min(420px,100%);padding:26px 22px;border-color:var(--line);border-radius:16px;box-shadow:var(--shadow)}
.login-card .brand{margin-bottom:24px}
.brand-mark{width:44px;height:44px;border-radius:12px}
.brand-title{font-size:17px;letter-spacing:0}
.brand-sub{font-size:12px}
.login-card h1{font-size:25px;line-height:1.22;letter-spacing:0}
.login-card .intro{font-size:14px;line-height:1.55;margin-bottom:20px}
.field{gap:7px;margin-bottom:15px}
.field label{font-size:13px;color:#475467}
.field input,.field select{height:50px;border-color:#d0d5dd;border-radius:11px;padding-inline:14px;font-size:16px}
.field input:focus,.field select:focus{border-color:var(--blue);box-shadow:0 0 0 3px var(--focus-ring)}
.login-btn{height:50px;border-radius:11px;font-size:16px}
.demo-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.demo-row button{min-width:0;padding:8px;border-color:var(--line);background:#f8fafc;color:#475467;font-size:12px;white-space:normal}
.demo-tip{font-size:12px}
.welcome{padding:18px;border-radius:16px;background:var(--blue);box-shadow:0 7px 18px rgba(16,24,40,.12)}
.welcome-kicker{font-size:13px}
.welcome-name{font-size:24px;letter-spacing:0}
.welcome-meta{font-size:12px;line-height:1.4}
.shift{min-height:32px;padding:7px 9px;border:1px solid rgba(255,255,255,.22);border-radius:9px;background:rgba(255,255,255,.12);font-size:11px;white-space:normal;text-align:center}
.metrics{gap:8px;margin:12px 0 20px}
.metric{min-height:88px;padding:12px 10px;border-color:var(--line);border-radius:12px;box-shadow:none}
.metric-label{font-size:12px;line-height:1.35;overflow-wrap:anywhere}
.metric-value{font-size:25px;line-height:1.05;margin-top:8px}
.section-title{margin:23px 2px 10px}
.section-title h2{font-size:17px;line-height:1.25}
.section-title span{font-size:12px}
.entry-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.entry{min-width:0;min-height:124px;padding:14px;border-color:var(--line);border-radius:14px;box-shadow:none}
.entry:hover{border-color:var(--line)}
.entry:active{background:#f8fafc}
.entry .entry-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:10px;background:var(--blue-soft);color:var(--blue);font-size:19px;font-weight:700}
.entry h3{font-size:15px;line-height:1.25;margin:12px 0 5px;overflow-wrap:anywhere}
.entry p{font-size:12px;line-height:1.4;overflow-wrap:anywhere}
.entry-grid .entry p{display:none}
.inbound-entry-grid{max-width:none}
.outbound-entry-grid{max-width:none}
.internal-entry-grid{max-width:none}
.section-title h2{font-size:15px;line-height:1.2;white-space:nowrap}
.section-title{margin:16px 2px 8px}
.page-heading h1{font-size:17px;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.page-heading p{display:none}
.entry-grid .entry{text-align:center;border-radius:10px}
.entry-grid .entry .entry-icon{margin-inline:auto}
.entry-grid .entry h3{font-size:12px;line-height:1.2;color:#667085;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.entry .badge{top:12px;right:12px;min-width:24px;text-align:center;border-radius:99px;font-size:11px}
.entry.merge-entry .entry-icon{background:var(--blue-soft);color:var(--blue)}
.task-strip{margin-top:22px;padding-top:18px;border-color:var(--line)}
.task-row{min-width:0;padding:12px 13px;border-color:var(--line);border-radius:12px;margin-bottom:8px;box-shadow:none}
.task-main strong{font-size:14px;overflow-wrap:anywhere}
.task-main p{font-size:12px;white-space:normal;overflow:visible;text-overflow:clip;line-height:1.35}
.task-row button{flex:0 0 auto;min-width:44px;min-height:44px;padding:0 12px;border-radius:10px;background:var(--blue-soft);font-size:12px;font-weight:700}
.toolbar{margin:4px 0 12px;min-height:32px}
.toolbar .subtle{font-size:13px}
.list{gap:10px}
.list-card,.work-card{border-color:var(--line);border-radius:14px;background:var(--surface);box-shadow:none}
.list-card{padding:15px}
.list-top{align-items:flex-start}
.list-title{font-size:15px;line-height:1.3;overflow-wrap:anywhere}
.list-meta{font-size:12px;line-height:1.5;overflow-wrap:anywhere}
.status{max-width:100%;padding:5px 8px;border-radius:99px;font-size:11px;line-height:1.25;white-space:normal;text-align:center;overflow-wrap:anywhere}
.list-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:13px 0}
.list-grid div{min-width:0;padding:9px;border-radius:9px;background:#f7f8fa}
.list-grid span{font-size:11px;line-height:1.25}
.list-grid strong{font-size:13px;line-height:1.35;overflow-wrap:anywhere}
.card-actions{gap:8px;padding-top:12px;border-color:var(--line);flex-wrap:wrap}
.btn{min-height:44px;border-radius:10px;padding:0 14px;font-size:13px}
.btn.primary{background:var(--blue)}
.btn.secondary{background:var(--blue-soft)}
.btn.ghost{border-color:var(--line);background:#fff;color:#475467}
.work-card{padding:16px}
.work-card+.work-card{margin-top:12px}
.work-head h2{font-size:17px;line-height:1.3}
.work-head p{font-size:12px;line-height:1.45;overflow-wrap:anywhere}
.detail-list{gap:0;margin-top:13px}
.detail-row{align-items:flex-start;min-height:42px;padding:10px 0;border-color:var(--line);font-size:13px;line-height:1.4}
.detail-row:last-child{border-bottom:0}
.detail-row span{flex:0 0 38%}
.detail-row strong{min-width:0;overflow-wrap:anywhere}
.scan-box{gap:8px;margin:16px 0 12px}
.scan-box input{height:50px;border:1px solid #b9c4d1;border-radius:11px;font-size:16px}
.scan-box button{width:50px;height:50px;border-radius:11px;background:var(--blue)}
.scan-result{padding:12px;border-radius:10px;font-size:13px}
.progress{height:7px;margin:16px 0 8px;background:#e9edf2}
.progress span{background:var(--blue)}
.progress-line{font-size:12px}
.chip{min-height:32px;font-size:11px}
.search-row{gap:8px;margin:8px 0 14px}
.search-row input,.search-row button{height:48px}
.table-wrap{margin-top:13px;border-color:var(--line);border-radius:11px;background:#fff;-webkit-overflow-scrolling:touch}
.table{min-width:620px}
.table th,.table td{padding:12px 10px;border-color:var(--line);font-size:12px}
.table th{background:#f7f8fa}
.empty{padding:36px 16px;font-size:13px}
.photo-grid{grid-template-columns:1fr;gap:10px}
.photo-step{min-height:150px;padding:13px;border-color:var(--line);border-radius:12px;background:#f8fafc}
.photo-preview{height:70px;border-radius:9px;background:#e9edf2}
.photo-step strong{font-size:13px;line-height:1.35;overflow-wrap:anywhere}
.photo-step label{border-radius:10px;font-size:13px}
.bottom-bar{z-index:12;margin:18px -16px calc(-24px - var(--safe-bottom));padding:12px 16px calc(12px + var(--safe-bottom));background:rgba(242,244,247,.96);border-top:1px solid var(--line);backdrop-filter:saturate(150%) blur(14px)}
.bottom-bar>div{align-items:stretch;flex-wrap:wrap}
.bottom-bar .btn{height:50px;min-width:120px;font-size:14px}
.modal-backdrop{padding:max(12px,var(--safe-top)) max(12px,var(--safe-right)) calc(12px + var(--safe-bottom)) max(12px,var(--safe-left));background:rgba(16,24,40,.46)}
.modal{padding:20px;border-radius:16px;box-shadow:var(--shadow-modal)}
.modal h2{font-size:18px}
.modal p{font-size:14px}
.toast{bottom:calc(20px + var(--safe-bottom));border-radius:11px;box-shadow:var(--shadow)}
.password-wrap button,.lang-select button{min-width:44px;min-height:44px}
@media (max-width:359px){
.topbar{padding-inline:12px}
.top-actions{gap:4px}
.topbar-row:has(.back-btn) .avatar{display:none}
.lang-pill button{min-width:44px;padding-inline:5px}
.page-heading h1{font-size:17px}
.content{padding-inline:12px}
.welcome{padding:15px}
.welcome-name{font-size:21px}
.metrics{gap:6px}
.metric{padding:10px 7px}
.metric-label{font-size:11px}
.metric-value{font-size:22px}
.entry-grid{max-width:288px;margin-inline:auto;gap:8px}
.inbound-entry-grid,.outbound-entry-grid{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));margin-inline:0}
.inbound-entry-grid .entry,.outbound-entry-grid .entry{min-height:92px;padding:10px 6px}
.internal-entry-grid{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));margin-inline:0}
.internal-entry-grid .entry{min-height:92px;padding:10px 6px}
.entry{min-height:92px;padding:11px}
.entry h3{font-size:14px}
.bottom-bar{margin-inline:-12px;padding-inline:12px}
}
@media (min-width:360px) and (max-width:639px){
.entry-grid{max-width:348px;margin-inline:auto;gap:8px}
.inbound-entry-grid{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));margin-inline:0}
.inbound-entry-grid .entry{min-height:94px;padding:11px 8px}
.outbound-entry-grid{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));margin-inline:0}
.outbound-entry-grid .entry{min-height:94px;padding:11px 8px}
.internal-entry-grid{max-width:none;grid-template-columns:repeat(3,minmax(0,1fr));margin-inline:0}
.internal-entry-grid .entry{min-height:94px;padding:11px 8px}
.entry{min-height:94px;padding:12px}
.entry h3{margin-top:10px}
}
@media (min-width:390px){
.photo-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:640px){
body{max-width:700px}
.content{padding:24px 22px calc(28px + var(--safe-bottom))}
.topbar{padding-inline:22px}
.entry-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
.bottom-bar{margin-inline:-22px;padding-inline:22px}
}
@media (min-width:768px){
body{max-width:760px}
.content{max-width:720px}
.list-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (hover:hover) and (pointer:fine){
.entry:hover,.list-card:hover{border-color:#cdd5df;background:#fcfcfd}
.btn.primary:hover,.login-btn:hover,.scan-box button:hover{background:var(--blue-dark)}
}
@media (prefers-reduced-motion:reduce){
*,*::before,*::after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}
}
</style>
<style id="ios-icon-system">
.entry .entry-icon{position:relative;isolation:isolate;width:40px;height:40px;border-radius:10px;font-size:0;line-height:0;box-shadow:0 2px 5px rgba(16,24,40,.07),inset 0 0 0 1px rgba(16,24,40,.025);transition:transform .16s cubic-bezier(.2,.8,.2,1),box-shadow .16s ease}
.entry .entry-icon::after{content:"";position:absolute;z-index:-1;inset:1px;border-radius:9px;box-shadow:inset 0 1px 0 rgba(255,255,255,.82);pointer-events:none}
.entry .entry-icon svg{width:22px;height:22px;display:block;stroke:currentColor;stroke-width:1.75;fill:none;stroke-linecap:round;stroke-linejoin:round}
.entry .entry-icon .icon-fill{fill:currentColor;stroke:none;opacity:.11}
.entry:active .entry-icon{transform:scale(.94);box-shadow:0 1px 2px rgba(16,24,40,.08),inset 0 0 0 1px rgba(16,24,40,.04)}
.entry.receive-entry .entry-icon{background:var(--orange-soft);color:var(--orange)}
.entry.pallet-entry .entry-icon{background:var(--blue-soft);color:var(--blue)}
.entry.pick-entry .entry-icon{background:var(--orange-soft);color:var(--orange)}
.entry.review-entry .entry-icon{background:var(--green-soft);color:var(--green)}
.entry.handover-entry .entry-icon{background:var(--green-soft);color:var(--green)}
.entry.inventory-entry .entry-icon{background:#e8f5f3;color:#26766f}
.entry.relabel-entry .entry-icon{background:#eef1ff;color:#4f5fc7}
.entry.exception-entry .entry-icon{background:var(--red-soft);color:var(--red)}
.entry.more-entry .entry-icon{background:#eef1f5;color:#475467}
.outbound-entry-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
.back-btn svg{width:21px;height:21px;display:block;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
.scan-box button{display:grid;place-items:center}
.scan-box button svg{width:22px;height:22px;display:block;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
.photo-preview svg{width:24px;height:24px;display:block;stroke:currentColor;stroke-width:1.7;fill:none;stroke-linecap:round;stroke-linejoin:round}
.photo-step.done .photo-preview{color:var(--green);background:#d9f2eb}
.photo-step.done .photo-preview svg{stroke-width:2}
.photo-capture{width:100%;height:50px;background:var(--blue)!important;color:#fff!important;border:0!important;border-radius:10px}
.photo-capture svg{width:20px;height:20px;display:block;flex:0 0 20px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
.photo-capture span{display:block;white-space:nowrap;letter-spacing:0}
.photo-add-tile:active{background:#f7f8fa!important}
.photo-add-tile:focus-visible{outline:3px solid var(--focus-ring);outline-offset:2px}
</style>
<style id="ios-final-report">
:root{
--ios-blue:#155eef;
--ios-blue-pressed:#004eeb;
--ios-blue-soft:#edf4ff;
--ios-bg:#f5f5f7;
--ios-surface:#fff;
--ios-surface-muted:#f9fafb;
--ios-ink:#1d1d1f;
--ios-secondary:#62626a;
--ios-tertiary:#85858d;
--ios-border:#e5e5ea;
--ios-green:#067a57;
--ios-green-soft:#e9f7f1;
--ios-orange:#a84f00;
--ios-orange-soft:#fff3e5;
--ios-red:#b42318;
--ios-red-soft:#fff0ee;
--ios-indigo:#4f5fc7;
--ios-indigo-soft:#eef0ff;
--ios-focus:rgba(21,94,239,.28);
--blue:var(--ios-blue);
--blue-dark:var(--ios-blue-pressed);
--blue-soft:var(--ios-blue-soft);
--ink:var(--ios-ink);
--muted:var(--ios-secondary);
--bg:var(--ios-bg);
--surface:var(--ios-surface);
--line:var(--ios-border);
--green:var(--ios-green);
--green-soft:var(--ios-green-soft);
--orange:var(--ios-orange);
--orange-soft:var(--ios-orange-soft);
--red:var(--ios-red);
--red-soft:var(--ios-red-soft);
--focus-ring:var(--ios-focus);
--shadow-soft:0 2px 8px rgba(16,24,40,.05);
--shadow:0 10px 30px rgba(16,24,40,.1);
--shadow-modal:0 22px 56px rgba(16,24,40,.2);
}
html{background:#eceef1}
body{color:var(--ios-ink);font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","PingFang SC","Noto Sans SC",Arial,sans-serif;font-feature-settings:"tnum" 1,"kern" 1;text-rendering:optimizeLegibility}
.app{min-height:100dvh;background:var(--ios-bg)}
button,input,select{min-height:44px;color:inherit;letter-spacing:0}
button{touch-action:manipulation}
button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--ios-focus);outline-offset:2px}
button:disabled{cursor:not-allowed;opacity:.42;transform:none!important}
.topbar{min-height:62px;padding:8px 16px;background:rgba(255,255,255,.94);border-bottom:1px solid rgba(60,60,67,.15);backdrop-filter:saturate(180%) blur(18px);-webkit-backdrop-filter:saturate(180%) blur(18px)}
.topbar-row{min-height:45px}
.page-heading{gap:6px}
.page-heading h1{color:var(--ios-ink);font-size:17px;font-weight:700;line-height:1.2}
.page-heading p{display:none}
.back-btn{border-radius:10px;color:var(--ios-blue);background:transparent}
.back-btn:active{background:var(--ios-blue-soft)}
.lang-pill{padding:2px;border-radius:10px;background:#edeef1}
.lang-pill button{min-width:44px;min-height:44px;padding:0 7px;border-radius:8px;color:var(--ios-secondary);font-size:12px}
.lang-pill button.active{color:var(--ios-blue);background:#fff;box-shadow:0 1px 4px rgba(16,24,40,.1)}
.avatar{border:1px solid #d7e5ff;background:var(--ios-blue-soft);color:var(--ios-blue);box-shadow:inset 0 1px 0 rgba(255,255,255,.8)}
.content{padding:18px 16px calc(28px + env(safe-area-inset-bottom,0px))}
.login-wrap{background:var(--ios-surface);padding:20px 18px calc(20px + env(safe-area-inset-bottom,0px))}
.login-card{border:0;border-radius:0;background:transparent;box-shadow:none}
.brand{gap:12px}.brand-mark{background:var(--ios-blue);box-shadow:0 4px 12px rgba(16,24,40,.14)}
.brand-title{color:var(--ios-ink);font-weight:760}.brand-sub{color:var(--ios-secondary)}
.login-card h1{color:var(--ios-ink);font-size:26px;font-weight:760}
.login-card .intro{color:var(--ios-secondary)}
.field label{color:#414149;font-weight:650}
.field input,.field select,.search-row input,.scan-box input{border:1px solid #c7c7cc;background:#fff;color:var(--ios-ink);box-shadow:inset 0 1px 0 rgba(0,0,0,.015)}
.field input::placeholder,.search-row input::placeholder,.scan-box input::placeholder{color:#777780;opacity:1}
.field input:focus,.field select:focus,.search-row input:focus,.scan-box input:focus{border-color:var(--ios-blue);box-shadow:0 0 0 3px var(--ios-focus)}
.login-error{color:var(--ios-red);font-size:13px}
.login-btn,.btn.primary,.search-row button,.scan-box button{background:var(--ios-blue);color:#fff;box-shadow:0 2px 5px rgba(16,24,40,.12)}
.login-btn:active,.btn.primary:active,.search-row button:active,.scan-box button:active{background:var(--ios-blue-pressed)}
.demo-row button{border:1px solid var(--ios-border);background:var(--ios-surface-muted);color:#414149}
.demo-row button:active{background:#eeeef0}
.welcome{border-radius:16px;background:var(--ios-blue);box-shadow:0 10px 24px rgba(16,24,40,.13)}
.welcome-kicker{color:rgba(255,255,255,.8)}
.welcome-name{font-weight:780}.welcome-meta{color:rgba(255,255,255,.86)}
.shift{border-color:rgba(255,255,255,.28);background:rgba(255,255,255,.12);color:#fff}
.metrics{margin:12px 0 18px}
.metric{border:1px solid var(--ios-border);background:var(--ios-surface);box-shadow:0 1px 2px rgba(16,24,40,.025)}
.metric-label{color:var(--ios-secondary)}
.metric-value{font-weight:760}.metric.warn .metric-value{color:var(--ios-orange)}.metric.good .metric-value{color:var(--ios-green)}.metric.blue .metric-value{color:var(--ios-blue)}
.section-title{margin:18px 2px 8px}.section-title h2{color:#3a3a40;font-size:15px;font-weight:730}
.entry-grid{gap:8px}
.entry{border:1px solid var(--ios-border);border-radius:10px;background:var(--ios-surface);box-shadow:0 1px 2px rgba(16,24,40,.025);transition:background-color .16s ease,border-color .16s ease,transform .16s cubic-bezier(.2,.8,.2,1)}
.entry:active{border-color:#d2d2d7;background:#fafafa;transform:scale(.975)}
.entry-grid .entry h3{color:#5b5b63;font-size:12px;font-weight:680}
.entry .entry-icon{box-shadow:0 3px 8px rgba(16,24,40,.07),inset 0 0 0 1px rgba(16,24,40,.025)}
.entry.relabel-entry .entry-icon{background:var(--ios-indigo-soft);color:var(--ios-indigo)}
.toolbar{margin:2px 0 12px}.toolbar .subtle{color:var(--ios-secondary);font-size:13px}
.list{gap:10px}
.list-card,.work-card{border:1px solid var(--ios-border);border-radius:12px;background:var(--ios-surface);box-shadow:0 1px 2px rgba(16,24,40,.02)}
.list-card{padding:15px}.work-card{padding:16px}
.list-title,.work-head h2{color:var(--ios-ink);font-weight:720}
.list-meta,.work-head p{color:var(--ios-secondary)}
.list-grid div{background:var(--ios-surface-muted)}
.list-grid span{color:var(--ios-tertiary)}
.list-grid strong{color:#34343a;font-weight:680}
.status{border-radius:6px;font-weight:680}
.status.good{color:var(--ios-green);background:var(--ios-green-soft)}
.status.wait{color:var(--ios-orange);background:var(--ios-orange-soft)}
.status.bad{color:var(--ios-red);background:var(--ios-red-soft)}
.status.blue{color:var(--ios-blue);background:var(--ios-blue-soft)}
.card-actions{border-color:var(--ios-border)}
.btn{border-radius:10px;font-weight:680}
.btn.secondary{background:var(--ios-blue-soft);color:var(--ios-blue)}
.btn.ghost{border:1px solid var(--ios-border);background:#fff;color:#4f4f57}
.btn.danger{background:var(--ios-red-soft);color:var(--ios-red)}
.btn.secondary:active,.btn.ghost:active{background:#eeeef1}
.detail-row{border-color:var(--ios-border)}
.detail-row span{color:var(--ios-secondary)}
.detail-row strong{color:#303036;font-weight:660}
.progress{height:7px;background:#e8e8ed}.progress span{background:var(--ios-blue)}
.progress-line{color:var(--ios-secondary)}.progress-line strong{color:#45454d}
.scan-box button{border-radius:11px}
.scan-result{border:1px solid transparent}
.scan-result.info{border-color:#d7e5ff;background:var(--ios-blue-soft);color:#174ea6}
.scan-result.good{border-color:#ccebdd;background:var(--ios-green-soft);color:var(--ios-green)}
.scan-result.error{border-color:#f2cbc7;background:var(--ios-red-soft);color:var(--ios-red)}
.chip{border-radius:6px;background:#f0f0f3;color:var(--ios-secondary)}
.photo-grid{gap:10px}.photo-step{border-color:var(--ios-border);border-radius:12px;background:var(--ios-surface-muted)}
.photo-step.done{border-color:#ccebdd;background:var(--ios-green-soft)}
.photo-preview{background:#eceef1}.photo-step label{background:var(--ios-blue);border-radius:10px}
.photo-add-tile{border-color:#c7c7cc!important;border-radius:10px!important;color:#7a7a83!important}
.bottom-bar{border-top:1px solid rgba(60,60,67,.15);background:rgba(245,245,247,.94);padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));backdrop-filter:saturate(180%) blur(18px);-webkit-backdrop-filter:saturate(180%) blur(18px)}
.modal-backdrop{background:rgba(0,0,0,.38)}
.modal{border-radius:16px 16px 12px 12px;background:#fff;box-shadow:var(--shadow-modal)}
.modal h2{color:var(--ios-ink);font-weight:740}.modal p{color:var(--ios-secondary)}
.toast{width:max-content;max-width:min(88vw,390px);border-radius:10px;background:#2c2c2e;color:#fff;box-shadow:0 8px 24px rgba(0,0,0,.2)}
.toast.good{background:#176b52}.toast.bad{background:#9f2d24}.toast.info{background:#2c2c2e}
.empty{color:var(--ios-secondary)}
.inventory-results{display:grid;gap:10px}
.inventory-card{border:1px solid var(--ios-border);border-radius:12px;background:#fff;padding:15px;box-shadow:0 1px 2px rgba(16,24,40,.02)}
.inventory-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding-bottom:12px;border-bottom:1px solid var(--ios-border)}
.inventory-card-title{min-width:0;color:var(--ios-ink);font-size:15px;font-weight:720;line-height:1.35;overflow-wrap:anywhere}
.inventory-card-customer{margin-top:4px;color:var(--ios-secondary);font-size:12px;line-height:1.4;overflow-wrap:anywhere}
.inventory-facts{display:grid;grid-template-columns:minmax(0,1fr) minmax(88px,.55fr);gap:8px;margin-top:12px}
.inventory-fact{min-width:0;padding:9px 10px;border-radius:9px;background:var(--ios-surface-muted)}
.inventory-fact span{display:block;color:var(--ios-tertiary);font-size:11px;line-height:1.3}
.inventory-fact strong{display:block;margin-top:4px;color:#34343a;font-size:13px;line-height:1.35;overflow-wrap:anywhere}
.inventory-fact.qty strong{font-size:18px;color:var(--ios-blue)}
.profile-line{border-color:var(--ios-border)}.profile-line span{color:var(--ios-secondary)}
.lang-select{flex-wrap:wrap}.lang-select button{border:1px solid transparent;background:#f0f0f3;color:#4f4f57}.lang-select button.active{border-color:#d7e5ff;background:var(--ios-blue-soft);color:var(--ios-blue)}
@media(max-width:359px){
.topbar{padding-inline:12px}.content{padding-inline:12px}
.topbar-row{gap:4px}.page-heading{gap:2px}.page-heading h1{font-size:16px}
.topbar-row:has(.back-btn) .avatar{display:none}
.lang-pill button{min-width:42px;padding-inline:4px}
.welcome{padding:15px}.shift{max-width:88px}
.metric{min-height:84px;padding:10px 7px}.metric-label{font-size:11px}.metric-value{font-size:23px}
.inbound-entry-grid .entry,.outbound-entry-grid .entry,.internal-entry-grid .entry{padding:9px 5px}
.entry .entry-icon{width:38px;height:38px}.entry-grid .entry h3{font-size:11px}
.list-card,.work-card{padding:14px}.card-actions .btn{flex:1 1 auto}
.inventory-facts{grid-template-columns:1fr 82px}
.bottom-bar{margin-inline:-12px;padding-inline:12px}
}
@media(max-width:480px){
.topbar-row:has(.back-btn) .avatar{display:none}
}
@media(min-width:390px){
.photo-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(min-width:640px){
body{max-width:720px;box-shadow:0 0 0 1px rgba(16,24,40,.06),0 18px 60px rgba(16,24,40,.12)}
.login-card{padding:28px;border:1px solid var(--ios-border);border-radius:16px;background:#fff;box-shadow:var(--shadow)}
.modal-backdrop{place-items:center}.modal{max-width:440px;border-radius:16px}
.inventory-results{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(hover:hover) and (pointer:fine){
.entry:hover,.list-card:hover,.inventory-card:hover{border-color:#d1d1d6;background:#fcfcfd}
.btn.primary:hover,.login-btn:hover,.search-row button:hover,.scan-box button:hover{background:var(--ios-blue-pressed)}
}
@media(prefers-reduced-motion:reduce){
*,*::before,*::after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}
}
</style>
</style><style id="pallet-list-toolbar">
.pallet-list-toolbar{display:flex!important;align-items:center;justify-content:space-between;flex-direction:row!important}
.pallet-list-toolbar .generate-pallet-label{min-height:32px;padding:0;color:var(--blue);background:transparent;border:0}
.pallet-list-footer>div{display:flex;gap:8px;width:100%}
.pallet-list-footer .btn{flex:1}
.pallet-list-table{min-width:0;table-layout:fixed}
.pallet-list-table th,.pallet-list-table td{padding:8px 6px;font-size:11px;white-space:normal;overflow-wrap:anywhere;vertical-align:middle}
.pallet-list-table tbody td:nth-child(-n+3){font-size:12px}
.pallet-list-table th:nth-child(1),.pallet-list-table td:nth-child(1){width:35%}
.pallet-list-table th:nth-child(2),.pallet-list-table td:nth-child(2){width:25%}
.pallet-list-table th:nth-child(3),.pallet-list-table td:nth-child(3){width:15%;text-align:left}
.pallet-list-table th:nth-child(4),.pallet-list-table td:nth-child(4){width:25%}
.pallet-list-table .status{display:inline-block;max-width:100%;padding:4px 6px;font-size:10px;line-height:1.25;white-space:normal}
.pallet-order-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:3px;padding:3px;border-radius:10px;background:#e9e9ee}
.pallet-order-tabs button{min-width:0;min-height:40px;padding:6px 4px;border:0;border-radius:8px;background:transparent;color:#55555d;font-size:12px;font-weight:680}
.pallet-order-tabs button.active{background:#fff;color:var(--ios-blue);box-shadow:0 1px 4px rgba(0,0,0,.08)}
.pallet-order-tabs button:focus-visible{outline:2px solid var(--ios-blue);outline-offset:1px}
.fba-pallet-options{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:12px}
.fba-pallet-choice{min-width:0;min-height:44px;padding:8px;border:1px solid var(--ios-border);border-radius:9px;background:#fff;color:var(--ios-secondary);font-size:11px;font-weight:680;overflow-wrap:anywhere}
.fba-pallet-choice.selected{border-color:#a9c8ff;background:var(--ios-blue-soft);color:var(--ios-blue)}
.fba-pallet-choice:disabled{cursor:not-allowed;background:#f3f4f6;color:#8b8d94;opacity:.68}
.fba-pallet-choice:focus-visible{outline:2px solid var(--ios-blue);outline-offset:1px}
</style></head>
<body>
<div id="app" class="app"></div>
<div id="modal-root"></div>
<div id="toast-root"></div>
<script>
/*
THESIS: A warehouse operator should see the next physical move before the paperwork.
OWN-WORLD: A calm blue work surface, white task sheets, and status colors that read at a glance.
STORY: Sign in, unload a container, consolidate same-destination freight, then move to outbound work.
FIRST VIEWPORT: Workbench metrics lead directly to the three inbound actions; scan controls stay thumb-ready.
FORM: Mobile task board with anchored action bars, chosen to extend the domestic PDA's direct-action grammar.
*/
const LANGS=['zh','en','es'];
const I18N={
zh:{brand:'柏悦物流 PDA',sub:'海外仓移动作业平台',loginTitle:'海外仓作业登录',loginIntro:'登录后处理卸柜、打托、出库和库存查询。',username:'工号',password:'密码',login:'登录',show:'显示',hide:'隐藏',demo:'演示账号',welcome:'欢迎回来',shift:'白班 · 作业中',warehouse:'洛杉矶海外仓',profile:'个人中心',logout:'退出登录',today:'今日',unload:'待卸货集装箱',receivedToday:'今日已入库',outboundToday:'今日待出库',inbound:'入库作业',outbound:'出库作业',internal:'库内作业',plan:'入库计划',planDesc:'按计划拆柜并核对托数',receive:'拆柜收货',receiveDesc:'拍摄 3 张作业照片完成收货',pallet:'打托',palletDesc:'按计划托数装对应货物',pick:'出库拣货',pickDesc:'扫描 SKU 或箱号完成拣货',review:'复核出库',reviewDesc:'核对数量后完成复核',handover:'出库交接',handoverDesc:'确认车辆和货物交接',inventory:'库存查询',inventoryDesc:'查询 SKU、箱号和托盘',stocktake:'库存盘点',transfer:'移库作业',relabel:'换标作业',exception:'异常处理',more:'更多作业',demoOnly:'当前版本仅展示入口',task:'待处理任务',empty:'暂无待处理任务',choose:'选择',details:'详情',status:'状态',customer:'客户',destination:'目的地',destinationCode:'目的地代码',destinationAddress:'目的地地址',ticket:'票号',planNo:'入库计划',container:'集装箱',boxes:'箱',expected:'预计',received:'已收',arrived:'预计到仓',startReceive:'开始拆柜',scanBox:'扫描或输入箱号',scan:'扫码',normal:'正常',short:'短少',damaged:'破损',hold:'待处理',receiveProgress:'收货进度',receivedList:'已收箱明细',goPallet:'进入打托',newPallet:'新建托盘',currentPallet:'当前托盘',palletNo:'托盘号',items:'明细',pieces:'件数',addCargo:'扫描箱号或 SKU 加入托盘',remove:'移除',submitPallet:'提交打托',sameDest:'同目的地可跨票合托',lockedDest:'已锁定目的地',mergeOk:'已加入托盘',mergeBlocked:'该货物目的地不一致,不能合托',duplicate:'该箱号已收货',notFound:'未找到匹配货物',emptyPallet:'托盘还没有货物,不能提交',submitted:'打托已完成',trace:'明细追溯',back:'返回',next:'下一步',complete:'完成',search:'查询',clear:'清空',sku:'SKU',boxNo:'箱号',qty:'数量',operator:'操作员',role:'角色',shiftLabel:'班次',language:'语言',version:'版本',selectPlan:'请选择入库计划',scanContainer:'扫描柜号或输入柜号',unloadTask:'待卸柜任务',pickTask:'待出库任务',startPick:'开始拣货',pickProgress:'拣货进度',picked:'已拣',partial:'部分',skipped:'跳拣',pending:'待处理',confirmPick:'确认拣出',partialPick:'部分拣货',skipPick:'跳拣',undo:'撤销',submitPick:'提交拣货完成',reviewScan:'扫描出库单或 SKU',confirmReview:'完成复核',carrier:'承运商',driver:'司机',vehicle:'车辆',handoverDone:'确认交接',queryHint:'输入 SKU、箱号或托盘号',noResult:'没有找到匹配库存',inventoryStatus:'库存状态',inStock:'在库',inTransit:'在途',newPalletConfirm:'当前托盘尚未提交,确认新建托盘吗?',exitConfirm:'当前作业有未提交内容,确认退出吗?',yes:'确认',cancel:'取消',success:'操作成功',loginError:'工号或密码错误',required:'请输入内容',scanSuccess:'扫码成功',unknownScan:'未找到匹配编码',taskDone:'任务已完成',demoAccount:'演示账号:W001 / 123456',containerMeta:'柜号 {container} · {boxes} 箱',planMeta:'{customer} · {destinationCode}',palletMeta:'{count} 票货物 · {pieces} 件',inventoryMeta:'{customer} · {destinationCode}',unavailable:'暂未开放'},
en:{brand:'Baiyue PDA',sub:'Overseas warehouse operations',loginTitle:'Overseas warehouse sign in',loginIntro:'Unload containers, consolidate pallets, process outbound work and search inventory.',username:'Worker ID',password:'Password',login:'Sign in',show:'Show',hide:'Hide',demo:'Demo account',welcome:'Welcome back',shift:'Day shift · Active',warehouse:'Los Angeles Overseas WH',profile:'Profile',logout:'Sign out',today:'Today',unload:'Containers to unload',receivedToday:'Received today',outboundToday:'Outbound due',inbound:'Inbound',outbound:'Outbound',internal:'Warehouse',plan:'Inbound plans',planDesc:'Unload and reconcile by plan',receive:'Unload & receive',receiveDesc:'Capture 3 operation photos to receive',pallet:'Palletize',palletDesc:'Load freight by planned pallet count',pick:'Outbound picking',pickDesc:'Scan SKU or carton to pick',review:'Outbound review',reviewDesc:'Verify quantity before release',handover:'Outbound handover',handoverDesc:'Confirm vehicle and freight',inventory:'Inventory search',inventoryDesc:'Search SKU, carton or pallet',more:'More operations',demoOnly:'Demo entry only in this version',task:'Open tasks',empty:'No open tasks',choose:'Open',details:'Details',status:'Status',customer:'Customer',destination:'Destination',destinationCode:'Destination code',destinationAddress:'Destination address',ticket:'Ticket',planNo:'Inbound plan',container:'Container',boxes:'Cartons',expected:'Expected',received:'Received',arrived:'ETA',startReceive:'Start unload',scanBox:'Scan or enter carton ID',scan:'Scan',normal:'Normal',short:'Short',damaged:'Damaged',hold:'Hold',receiveProgress:'Receiving progress',receivedList:'Received cartons',goPallet:'Continue to palletize',newPallet:'New pallet',currentPallet:'Current pallet',palletNo:'Pallet ID',items:'Lines',pieces:'Pieces',addCargo:'Scan carton or SKU to add',remove:'Remove',submitPallet:'Submit pallet',sameDest:'Same destination can merge across tickets',lockedDest:'Destination locked',mergeOk:'Added to pallet',mergeBlocked:'Destination mismatch. Cannot merge.',duplicate:'This carton was already received',notFound:'No matching freight found',emptyPallet:'Add freight before submitting the pallet',submitted:'Pallet completed',trace:'Traceable lines',back:'Back',next:'Next',complete:'Complete',search:'Search',clear:'Clear',sku:'SKU',boxNo:'Carton ID',qty:'Qty',operator:'Operator',role:'Role',shiftLabel:'Shift',language:'Language',version:'Version',selectPlan:'Select an inbound plan',scanContainer:'Scan or enter container ID',unloadTask:'Containers to unload',pickTask:'Outbound tasks',startPick:'Start picking',pickProgress:'Picking progress',picked:'Picked',partial:'Partial',skipped:'Skipped',pending:'Pending',confirmPick:'Confirm pick',partialPick:'Partial pick',skipPick:'Skip',undo:'Undo',submitPick:'Submit picking',reviewScan:'Scan order or SKU',confirmReview:'Complete review',carrier:'Carrier',driver:'Driver',vehicle:'Vehicle',handoverDone:'Confirm handover',queryHint:'Enter SKU, carton or pallet ID',noResult:'No matching inventory',inventoryStatus:'Inventory status',inStock:'In stock',inTransit:'In transit',newPalletConfirm:'This pallet is not submitted. Start a new pallet?',exitConfirm:'There are unsaved changes. Exit anyway?',yes:'Confirm',cancel:'Cancel',success:'Action completed',loginError:'Invalid worker ID or password',required:'Enter a value',scanSuccess:'Scan accepted',unknownScan:'No matching code',taskDone:'Task completed',demoAccount:'Demo: W001 / 123456',containerMeta:'{container} · {boxes} cartons',planMeta:'{customer} · {destinationCode}',palletMeta:'{count} tickets · {pieces} pcs',inventoryMeta:'{customer} · {destinationCode}',unavailable:'Not available'},
es:{brand:'PDA Baiyue',sub:'Operaciones de almacén en el extranjero',loginTitle:'Acceso al almacén',loginIntro:'Descarga contenedores, consolida pallets, procesa salidas y consulta inventario.',username:'ID de trabajador',password:'Contraseña',login:'Entrar',show:'Mostrar',hide:'Ocultar',demo:'Cuenta demo',welcome:'Bienvenido',shift:'Turno de día · Activo',warehouse:'Almacén Los Ángeles',profile:'Perfil',logout:'Salir',today:'Hoy',unload:'Contenedores por descargar',receivedToday:'Recibidos hoy',outboundToday:'Salidas pendientes',inbound:'Entrada',outbound:'Salida',internal:'Almacén',plan:'Planes de entrada',planDesc:'Descarga y verifica por plan',receive:'Descarga y recepción',receiveDesc:'Captura 3 fotos para recibir',pallet:'Paletizar',palletDesc:'Carga según los pallets del plan',pick:'Picking de salida',pickDesc:'Escanea SKU o caja',review:'Revisión de salida',reviewDesc:'Verifica cantidades antes de liberar',handover:'Entrega de salida',handoverDesc:'Confirma vehículo y carga',inventory:'Buscar inventario',inventoryDesc:'Busca SKU, caja o pallet',more:'Más operaciones',demoOnly:'Solo demostración en esta versión',task:'Tareas abiertas',empty:'No hay tareas abiertas',choose:'Abrir',details:'Detalles',status:'Estado',customer:'Cliente',destination:'Destino',destinationCode:'Código de destino',destinationAddress:'Dirección de destino',ticket:'Referencia',planNo:'Plan de entrada',container:'Contenedor',boxes:'Cajas',expected:'Previsto',received:'Recibido',arrived:'ETA',startReceive:'Iniciar descarga',scanBox:'Escanea o escribe la caja',scan:'Escanear',normal:'Normal',short:'Faltante',damaged:'Dañada',hold:'En espera',receiveProgress:'Progreso de recepción',receivedList:'Cajas recibidas',goPallet:'Continuar a paletizar',newPallet:'Nuevo pallet',currentPallet:'Pallet actual',palletNo:'ID de pallet',items:'Líneas',pieces:'Unidades',addCargo:'Escanea caja o SKU para añadir',remove:'Quitar',submitPallet:'Enviar pallet',sameDest:'Mismo destino permite consolidar referencias',lockedDest:'Destino bloqueado',mergeOk:'Añadido al pallet',mergeBlocked:'Destino distinto. No se puede consolidar.',duplicate:'Esta caja ya fue recibida',notFound:'No se encontró la carga',emptyPallet:'Añade carga antes de enviar el pallet',submitted:'Pallet completado',trace:'Trazabilidad',back:'Volver',next:'Siguiente',complete:'Completar',search:'Buscar',clear:'Limpiar',sku:'SKU',boxNo:'ID de caja',qty:'Cantidad',operator:'Operador',role:'Rol',shiftLabel:'Turno',language:'Idioma',version:'Versión',selectPlan:'Selecciona un plan de entrada',scanContainer:'Escanea o escribe el contenedor',unloadTask:'Contenedores por descargar',pickTask:'Tareas de salida',startPick:'Iniciar picking',pickProgress:'Progreso de picking',picked:'Recogido',partial:'Parcial',skipped:'Omitido',pending:'Pendiente',confirmPick:'Confirmar picking',partialPick:'Picking parcial',skipPick:'Omitir',undo:'Deshacer',submitPick:'Enviar picking',reviewScan:'Escanea orden o SKU',confirmReview:'Completar revisión',carrier:'Transportista',driver:'Conductor',vehicle:'Vehículo',handoverDone:'Confirmar entrega',queryHint:'Escribe SKU, caja o pallet',noResult:'No hay inventario coincidente',inventoryStatus:'Estado de inventario',inStock:'En stock',inTransit:'En tránsito',newPalletConfirm:'El pallet no está enviado. ¿Crear otro?',exitConfirm:'Hay cambios sin enviar. ¿Salir?',yes:'Confirmar',cancel:'Cancelar',success:'Operación completada',loginError:'ID o contraseña incorrectos',required:'Introduce un valor',scanSuccess:'Escaneo aceptado',unknownScan:'Código no encontrado',taskDone:'Tarea completada',demoAccount:'Demo: W001 / 123456',containerMeta:'{container} · {boxes} cajas',planMeta:'{customer} · {destinationCode}',palletMeta:'{count} referencias · {pieces} uds.',inventoryMeta:'{customer} · {destinationCode}',unavailable:'No disponible'}
};
const users={W001:{pass:'123456',name:'张伟',role:'仓管员',warehouse:'洛杉矶海外仓'},W002:{pass:'123456',name:'李敏',role:'仓库组长',warehouse:'洛杉矶海外仓'},W003:{pass:'123456',name:'王磊',role:'收货员',warehouse:'新泽西海外仓'}};
const plans=[
{id:'INB-260819-018',customer:'杭州积乐贸易',destinationCode:'ONT8',destinationWarehouses:['ONT8','LAX9'],destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',tickets:'TK-88401 / TK-88402',inboundCode:'FBA-ONT8-260819-018',tractorDriver:'Robert Chen',tractorPlate:'CA 8TR-221',expectedPallets:18,receivedPallets:5,eta:'2026-08-19 10:30',status:'待卸货',container:'D2222'},
{id:'INB-260819-021',customer:'深圳璟智供应链',destinationCode:'ONT8',destinationWarehouses:['ONT8','LAX9','SNA6'],destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',tickets:'TK-88416',inboundCode:'FBA-ONT8-260819-021',tractorDriver:'Luis Garcia',tractorPlate:'CA 7LG-333',expectedPallets:12,receivedPallets:0,eta:'2026-08-19 13:00',status:'待卸货',container:'D3333'},
{id:'INB-260819-026',customer:'宁波远航电商',destinationCode:'LGB8',destinationWarehouses:['LGB8'],destinationAddress:'1568 N Linden Ave, Rialto, CA',tickets:'TK-88429',inboundCode:'FBA-LGB8-260819-026',tractorDriver:'Kevin Wu',tractorPlate:'CA 5KW-444',expectedPallets:8,receivedPallets:0,eta:'2026-08-18 16:20',status:'待打托',container:'D4444'}
];
plans.forEach(p=>{p.expectedBoxes=p.expectedPallets;p.receivedBoxes=p.receivedPallets});
const cargo=[
{id:'C-001',planNo:'INB-260819-018',ticketNo:'TK-88401',customer:'杭州积乐贸易',container:'D2222',boxNo:'BX-88401-01',sku:'YQ-CASE-01',name:'Phone case set',qty:120,receivedQty:120,destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',received:true},
{id:'C-002',planNo:'INB-260819-018',ticketNo:'TK-88402',customer:'杭州积乐贸易',container:'D2222',boxNo:'BX-88402-01',sku:'YQ-CABLE-02',name:'USB-C cable',qty:80,receivedQty:80,destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',received:true},
{id:'C-003',planNo:'INB-260819-021',ticketNo:'TK-88416',customer:'深圳璟智供应链',container:'D3333',boxNo:'BX-88416-01',sku:'JZ-LAMP-03',name:'Desk lamp',qty:60,receivedQty:0,destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',received:false},
{id:'C-004',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-01',sku:'NH-BAG-04',name:'Travel bag',qty:32,receivedQty:32,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true},
{id:'C-005',planNo:'INB-260819-021',ticketNo:'TK-88416',customer:'深圳璟智供应链',container:'D3333',boxNo:'BX-88416-02',sku:'JZ-HOLDER-05',name:'Tablet holder',qty:40,receivedQty:0,destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',received:false},
{id:'C-006',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-02',sku:'NH-BAG-06',name:'Packing pouch',qty:48,receivedQty:48,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-007',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-03',sku:'NH-BAG-07',name:'Travel bag accessory',qty:36,receivedQty:36,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-008',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-04',sku:'NH-BAG-08',name:'Packing strap',qty:28,receivedQty:28,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-009',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-05',sku:'NH-BAG-09',name:'Luggage tag',qty:24,receivedQty:24,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-010',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-06',sku:'NH-BAG-10',name:'Carry pouch',qty:42,receivedQty:42,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-011',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-07',sku:'NH-BAG-11',name:'Shoe bag',qty:30,receivedQty:30,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-012',planNo:'INB-260819-026',ticketNo:'TK-88429',customer:'宁波远航电商',container:'D4444',boxNo:'BX-88429-08',sku:'NH-BAG-12',name:'Foldable tote',qty:38,receivedQty:38,destinationCode:'LGB8',destinationAddress:'1568 N Linden Ave, Rialto, CA',received:true}
,{id:'C-013',planNo:'INB-260819-021',ticketNo:'TK-88416',customer:'深圳璟智供应链',container:'D3333',boxNo:'BX-88416-03',sku:'JZ-MOUSE-07',name:'Wireless mouse',qty:90,receivedQty:90,destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',received:true}
];
const pickTasks=[{id:'OUT-260819-004',inboundCode:'FBA-ONT8-260819-004',plateNo:'CA 8KX-217',driver:'Michael Chen',outboundTime:'2026-08-20 09:30',destination:'ONT8',customer:'杭州积乐贸易',boxes:6,palletCount:3,totalWeightLb:203.0,totalPallets:3,orderInfo:'SO-260819-004 / AMZ-PO-88401',items:[{sku:'YQ-CASE-01',qty:45,picked:0},{sku:'YQ-CABLE-02',qty:20,picked:0}]},{id:'OUT-260819-006',inboundCode:'FBA-LGB8-260819-006',plateNo:'CA 6MP-908',driver:'David Lee',outboundTime:'2026-08-20 13:00',destination:'LGB8',customer:'宁波远航电商',boxes:3,palletCount:2,totalWeightLb:99.9,totalPallets:2,orderInfo:'SO-260819-006 / AMZ-PO-88412',items:[{sku:'NH-BAG-04',qty:12,picked:0},{sku:'NH-BAG-06',qty:18,picked:0}]}];
const cartonWeights=[18.5,12.2,9.8,14.6,7.4,16.1,13.8,5.6,3.2,11.7,4.8,10.9,8.3];
cargo.forEach((item,index)=>{item.weight=cartonWeights[index]||0});
const inventory=[...cargo.map((x,i)=>({sku:x.sku,name:x.name,customer:x.customer,boxNo:x.boxNo,palletNo:i<2?'D2222-TC-001':'—',destinationCode:x.destinationCode,qty:x.receivedQty,status:x.receivedQty?'在库':'在途'})),{sku:'JZ-MOUSE-07',name:'Wireless mouse',customer:'深圳璟智供应链',boxNo:'BX-88416-03',palletNo:'—',destinationCode:'ONT8',qty:90,status:'在途'}];
const demoPalletizedPallets=[{id:'D2222-TC-001',mode:'palletize',planId:'INB-260819-018',targetPallets:18,container:'D2222',destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',items:[{...cargo[0],palletNo:'D2222-TC-001'},{...cargo[1],palletNo:'D2222-TC-001'}]}];
cargo[0].palletNo='D2222-TC-001';cargo[1].palletNo='D2222-TC-001';
const demoConsolidatedPallets=[{id:'D2222-TC-019',mode:'consolidate',planId:null,targetPallets:null,container:'D2222',destinationCode:'ONT8',destinationAddress:'24300 Nandina Ave, Moreno Valley, CA',items:[{...cargo[0],isMerged:false,mergeFlag:'否'},{...cargo[1],isMerged:true,mergeFlag:'是'}]}];
const WORK_LABELS={zh:{inboundPallet:'入库打托',inboundPalletDesc:'按入库计划托数完成打托',pallet:'入库打托',palletDesc:'按计划托数装对应货物',palletList:'托盘列表',palletTotal:'托盘数',palletBoxSummary:'托盘数/箱数',palletListDesc:'选择托盘进入详情',pendingPallet:'待创建托盘',cartonCount:'箱数',boxMark:'箱唛',productName:'产品名称',palletStatus:'托盘状态',idle:'闲置',occupied:'占用',viewPalletDetails:'查看明细',directEnter:'直接进入',selectPalletLabel:'选择托盘标',palletLabel:'托盘标',scanBoxSku:'扫描箱唛/SKU',scanPalletLabel:'扫描托盘标',unknownPalletScan:'未找到匹配托盘',palletCompleted:'已完成',palletPending:'未完成',palletizeAction:'卸货打托',planDesc:'按入库计划选择打托任务',mergeOnly:'合托',mergeDesc:'将不足一托的箱子合成一托',startPallet:'开始打托',palletProgress:'打托进度',palletTargetReached:'已达到计划托数,不能继续提交'},en:{inboundPallet:'Inbound palletizing',inboundPalletDesc:'Palletize against the inbound plan',pallet:'Inbound palletizing',palletDesc:'Load freight by planned pallet count',palletList:'Pallet list',palletTotal:'Pallets',palletBoxSummary:'Pallets/Cartons',palletListDesc:'Select a pallet to view details',pendingPallet:'Pallet to create',cartonCount:'Cartons',boxMark:'Carton mark',productName:'Product name',palletStatus:'Pallet status',idle:'Idle',occupied:'Occupied',viewPalletDetails:'View pallet details',directEnter:'Enter directly',selectPalletLabel:'Select pallet label',palletLabel:'Pallet label',scanBoxSku:'Scan carton mark/SKU',scanPalletLabel:'Scan pallet label',unknownPalletScan:'No matching pallet found',palletCompleted:'Completed',palletPending:'Incomplete',palletizeAction:'Palletize',planDesc:'Choose an inbound plan to palletize',mergeOnly:'Consolidate',mergeDesc:'Combine cartons that do not fill a pallet',startPallet:'Start palletizing',palletProgress:'Palletizing progress',palletTargetReached:'Planned pallet count reached'},es:{inboundPallet:'Paletizado de entrada',inboundPalletDesc:'Paletiza según el plan de entrada',pallet:'Paletizado de entrada',palletDesc:'Carga según los pallets del plan',palletList:'Lista de pallets',palletTotal:'Pallets',palletBoxSummary:'Pallets/Cajas',palletListDesc:'Selecciona un pallet para ver detalles',pendingPallet:'Pallet por crear',cartonCount:'Cajas',boxMark:'Marca de caja',productName:'Nombre del producto',palletStatus:'Estado del pallet',idle:'Libre',occupied:'Ocupado',viewPalletDetails:'Ver detalles de pallets',directEnter:'Entrar directamente',selectPalletLabel:'Seleccionar etiqueta de pallet',palletLabel:'Etiqueta de pallet',scanBoxSku:'Escanear marca de caja/SKU',scanPalletLabel:'Escanear etiqueta de pallet',unknownPalletScan:'No se encontró el pallet',palletCompleted:'Completado',palletPending:'Incompleto',palletizeAction:'Paletizar',planDesc:'Elige un plan de entrada para paletizar',mergeOnly:'Consolidar',mergeDesc:'Combina cajas que no llenan un pallet',startPallet:'Iniciar paletizado',palletProgress:'Progreso de paletizado',palletTargetReached:'Se alcanzó el total de pallets del plan'}};
const state={lang:localStorage.getItem('overseas-pda-lang')||'zh',page:'login',user:null,error:'',selectedPlanId:null,receiveScan:[],receiveStatus:{},unloadPhotos:{},pallet:null,palletMode:'palletize',palletOrderType:'fba',fbaPalletLabels:[],dropshipPalletLabel:'',palletTypeMessage:null,fbaPalletMessage:null,dropshipScanMessage:null,pallets:[...demoPalletizedPallets,...demoConsolidatedPallets].map(pallet=>({...pallet,items:pallet.items.map(item=>({...item}))})),palletMessage:null,palletTopMessage:null,palletPhotos:{},photoPalletId:'',pickTaskId:null,pickMessage:null,reviewScanned:[],handoverDone:false,handoverPhotos:{},inventoryQuery:'',relabelMessage:null,relabelDoneOrders:{},selectedRelabelId:null,relabelPhotos:{},exceptionRefValue:'',exceptionMessage:null};
const EXTRA_LABELS={zh:{planDesc:'按计划核对托数并打托',startPallet:'开始打托',mergeOnly:'合托',receiveDesc:'拍摄 3 张作业照片完成收货',receiveListTitle:'待卸货集装箱',receiveListDesc:'选择集装箱后拍摄 3 张照片完成收货',pendingContainers:'个待卸货集装箱',startUnload:'开始拆柜',stocktake:'库存盘点',transfer:'移库作业',relabel:'换标作业',exception:'异常处理',palletCount:'托数',palletQty:'托盘数量',linkedOrder:'关联订单',licensePlate:'车牌照片',containerSeal:'集装箱封签',doorOpen:'开柜照片',unloading:'卸货照片',unloadComplete:'卸货完成照片',takePhoto:'拍照',retake:'重拍',photoProgress:'拍照进度',confirmUnload:'确认拆柜收货',photosRequired:'请完成全部 3 张照片',handoverPhotosRequired:'请完成全部 7 张交接照片',mergeFlag:'是否合托',yesMerge:'是',noMerge:'否',licensePlatePhoto:'车牌照片',vehicleCabPhoto:'车厢照片',driverLicense:'司机驾照',loadingSealPhoto:'装货封门照片',lockSealPhoto:'锁门 SEAL 照片',bolDriverSigned:'BOL(含司机签字)',amazonAppointment:'亚马逊预约单',handoverPhotos:'交接照片(7张)',relabelTitle:'换标作业',relabelHint:'订单列表中提交换标完成',newLabel:'新标签 / 新 SKU',submitRelabel:'换标完成',relabelDone:'换标已完成',relabelStatus:'换标状态',relabelPhotoRequired:'请先完成换标拍照',exceptionTitle:'异常处理',exceptionType:'异常类型',exceptionRef:'关联尾程订单号 / 入仓码 / 托盘号',exceptionDesc:'异常描述',submitException:'提交异常',exceptionDone:'异常已记录',selectException:'请选择异常类型',queryHint:'输入托盘号或产品名称',inventoryDesc:'查询产品名称或托盘号'},en:{planDesc:'Verify pallet count and start palletizing',startPallet:'Start palletizing',mergeOnly:'Consolidate',receiveDesc:'Capture 3 operation photos to receive',receiveListTitle:'Containers to unload',receiveListDesc:'Choose a container, then capture 3 photos to receive it',pendingContainers:'containers to unload',startUnload:'Start unload',stocktake:'Stocktake',transfer:'Stock transfer',relabel:'Relabeling',exception:'Exceptions',palletCount:'Pallets',palletQty:'Pallet quantity',linkedOrder:'Linked order',licensePlate:'License plate photo',containerSeal:'Container seal',doorOpen:'Door open',unloading:'Unloading',unloadComplete:'Unload complete',takePhoto:'Take photo',retake:'Retake',photoProgress:'Photo progress',confirmUnload:'Confirm unloading',photosRequired:'Complete all 3 photos',handoverPhotosRequired:'Complete all 7 handover photos',mergeFlag:'Merged pallet',yesMerge:'Yes',noMerge:'No',licensePlatePhoto:'License plate photo',vehicleCabPhoto:'Vehicle cab photo',driverLicense:'Driver license',loadingSealPhoto:'Loading door photo',lockSealPhoto:'Lock SEAL photo',bolDriverSigned:'BOL with driver signature',amazonAppointment:'Amazon appointment',handoverPhotos:'Handover photos (7)',relabelTitle:'Relabeling',relabelHint:'Submit completion from the order list',newLabel:'New label / new SKU',submitRelabel:'Relabel complete',relabelDone:'Relabel completed',relabelStatus:'Relabel status',relabelPhotoRequired:'Take a relabel photo before completing',exceptionTitle:'Exception handling',exceptionType:'Exception type',exceptionRef:'Outbound order / inbound code / pallet ID',exceptionDesc:'Exception description',submitException:'Log exception',exceptionDone:'Exception recorded',selectException:'Select an exception type',queryHint:'Enter pallet ID or product name',inventoryDesc:'Search product name or pallet ID'},es:{planDesc:'Verifica pallets e inicia paletizado',startPallet:'Iniciar paletizado',mergeOnly:'Consolidar',receiveDesc:'Captura 3 fotos para recibir',receiveListTitle:'Contenedores por descargar',receiveListDesc:'Elige un contenedor y captura 3 fotos para recibirlo',pendingContainers:'contenedores por descargar',startUnload:'Iniciar descarga',stocktake:'Inventario físico',transfer:'Traslado interno',relabel:'Reetiquetado',exception:'Excepciones',palletCount:'Pallets',palletQty:'Cantidad de pallets',linkedOrder:'Pedido relacionado',licensePlate:'Matrícula',containerSeal:'Sello del contenedor',doorOpen:'Apertura',unloading:'Descarga',unloadComplete:'Descarga terminada',takePhoto:'Tomar foto',retake:'Repetir',photoProgress:'Progreso de fotos',confirmUnload:'Confirmar descarga',photosRequired:'Completa las 3 fotos',handoverPhotosRequired:'Completa las 7 fotos de entrega',mergeFlag:'Pallet consolidado',yesMerge:'Sí',noMerge:'No',licensePlatePhoto:'Foto de matrícula',vehicleCabPhoto:'Foto de cabina',driverLicense:'Licencia del conductor',loadingSealPhoto:'Puerta de carga',lockSealPhoto:'Foto del SEAL',bolDriverSigned:'BOL firmado por conductor',amazonAppointment:'Cita de Amazon',handoverPhotos:'Fotos de entrega (7)',relabelTitle:'Reetiquetado',relabelHint:'Envía la finalización desde la lista de pedidos',newLabel:'Nueva etiqueta / SKU',submitRelabel:'Reetiquetado completo',relabelDone:'Reetiquetado completado',relabelStatus:'Estado del reetiquetado',relabelPhotoRequired:'Toma una foto del reetiquetado antes de completar',exceptionTitle:'Gestión de excepciones',exceptionType:'Tipo de excepción',exceptionRef:'Orden de salida / código de entrada / ID de pallet',exceptionDesc:'Descripción de la excepción',submitException:'Registrar excepción',exceptionDone:'Excepción registrada',selectException:'Selecciona un tipo',queryHint:'Escribe ID de pallet o nombre del producto',inventoryDesc:'Busca nombre del producto o ID de pallet'}};
const PICK_LABELS={zh:{outboundOrderNo:'出库单号',inboundCode:'入仓码',plateNo:'车牌号',outboundTime:'出库时间',totalPalletBoxes:'总托数 / 总箱数',totalBoxes:'总箱数',totalWeight:'总重量',orderList:'订单列表',tractorDriver:'拖车司机',tractorPlate:'拖车车牌',destinationWarehouse:'目的仓编码'},en:{outboundOrderNo:'Outbound order',inboundCode:'Inbound code',plateNo:'Plate number',outboundTime:'Outbound time',totalPalletBoxes:'Total pallets / cartons',totalBoxes:'Total cartons',totalWeight:'Total weight',orderList:'Order list',tractorDriver:'Tractor driver',tractorPlate:'Tractor plate',destinationWarehouse:'Destination warehouse'},es:{outboundOrderNo:'Orden de salida',inboundCode:'Código de entrada',plateNo:'Matrícula',outboundTime:'Hora de salida',totalPalletBoxes:'Pallets / cajas totales',totalBoxes:'Total de cajas',totalWeight:'Peso total',orderList:'Lista de pedidos',tractorDriver:'Conductor del tractor',tractorPlate:'Matrícula del tractor',destinationWarehouse:'Almacén de destino'}};
function t(key,vars={}){let value=WORK_LABELS[state.lang]?.[key]||EXTRA_LABELS[state.lang]?.[key]||PICK_LABELS[state.lang]?.[key]||(I18N[state.lang]&&I18N[state.lang][key])||WORK_LABELS.zh[key]||EXTRA_LABELS.zh[key]||PICK_LABELS.zh[key]||I18N.zh[key]||key;return String(value).replace(/\{(\w+)\}/g,(_,k)=>vars[k]??'');}
function esc(value){return String(value??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
const ICON_PATHS={
pallet:'<path class="icon-fill" d="m4.5 8.5 7.5-4 7.5 4-7.5 4-7.5-4Z"/><path d="m4.5 8.5 7.5-4 7.5 4-7.5 4-7.5-4Z"/><path d="M4.5 8.5v7l7.5 4 7.5-4v-7M12 12.5v7M3.5 21h17"/>',
unload:'<path class="icon-fill" d="M3.5 4.5h17v11h-17z"/><rect x="3.5" y="4.5" width="17" height="11" rx="2"/><path d="M8 4.5v11m8-11v11M6.5 8h3M14.5 8h3M12 14v7m-3-3 3 3 3-3"/>',
merge:'<rect class="icon-fill" x="7.5" y="15.5" width="9" height="5" rx="1.25"/><rect x="3" y="3.5" width="6.5" height="5" rx="1.25"/><rect x="14.5" y="3.5" width="6.5" height="5" rx="1.25"/><rect x="7.5" y="15.5" width="9" height="5" rx="1.25"/><path d="M6.25 8.5v1.75A2.75 2.75 0 0 0 9 13h6a2.75 2.75 0 0 0 2.75-2.75V8.5M12 13v2.5"/>',
pick:'<path class="icon-fill" d="M5 7.5h13l-1.5 8h-10z"/><path d="M3 4h1.5L6.5 16h10l1.5-8.5H5.1M8.5 20h.01M15 20h.01"/><path d="m9.5 11.5 1.6 1.6 3.4-3.4"/>',
review:'<rect x="5" y="3" width="14" height="18" rx="2"/><path d="M9 3.5h6"/><path d="m8.5 12 2 2 5-5"/><path d="M8.5 8h2"/>',
handover:'<path class="icon-fill" d="M3 6h11v10H3zM14 9h4l3 3v4h-7z"/><path d="M3 6h11v10H3V6Zm11 3h4l3 3v4h-7V9ZM6.5 19h.01M17.5 19h.01"/><path d="m6.5 11 1.5 1.5 3-3"/>',
inventory:'<path class="icon-fill" d="m3.5 6.5 6.5-3 6.5 3-6.5 3-6.5-3Z"/><path d="m3.5 6.5 6.5-3 6.5 3-6.5 3-6.5-3Zm0 0v7l6.5 3 3-1.4M10 9.5v7"/><circle cx="16.5" cy="16.5" r="3.5"/><path d="m19.25 19.25 2.25 2.25"/>',
relabel:'<path class="icon-fill" d="m3.5 6.5 7.5-3 9.5 9.5-7.5 7.5L3.5 11z"/><path d="m3.5 6.5 7.5-3 9.5 9.5-7.5 7.5L3.5 11V6.5Z"/><circle cx="8" cy="8" r="1.25"/><path d="M12.5 9.5h4m0 0-1.5-1.5m1.5 1.5L15 11m-3.5 4h4m-4 0 1.5-1.5M11.5 15l1.5 1.5"/>',
exception:'<path class="icon-fill" d="M12 3 21 20H3L12 3Z"/><path d="M12 3 21 20H3L12 3Z"/><path d="M12 9v4.5M12 17h.01"/>',
more:'<rect x="4" y="4" width="6" height="6" rx="1"/><rect x="14" y="4" width="6" height="6" rx="1"/><rect x="4" y="14" width="6" height="6" rx="1"/><rect x="14" y="14" width="6" height="6" rx="1"/>',
scan:'<path d="M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3"/><path d="M7 9h10v6H7z"/><path d="M10 9v6M13 9v6"/>',
arrowLeft:'<path d="M19 12H5"/><path d="m11 6-6 6 6 6"/>',
camera:'<path d="M4 7h3l1.5-2h7L17 7h3v12H4V7Z"/><circle cx="12" cy="13" r="3.5"/>',
check:'<path d="m5 12 4 4L19 6"/>'
};
function icon(name,label=''){const path=ICON_PATHS[name]||ICON_PATHS.more;return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"${label?` aria-label="${esc(label)}"`:''}>${path}</svg>`;}
function setLang(lang){if(!LANGS.includes(lang))return;state.lang=lang;localStorage.setItem('overseas-pda-lang',lang);render();}
function langButtons(){return `<div class="lang-pill" role="group" aria-label="${t('language')}">${[['zh','中'],['en','EN'],['es','ES']].map(([v,l])=>`<button type="button" class="${state.lang===v?'active':''}" aria-pressed="${state.lang===v}" onclick="setLang('${v}')">${l}</button>`).join('')}</div>`;}
function header(title,sub='',back='workbench'){return `<header class="topbar"><div class="topbar-row"><div class="page-heading">${back?`<button type="button" class="back-btn" onclick="navigate('${back}')" aria-label="${t('back')}">${icon('arrowLeft')}</button>`:''}<div><h1>${esc(compactPageTitle(title))}</h1>${sub?`<p>${esc(sub)}</p>`:''}</div></div><div class="top-actions">${langButtons()}<button type="button" class="avatar" onclick="navigate('profile')" aria-label="${t('profile')}">${esc((state.user?.name||'张').slice(0,1))}</button></div></div></header>`;}
function layout(body,title='',sub='',back='workbench'){const resolvedBack=state.page==='pallet'?(state.palletMode==='consolidate'?'pallet-list':'plans'):back;return `${state.page==='login'?'':header(title,sub,resolvedBack)}<main class="content ${state.page==='login'?'narrow':''}">${body}</main>`;}
function loginView(){return `<div class="login-wrap"><section class="login-card"><div class="brand"><div class="brand-mark">P</div><div><div class="brand-title">${t('brand')}</div><div class="brand-sub">${t('sub')}</div></div></div><div style="display:flex;justify-content:flex-end;margin-top:-16px;margin-bottom:14px">${langButtons()}</div><h1>${t('loginTitle')}</h1><p class="intro">${t('loginIntro')}</p><div class="field"><label for="login-user">${t('username')}</label><input id="login-user" autocomplete="username" placeholder="W001" aria-describedby="login-error"></div><div class="field"><label for="login-pass">${t('password')}</label><div class="password-wrap"><input id="login-pass" type="password" autocomplete="current-password" placeholder="123456" aria-describedby="login-error"><button onclick="togglePassword()" type="button" id="password-toggle">${t('show')}</button></div></div><div class="login-error" id="login-error" role="alert" aria-live="polite">${esc(state.error)}</div><button type="button" class="login-btn" onclick="login()">${t('login')}</button><div class="demo-row"><button type="button" onclick="quickLogin('W001')">W001 · ${esc(users.W001.name)}</button><button type="button" onclick="quickLogin('W002')">W002 · ${esc(users.W002.name)}</button></div><div class="demo-tip">${t('demoAccount')}</div></section></div>`;}
function togglePassword(){const input=document.getElementById('login-pass');const button=document.getElementById('password-toggle');if(!input||!button)return;const show=input.type==='password';input.type=show?'text':'password';button.textContent=t(show?'hide':'show');}
function login(){const user=(document.getElementById('login-user')?.value||'').trim();const pass=document.getElementById('login-pass')?.value||'';if(!user||!pass){state.error=t('required');render();return}if(!users[user]||users[user].pass!==pass){state.error=t('loginError');render();return}state.user={id:user,...users[user]};state.error='';state.page='workbench';render();toast(t('success'),'good');}
function quickLogin(id){state.user={id,...users[id]};state.error='';state.page='workbench';render();toast(t('success'),'good');}
function logout(){state.user=null;state.page='login';state.pallet=null;render();}
function navigate(page){if(page==='pallet'&&state.palletMode!=='consolidate')state.palletMode='palletize';if(state.page==='pallet'&&state.pallet&&state.pallet.items.length&&page==='workbench'){confirmExit();return}state.page=page;render();}
function confirmExit(){showModal(t('exitConfirm'),()=>{state.pallet=null;state.page='workbench';closeModal();render();});}
function workbenchView(){const unload=plans.filter(p=>p.status==='待卸货').length;const received=plans.reduce((sum,p)=>sum+p.receivedBoxes,0);const outbound=pickTasks.filter(t=>!t.done).length;return layout(`<section class="welcome"><div><div class="welcome-kicker">${t('welcome')}</div><div class="welcome-name">${esc(state.user?.name||'')}</div><div class="welcome-meta">${esc(state.user?.role||'')} · ${esc(state.user?.warehouse||t('warehouse'))}</div></div><div class="shift">${t('shift')}</div></section><section class="metrics"><div class="metric warn"><div class="metric-label">${t('unload')}</div><div class="metric-value">${unload}</div></div><div class="metric good"><div class="metric-label">${t('receivedToday')}</div><div class="metric-value">${received}</div></div><div class="metric blue"><div class="metric-label">${t('outboundToday')}</div><div class="metric-value">${outbound}</div></div></section><div class="section-title"><h2>${t('inbound')}</h2><span>${t('task')}</span></div><div class="entry-grid inbound-entry-grid"><button class="entry receive-entry" onclick="navigate('receive-list')"><div class="entry-icon">${icon('unload')}</div><h3>${t('receive')}</h3><p>${t('receiveDesc')}</p><span class="badge">${unload}</span></button><button class="entry pallet-entry" onclick="navigate('plans')"><div class="entry-icon">${icon('pallet')}</div><h3>${t('inboundPallet')}</h3><p>${t('inboundPalletDesc')}</p><span class="badge">${plans.filter(p=>p.status==='待打托').length}</span></button></div><div class="section-title"><h2>${t('outbound')}</h2><span>${t('pickTask')}</span></div><div class="entry-grid outbound-entry-grid"><button class="entry merge-entry" onclick="startConsolidation()"><div class="entry-icon">${icon('merge')}</div><h3>${t('mergeOnly')}</h3><p>${t('mergeDesc')}</p></button><button class="entry pick-entry" onclick="navigate('pick')"><div class="entry-icon">${icon('pick')}</div><h3>${t('pick')}</h3><p>${t('pickDesc')}</p><span class="badge">${outbound}</span></button><button class="entry handover-entry" onclick="navigate('handover')"><div class="entry-icon">${icon('handover')}</div><h3>${t('handover')}</h3><p>${t('handoverDesc')}</p></button></div><div class="section-title"><h2>${t('internal')}</h2><span>${t('inventory')}</span></div><div class="entry-grid"><button class="entry inventory-entry" onclick="navigate('inventory')"><div class="entry-icon">${icon('inventory')}</div><h3>${t('inventory')}</h3><p>${t('inventoryDesc')}</p></button><button class="entry more-entry disabled" onclick="toast(t('demoOnly'),'info')"><div class="entry-icon">${icon('more')}</div><h3>${t('more')}</h3><p>${t('unavailable')}</p></button></div><div class="task-strip"><div class="section-title" style="margin-top:0"><h2>${t('task')}</h2><span>${t('unloadTask')}</span></div>${plans.filter(p=>p.status==='待卸货').slice(0,2).map(p=>`<div class="task-row"><span class="task-dot"></span><div class="task-main"><strong>${esc(p.container)}</strong><p>${esc(p.customer)} · ${esc(p.destinationCode)} · ${p.expectedPallets} ${t('palletCount')}</p></div><button onclick="openPlan('${p.id}')">${t('choose')}</button></div>`).join('')||`<div class="empty">${t('empty')}</div>`}</div>`,t('brand'),t('sub'),null);}
function openPlan(id){state.selectedPlanId=id;state.page='receive';render();}
function receiveListView(){const pending=plans.filter(p=>p.status==='待卸货');return layout(`<div class="toolbar"><span class="subtle">${pending.length} ${t('pendingContainers')}</span></div><div class="list">${pending.map(p=>`<article class="list-card"><div class="list-top"><div><div class="list-title">${esc(p.container)}</div><div class="list-meta">${esc(p.id)}</div></div><span class="status wait">${t('unload')}</span></div><div class="list-grid"><div><span>${t('destinationWarehouse')}</span><strong>${(p.destinationWarehouses||[p.destinationCode]).slice(0,3).map(esc).join(' / ')}</strong></div><div><span>${t('palletCount')}</span><strong>${p.expectedPallets} ${t('palletUnit')} / ${p.expectedBoxes} ${t('boxes')}</strong></div><div><span>${t('arrived')}</span><strong>${esc(p.eta.slice(5))}</strong></div><div><span>${t('tractorDriver')}</span><strong>${esc(p.tractorDriver)}</strong></div><div><span>${t('tractorPlate')}</span><strong>${esc(p.tractorPlate)}</strong></div></div><div class="card-actions"><button class="btn primary" onclick="openPlan('${p.id}')">${t('startUnload')}</button></div></article>`).join('')||`<div class="empty">${t('empty')}</div>`}</div>`,t('receiveListTitle'),t('receiveListDesc'),'workbench');}
function plansView(){return layout(`<div class="toolbar"><span class="subtle">${plans.length} ${t('plan')}</span></div><div class="list">${plans.map(p=>`<article class="list-card"><div class="list-top"><div><div class="list-title">${esc(p.id)}</div><div class="list-meta">${esc(p.customer)} · ${esc(p.container)}</div></div><span class="status ${p.status==='待打托'?'blue':'wait'}">${esc(p.status==='待打托'?t('pallet'):t('unload'))}</span></div><div class="list-grid"><div><span>${t('destinationCode')}</span><strong>${esc(p.destinationCode)}</strong></div><div><span>${t('boxes')}</span><strong>${p.receivedBoxes} / ${p.expectedBoxes}</strong></div><div><span>${t('arrived')}</span><strong>${esc(p.eta.slice(5))}</strong></div></div><div class="list-meta">${t('destinationAddress')}: ${esc(p.destinationAddress)}<br>${t('ticket')}: ${esc(p.tickets)}</div><div class="card-actions"><button class="btn primary" onclick="openPlan('${p.id}')">${t('startReceive')}</button></div></article>`).join('')}</div>`,t('plan'),t('planDesc'),'workbench');}
function selectedPlan(){return plans.find(p=>p.id===state.selectedPlanId)||plans[0];}
function resetPalletTypeInputs(){state.fbaPalletLabels=[];state.dropshipPalletLabel='';state.palletTypeMessage=null;state.fbaPalletMessage=null;state.dropshipScanMessage=null;}
function setPalletOrderType(type){if(!['fba','overseas','dropship'].includes(type))return;state.palletOrderType=type;state.palletTypeMessage=null;state.fbaPalletMessage=null;state.dropshipScanMessage=null;render();}
function palletOrderTabs(){return `<div class="pallet-order-tabs" role="tablist" aria-label="${t('palletOrderType')}"><button type="button" role="tab" aria-selected="${state.palletOrderType==='fba'}" class="${state.palletOrderType==='fba'?'active':''}" onclick="setPalletOrderType('fba')">${t('fbaWarehouse')}</button><button type="button" role="tab" aria-selected="${state.palletOrderType==='overseas'}" class="${state.palletOrderType==='overseas'?'active':''}" onclick="setPalletOrderType('overseas')">${t('overseasWarehouse')}</button><button type="button" role="tab" aria-selected="${state.palletOrderType==='dropship'}" class="${state.palletOrderType==='dropship'?'active':''}" onclick="setPalletOrderType('dropship')">${t('oneClickDropship')}</button></div>`;}
const UNLOAD_PHOTO_STEPS=['licensePlate','containerSeal','doorOpen'];
function startPalletWork(planId){const plan=plans.find(x=>x.id===planId)||plans[0];state.selectedPlanId=plan.id;state.palletMode='palletize';const nextIndex=Array.from({length:plan.expectedPallets},(_,index)=>index).find(index=>!state.pallets.some(x=>x.id===plannedPalletNo(plan,index)));if(nextIndex===undefined){startPlanPallet(plan.id);return}selectPallet(plannedPalletNo(plan,nextIndex),plan.id);}
function plansPalletView(){const pallets=plans.flatMap(plan=>Array.from({length:plan.expectedPallets},(_,index)=>{const id=plannedPalletNo(plan,index);const saved=state.pallets.find(item=>item.mode==='palletize'&&item.id===id);return {id,plan,saved,items:saved?.items||[],destinationCode:saved?.destinationCode||plan.destinationCode}}));const totalBoxes=pallets.length*5;return layout(`<div class="scan-box pallet-top-scan"><input id="pallet-top-scan" placeholder="${t('scanBoxSku')}" onkeydown="if(event.key==='Enter')handlePalletTopScan()"><button onclick="handlePalletTopScan()" aria-label="${t('scan')}">${icon('scan')}</button></div>${state.palletTopMessage?`<div class="scan-result ${state.palletTopMessage.type}">${esc(state.palletTopMessage.text)}</div>`:''}<div class="toolbar pallet-list-toolbar"><span class="subtle">${t('palletBoxSummary')}:${pallets.length}/${totalBoxes}</span></div><div class="table-wrap"><table class="table pallet-list-table"><thead><tr><th>${t('palletLabel')}</th><th>${t('destination')}</th><th>${t('cartonCount')}</th><th>${t('palletStatus')}</th></tr></thead><tbody>${pallets.map(pallet=>{const completed=Boolean(pallet.saved?.persisted||pallet.items.length);return `<tr><td>${esc(pallet.id)}</td><td>${esc(pallet.destinationCode||'—')}</td><td>${pallet.items.length}</td><td><span class="status ${completed?'good':'wait'}">${completed?t('palletCompleted'):t('palletPending')}</span></td></tr>`}).join('')}</tbody></table></div>`,t('inboundPallet'),t('inboundPalletDesc'),'workbench');}
function handlePalletTopScan(){const input=document.getElementById('pallet-top-scan');const value=(input?.value||'').trim();if(!value){state.palletTopMessage=null;state.palletMode='palletize';startPallet();return}const item=cargo.find(c=>c.boxNo===value||c.sku===value);if(!item||!item.received){state.palletTopMessage={type:'error',text:t('notFound')};render();return}const existing=state.pallets.find(pallet=>pallet.mode==='palletize'&&pallet.items?.some(current=>current.id===item.id));if(existing){state.palletTopMessage=null;state.palletMode='palletize';selectPallet(existing.id,existing.planId);return}state.palletTopMessage=null;state.palletMode='palletize';state.selectedPlanId=item.planNo;startPallet(item.id);}
function receivePhotoView(){const p=selectedPlan();const photos=state.unloadPhotos[p.id]||{};const completed=UNLOAD_PHOTO_STEPS.filter(k=>photos[k]).length;const total=UNLOAD_PHOTO_STEPS.length;return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('receive')} · ${esc(p.container)}</h2><p>${esc(p.id)} · ${esc(p.customer)}</p></div><span class="status ${completed===total?'good':'wait'}">${completed} / ${total}</span></div><div class="chips" style="margin-top:12px"><span class="chip blue">${t('destinationCode')}: ${esc(p.destinationCode)}</span><span class="chip">${t('palletCount')}: ${p.expectedPallets}</span></div><div class="progress"><span style="width:${completed/total*100}%"></span></div><div class="progress-line"><span>${t('photoProgress')}</span><strong>${completed} / ${total}</strong></div></section><section class="work-card"><div class="photo-grid">${UNLOAD_PHOTO_STEPS.map(key=>`<div class="photo-step ${photos[key]?'done':''}"><div class="photo-preview">${photos[key]?.startsWith('data:')?`<img src="${photos[key]}" alt="${t(key)}">`:photos[key]?icon('check'):icon('camera')}</div><strong>${t(key)}</strong><label>${photos[key]?t('retake'):t('takePhoto')}<input type="file" accept="image/*" capture="environment" onchange="handleUnloadPhoto('${key}',this)"></label></div>`).join('')}</div></section>${completed<total?`<div class="scan-result error">${t('photosRequired')}</div>`:''}<div class="bottom-bar"><button class="btn primary" onclick="completeUnload()" ${completed===total?'':'disabled'}>${t('confirmUnload')}</button></div>`,t('receive'),`${esc(p.id)} · ${esc(p.destinationCode)}`,'receive-list');}
function handleUnloadPhoto(key,input){const file=input.files&&input.files[0];if(!file)return;const reader=new FileReader();reader.onload=()=>{const p=selectedPlan();state.unloadPhotos[p.id]??={};state.unloadPhotos[p.id][key]=reader.result;render()};reader.readAsDataURL(file);}
function completeUnload(){const p=selectedPlan();const photos=state.unloadPhotos[p.id]||{};if(UNLOAD_PHOTO_STEPS.some(key=>!photos[key])){toast(t('photosRequired'),'bad');return}p.receivedPallets=0;p.receivedBoxes=p.expectedPallets;p.status='待打托';cargo.filter(c=>c.planNo===p.id).forEach(c=>{c.received=true;c.receivedQty=c.qty});state.selectedPlanId=p.id;state.palletMode='palletize';state.pallet=null;state.palletMessage=null;state.page='plans';toast(t('taskDone'),'good');render();}
function receiveView(){const p=selectedPlan();const received=cargo.filter(c=>c.planNo===p.id&&c.received);const pct=Math.min(100,Math.round((p.receivedBoxes/p.expectedBoxes)*100));return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('receive')} · ${esc(p.id)}</h2><p>${esc(p.customer)} · ${esc(p.container)}</p></div><span class="status ${p.status==='待打托'?'blue':'wait'}">${esc(p.status)}</span></div><div class="chips" style="margin-top:12px"><span class="chip blue">${t('destinationCode')}: ${esc(p.destinationCode)}</span><span class="chip">${p.expectedBoxes} ${t('boxes')}</span></div><div class="progress"><span style="width:${pct}%"></span></div><div class="progress-line"><span>${t('receiveProgress')}</span><strong>${p.receivedBoxes} / ${p.expectedBoxes} ${t('boxes')}</strong></div><div class="scan-box"><input id="receive-scan" placeholder="${t('scanBox')}" onkeydown="if(event.key==='Enter')handleReceiveScan()"><button onclick="handleReceiveScan()" aria-label="${t('scan')}">${icon('scan')}</button></div>${state.receiveMessage?`<div class="scan-result ${state.receiveMessage.type}">${esc(state.receiveMessage.text)}</div>`:''}</section><section class="work-card"><div class="work-head"><div><h2>${t('receivedList')}</h2><p>${t('containerMeta',{container:p.container,boxes:received.length})}</p></div><span class="status good">${received.length}</span></div>${received.length?`<div class="list" style="margin-top:12px">${received.map(c=>`<div class="task-row"><span class="task-dot" style="background:var(--green)"></span><div class="task-main"><strong>${esc(c.boxNo)}</strong><p>${esc(c.sku)} · ${c.receivedQty} ${t('pieces')} · ${esc(c.ticketNo)}</p></div><span class="chip ${state.receiveStatus[c.id]==='damaged'?'red':'green'}">${t(state.receiveStatus[c.id]||'normal')}</span></div>`).join('')}</div>`:`<div class="empty">${t('empty')}</div>`}</section><div class="bottom-bar"><button class="btn primary" onclick="navigate('pallet')">${t('goPallet')}</button></div>`,t('receive'),`${esc(p.id)} · ${esc(p.destinationCode)}`,'plans');}
function handleReceiveScan(){const input=document.getElementById('receive-scan');const value=(input?.value||'').trim();if(!value){state.receiveMessage={type:'error',text:t('required')};render();return}const p=selectedPlan();const item=cargo.find(c=>c.planNo===p.id&&(c.boxNo===value||c.container===value));if(!item){state.receiveMessage={type:'error',text:t('notFound')};render();return}if(item.received){state.receiveMessage={type:'error',text:t('duplicate')};render();return}item.received=true;item.receivedQty=item.qty;p.receivedBoxes=Math.min(p.expectedBoxes,p.receivedBoxes+1);p.status=p.receivedBoxes>=p.expectedBoxes?'待打托':'待卸货';state.receiveMessage={type:'good',text:`${t('scanSuccess')}: ${item.boxNo}`};render();}
function canMergePallet(pallet,cargo){
return !pallet.destinationCode || ((!cargo.destinationCode || pallet.destinationCode===cargo.destinationCode) && (!pallet.destinationAddress || pallet.destinationAddress===cargo.destinationAddress));
}
function startPlanPallet(planId){state.selectedPlanId=planId;state.palletMode='palletize';state.pallet=null;state.palletMessage=null;resetPalletTypeInputs();state.page='pallet-list';render();}
function startConsolidation(){state.palletMode='consolidate';state.pallet=null;state.palletMessage=null;state.photoPalletId='';resetPalletTypeInputs();state.page='pallet-list';render();}
function plannedPalletNo(plan,index){return `${plan.container}-TC-${String(index+1).padStart(3,'0')}`;}
function palletWeight(pallet){return pallet.items.reduce((sum,item)=>sum+Number(item.weight||0),0);}
function newGeneratedPalletLabel(){const plan=selectedPlan();const used=state.pallets.filter(pallet=>pallet.mode==='palletize'&&pallet.planId===plan.id).map(pallet=>pallet.id);const nextIndex=Array.from({length:plan.expectedPallets},(_,index)=>index).find(index=>!used.includes(plannedPalletNo(plan,index)));if(nextIndex===undefined){toast(t('palletTargetReached'),'bad');return}selectPallet(plannedPalletNo(plan,nextIndex),plan.id);}
function palletListView(){const plan=selectedPlan();const created=state.pallets.filter(pallet=>pallet.mode==='palletize'&&pallet.planId===plan.id&&pallet.items?.length>0);const progress=`${created.length} / ${plan.expectedPallets} ${t('palletCount')}`;return layout(`<div class="toolbar pallet-list-toolbar"><span class="subtle">${t('palletProgress')}: ${progress}</span></div>${created.length?`<div class="list">${created.map(pallet=>`<article class="list-card"><div class="list-top"><div><div class="list-title">${esc(pallet.id)}</div><div class="list-meta">${esc(plan.id)} · ${esc(pallet.container||plan.container)}</div></div></div><div class="list-grid"><div><span>${t('destination')}</span><strong>${esc(pallet.destinationCode||plan.destinationCode)}</strong></div><div><span>${t('cartonCount')}</span><strong>${pallet.items.length}</strong></div><div><span>${t('weight')}</span><strong>${palletWeight(pallet).toFixed(1)} kg</strong></div></div><div class="card-actions"><button class="btn ghost" onclick="openPalletPhotos('${pallet.id}','${plan.id}')">${t('photoDetails')}</button><button class="btn primary" onclick="selectPallet('${pallet.id}','${plan.id}')">${t('directEnter')}</button></div></article>`).join('')}</div>`:`<div class="empty">${t('empty')}</div>`}<div class="bottom-bar pallet-list-footer"><div><button class="btn secondary" onclick="newGeneratedPalletLabel()">${t('generatePalletLabel')}</button><button class="btn primary" onclick="startPallet()">${t('palletizeFooter')}</button></div></div>`,t('palletList'),`${plan.id} · ${t('palletListDesc')}`,'plans');}
function selectPallet(palletId,planId){const saved=state.pallets.find(x=>x.id===palletId);if(planId)state.selectedPlanId=planId;const consolidating=state.palletMode==='consolidate';resetPalletTypeInputs();if(saved?.orderType)state.palletOrderType=saved.orderType;state.pallet=saved?{...JSON.parse(JSON.stringify(saved)),persisted:true}:{id:palletId,mode:consolidating?'consolidate':'palletize',planId:consolidating?null:state.selectedPlanId,targetPallets:consolidating?null:selectedPlan().expectedPallets,destinationCode:'',destinationAddress:'',items:[]};if(saved?.selectedPalletLabels)state.fbaPalletLabels=[...saved.selectedPalletLabels];if(saved?.dropshipPalletLabel)state.dropshipPalletLabel=saved.dropshipPalletLabel;state.palletMessage=null;state.page='pallet';render();}
function overseasPalletDetails(){return `<section class="work-card pallet-items-card"><div class="work-head"><div><h2>${t('items')}</h2><p>${t('trace')}</p></div></div>${state.pallet.items.length?`<div class="table-wrap"><table class="table"><thead><tr><th>${t('boxNo')}</th><th>${t('sku')}</th><th>${t('customer')}</th><th>${t('planNo')}</th><th>${t('qty')}</th><th></th></tr></thead><tbody>${state.pallet.items.map((item,index)=>`<tr><td>${esc(item.boxNo)}</td><td>${esc(item.sku)}</td><td>${esc(item.customer)}</td><td>${esc(item.planNo)}</td><td>${item.qty}</td><td><button class="btn danger" style="min-height:44px;padding:0 8px" onclick="removePalletItem(${index})">${t('remove')}</button></td></tr>`).join('')}</tbody></table></div>`:`<div class="empty">${t('emptyPallet')}</div>`}</section>`;}
function toggleFbaPalletLabel(palletId){const occupied=state.pallets.some(pallet=>pallet.id===palletId);if(state.pallet?.persisted||occupied)return;const selected=state.fbaPalletLabels.includes(palletId);state.fbaPalletLabels=selected?state.fbaPalletLabels.filter(id=>id!==palletId):[...state.fbaPalletLabels,palletId];state.palletTypeMessage=null;state.fbaPalletMessage=null;render();}
function fbaPalletLabelSelector(plan){const labels=Array.from({length:plan.expectedPallets},(_,index)=>plannedPalletNo(plan,index));const readonly=Boolean(state.pallet?.persisted);return `<section class="work-card fba-pallet-selector"><div class="work-head"><div><h2>${t('selectPalletLabels')}</h2><p>${t('palletLabel')} · ${t('palletStatus')}</p></div><span class="status blue">${state.fbaPalletLabels.length}</span></div><div class="fba-pallet-options">${labels.map(palletId=>{const selected=state.fbaPalletLabels.includes(palletId);const occupied=state.pallets.some(pallet=>pallet.id===palletId);const disabled=readonly||occupied;return `<button type="button" class="fba-pallet-choice ${selected?'selected':''}" aria-pressed="${selected}" ${disabled?'disabled aria-disabled="true"':''} onclick="toggleFbaPalletLabel('${esc(palletId)}')">${esc(palletId)}${selected?' ✓':''}</button>`}).join('')}</div>${state.fbaPalletMessage?`<div class="scan-result ${state.fbaPalletMessage.type}">${esc(state.fbaPalletMessage.text)}</div>`:''}</section>`;}
function palletView(){
const p=selectedPlan();
const consolidating=state.palletMode==='consolidate';
const done=consolidating?0:state.pallets.filter(x=>x.mode==='palletize'&&x.planId===p.id).length;
const targetReached=!consolidating&&done>=p.expectedPallets;
const candidates=cargo.filter(c=>c.received&&!c.palletNo&&(consolidating||c.planNo===p.id));
const title=consolidating?t('mergeOnly'):t('pallet');
const description=consolidating?t('mergeDesc'):t('palletDesc');
if(!state.pallet){return layout(`<section class="work-card"><div class="work-head"><div><h2>${title}</h2><p>${description}</p></div><span class="status ${targetReached?'good':'blue'}">${consolidating?candidates.length:`${done} / ${p.expectedPallets}`}</span></div><div class="list" style="margin-top:12px">${candidates.map(c=>`<div class="task-row"><span class="task-dot"></span><div class="task-main"><strong>${esc(c.boxNo)} · ${esc(c.sku)}</strong><p>${esc(c.customer)} · ${esc(c.planNo)} · ${esc(c.destinationCode)}</p></div><button onclick="startPallet('${c.id}')" ${targetReached?'disabled':''}>${t('choose')}</button></div>`).join('')||`<div class="empty">${targetReached?t('taskDone'):t('empty')}</div>`}</div></section><div class="bottom-bar"><button class="btn primary" onclick="startPallet()" ${targetReached?'disabled':''}>${t('newPallet')}</button></div>`,title,description,consolidating?'workbench':'plans');}
const showDetails=state.palletOrderType==='overseas';
const scanHint=state.palletOrderType==='dropship'?t('scanPalletLabel'):t('scanBoxSku');
const progress=`${t('palletProgress')}: ${done} / ${p.expectedPallets} ${t('palletCount')}`;
const cargoInfo=`<section class="work-card cargo-info-card"><div class="work-head"><div><h2>${t('cargoInfo')}</h2><p>${state.pallet.destinationCode?`${t('lockedDest')}: ${esc(state.pallet.destinationCode)}`:description}</p></div><span class="status blue">${state.pallet.items.length} ${t('items')}</span></div><div class="detail-list"><div class="detail-row"><span>${t('inboundCode')}</span><strong>${esc(p.inboundCode||p.id||'—')}</strong></div><div class="detail-row"><span>${t('destination')}</span><strong>${esc(state.pallet.destinationCode||'—')}</strong></div><div class="detail-row"><span>${t('cartonCount')}</span><strong>${state.pallet.items.length}</strong></div></div></section>`;
const scanCard=`<section class="work-card pallet-type-scan"><div class="work-head"><div><h2>${esc(scanHint)}</h2></div></div><div class="scan-box"><input id="pallet-scan" placeholder="${esc(scanHint)}" onkeydown="if(event.key==='Enter')handlePalletScan()"><button onclick="handlePalletScan()" aria-label="${t('scan')}">${icon('scan')}</button></div>${state.palletMessage?`<div class="scan-result ${state.palletMessage.type}">${esc(state.palletMessage.text)}</div>`:''}${state.palletOrderType==='dropship'&&state.dropshipScanMessage?`<div class="scan-result ${state.dropshipScanMessage.type}">${esc(state.dropshipScanMessage.text)}</div>`:''}</section>`;
return layout(`${palletOrderTabs()}<div class="scan-result info pallet-progress-card">${progress}</div>${cargoInfo}${scanCard}${state.palletOrderType==='fba'?fbaPalletLabelSelector(p):''}${showDetails?overseasPalletDetails():''}<div class="bottom-bar"><div style="display:flex;gap:8px"><button class="btn ghost" style="flex:1" onclick="newPallet()">${t('newPallet')}</button><button class="btn primary" style="flex:2" onclick="submitPallet()" ${state.pallet.items.length?'':'disabled'}>${t('submitPallet')}</button></div></div>`,title,consolidating?t('sameDest'):`${p.id} · ${done} / ${p.expectedPallets} ${t('palletCount')}`,consolidating?'workbench':'plans');
}
function startPallet(itemId){const p=selectedPlan();const item=itemId?cargo.find(c=>c.id===itemId):null;const consolidating=state.palletMode==='consolidate';const done=state.pallets.filter(x=>x.mode==='palletize'&&x.planId===p.id).length;if(!consolidating&&done>=p.expectedPallets){toast(t('palletTargetReached'),'bad');return}if(item&&!consolidating&&item.planNo!==p.id){toast(t('notFound'),'bad');return}resetPalletTypeInputs();state.pallet={id:'',mode:consolidating?'consolidate':'palletize',planId:consolidating?null:p.id,targetPallets:consolidating?null:p.expectedPallets,destinationCode:item?.destinationCode||'',destinationAddress:item?.destinationAddress||'',items:[]};state.palletMessage=null;if(item)addPalletItem(item);state.page='pallet';render();}
function newPallet(){if(state.pallet?.items?.length){showModal(t('newPalletConfirm'),()=>{closeModal();startPallet();});return}startPallet();}
function openPalletScanModal(){const root=document.getElementById('modal-root');const value=(document.getElementById('pallet-scan')?.value||'').trim();root.innerHTML=`<div class="modal-backdrop"><div class="modal" role="dialog" aria-modal="true" aria-labelledby="pallet-scan-modal-title"><h2 id="pallet-scan-modal-title">${t('addCargo')}</h2><div class="detail-row"><span>${t('sku')} / ${t('boxNo')}</span><strong>${esc(value||'—')}</strong></div><div class="field"><label for="pallet-modal-qty">${t('scanCartonQty')}</label><input id="pallet-modal-qty" type="number" min="1" step="1" value="1" onkeydown="if(event.key==='Enter')confirmPalletScanModal()"></div><div class="detail-row"><span>${t('remainingSkuCartons')}</span><strong id="pallet-modal-remaining">0</strong></div><div class="modal-actions"><button class="btn ghost" onclick="closeModal()">${t('cancel')}</button><button class="btn primary" onclick="confirmPalletScanModal()">${t('yes')}</button></div></div></div>`;updatePalletScanRemaining();requestAnimationFrame(()=>document.getElementById('pallet-modal-qty')?.focus());}
function updatePalletScanRemaining(){const value=(document.getElementById('pallet-scan')?.value||'').trim();const remaining=cargo.filter(item=>(item.sku===value||item.boxNo===value)&&item.received&&!item.palletNo&&!state.pallet.items.some(current=>current.id===item.id)&&(state.palletMode==='consolidate'||item.planNo===state.selectedPlanId)).length;const output=document.getElementById('pallet-modal-remaining');if(output)output.textContent=remaining;const qty=document.getElementById('pallet-modal-qty');if(qty)qty.max=String(Math.max(1,remaining));}
function confirmPalletScanModal(){const value=(document.getElementById('pallet-scan')?.value||'').trim();const qty=Number(document.getElementById('pallet-modal-qty')?.value);if(!value){toast(t('required'),'bad');return}if(!Number.isInteger(qty)||qty<1){toast(t('required'),'bad');return}closeModal();handlePalletScan();}
function handlePalletScan(){
const input=document.getElementById('pallet-scan');
const value=(input?.value||'').trim();
if(!value){state.palletMessage={type:'error',text:t('required')};render();return}
if(state.palletMode==='palletize'&&state.palletOrderType==='dropship'){
const pallet=state.pallets.find(item=>String(item.id).toLowerCase()===value.toLowerCase());
if(!pallet){state.dropshipScanMessage={type:'error',text:t('unknownPalletScan')};render();return}
state.dropshipPalletLabel=pallet.id;
if(!state.pallet.items.length){state.selectedPlanId=pallet.planId||state.selectedPlanId;state.pallet={...state.pallet,planId:pallet.planId||state.pallet.planId,destinationCode:pallet.destinationCode||'',destinationAddress:pallet.destinationAddress||'',items:JSON.parse(JSON.stringify(pallet.items||[]))}}
state.palletMessage=null;
state.dropshipScanMessage={type:'good',text:`${t('scanSuccess')}: ${pallet.id}`};
render();
return;
}
const item=cargo.find(c=>(c.boxNo===value||c.sku===value)&&c.received&&(state.palletMode==='consolidate'||c.planNo===state.selectedPlanId));
if(!item){state.palletMessage={type:'error',text:t('notFound')};render();return}
if(state.pallet.items.some(x=>x.id===item.id)||item.palletNo){state.palletMessage={type:'error',text:t('duplicate')};render();return}
if(!canMergePallet(state.pallet,item)){state.palletMessage={type:'error',text:t('mergeBlocked')};render();return}
addPalletItem(item);
state.palletMessage={type:'good',text:`${t('mergeOk')}: ${item.boxNo}`};
render();
}
function addPalletItem(item){const isMerged=state.pallet.items.length>0;if(!state.pallet.destinationCode){state.pallet.destinationCode=item.destinationCode;state.pallet.destinationAddress=item.destinationAddress}state.pallet.items.push({...item,isMerged,mergeFlag:isMerged?t('yesMerge'):t('noMerge')});}
function removePalletItem(index){const removed=state.pallet.items.splice(index,1)[0];const source=cargo.find(x=>x.id===removed.id);if(source&&source.palletNo===state.pallet.id)source.palletNo='';if(state.pallet.persisted){const savedIndex=state.pallets.findIndex(x=>x.id===state.pallet.id);if(savedIndex>=0){if(state.pallet.items.length){const updated=JSON.parse(JSON.stringify(state.pallet));delete updated.persisted;state.pallets[savedIndex]=updated}else{state.pallets.splice(savedIndex,1);state.pallet.persisted=false}}}if(!state.pallet.items.length){state.pallet.destinationCode='';state.pallet.destinationAddress=''}state.palletMessage={type:'info',text:`${t('remove')}: ${removed.boxNo}`};render();}
function submitPallet(){
if(!state.pallet?.items.length){state.palletMessage={type:'error',text:t('emptyPallet')};render();return}
if(state.pallet.mode==='palletize'&&state.palletOrderType==='dropship'&&!state.dropshipPalletLabel){state.dropshipScanMessage={type:'error',text:t('scanPalletLabel')};render();return}
if(state.pallet.mode==='palletize'&&state.palletOrderType==='fba'&&!state.fbaPalletLabels.length){state.fbaPalletMessage={type:'error',text:t('selectAtLeastOnePallet')};render();return}
const p=selectedPlan();
const done=state.pallets.filter(x=>x.mode==='palletize'&&x.planId===p.id).length;
if(state.pallet.mode==='palletize'&&done>=p.expectedPallets){state.palletMessage={type:'error',text:t('palletTargetReached')};render();return}
const sourcePlan=state.pallet.mode==='palletize'?p:plans.find(x=>x.id===state.pallet.items[0]?.planNo);
const prefix=sourcePlan?.container||'D0000';
const sequence=state.pallets.filter(x=>x.container===prefix).length+1;
if(state.pallet.mode==='palletize'&&state.palletOrderType==='fba'){
const remaining=Math.max(0,p.expectedPallets-done);
const selected=[...new Set(state.fbaPalletLabels)].filter(id=>!state.pallets.some(pallet=>pallet.id===id)).slice(0,remaining);
if(!selected.length){state.fbaPalletMessage={type:'error',text:t('selectAtLeastOnePallet')};render();return}
const records=selected.map((id,index)=>({...JSON.parse(JSON.stringify(state.pallet)),id,container:prefix,orderType:'fba',selectedPalletLabels:[...selected],items:state.pallet.items.filter((item,itemIndex)=>itemIndex%selected.length===index),persisted:true}));
records.forEach(record=>record.items.forEach(item=>{const source=cargo.find(c=>c.id===item.id);if(source)source.palletNo=record.id}));
state.pallets.push(...records);
p.receivedPallets=Math.min(p.expectedPallets,done+records.length);
state.pallet=records[0];
const submittedLabels=records.map(record=>record.id).join('、');
const submittedItems=records.reduce((count,record)=>count+record.items.length,0);
resetPalletTypeInputs();
state.palletMessage={type:'good',text:`${t('submitted')}: ${submittedLabels}`};
showModal(`${t('submitted')}: ${submittedLabels} · ${t('trace')}: ${submittedItems} ${t('items')}`,()=>{state.pallet=null;closeModal();state.page='plans';render();});
return;
}
const no=state.pallet.id||state.pallet.selectedPalletLabels?.[0]||`${prefix}-TC-${String(sequence).padStart(3,'0')}`;
state.pallet.id=no;
state.pallet.container=prefix;
state.pallet.items.forEach(x=>{const source=cargo.find(c=>c.id===x.id);if(source)source.palletNo=no});
state.pallet.orderType=state.palletOrderType;
state.pallet.dropshipPalletLabel=state.dropshipPalletLabel;
state.pallet.persisted=true;
state.pallets.push(JSON.parse(JSON.stringify(state.pallet)));
if(state.pallet.mode==='palletize')p.receivedPallets=Math.min(p.expectedPallets,done+1);
resetPalletTypeInputs();
state.palletMessage={type:'good',text:`${t('submitted')}: ${no}`};
showModal(`${t('submitted')} ${no}<br><small>${t('trace')}: ${state.pallet.items.length} ${t('items')}</small>`,()=>{const mode=state.pallet.mode;state.pallet=null;closeModal();state.page=mode==='palletize'?'plans':'pallet-list';render();});
}
function pickView(){const task=pickTasks.find(x=>x.id===state.pickTaskId)||pickTasks.find(x=>!x.done)||pickTasks[0];if(!state.pickTaskId)state.pickTaskId=task.id;const done=task.items.filter(x=>x.status||x.picked>=x.qty).length;const total=task.items.length;const pickedPallets=task.done?task.palletCount:Math.min(task.palletCount,Math.floor(done/total*task.palletCount));return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('pick')} · ${esc(task.id)}</h2><p>${esc(task.customer)} · ${esc(task.destination)}</p></div><span class="status wait">${task.done?t('taskDone'):t('pending')}</span></div><div class="detail-list"><div class="detail-row"><span>${t('palletQty')}</span><strong>${pickedPallets} / ${task.palletCount} ${t('palletCount')}</strong></div><div class="detail-row"><span>${t('linkedOrder')}</span><strong>${esc(task.orderInfo)}</strong></div><div class="detail-row"><span>${t('boxes')}</span><strong>${task.boxes}</strong></div></div><div class="progress"><span style="width:${task.palletCount?Math.round(pickedPallets/task.palletCount*100):0}%"></span></div><div class="progress-line"><span>${t('pickProgress')}</span><strong>${pickedPallets} / ${task.palletCount} ${t('palletCount')}</strong></div><div class="scan-box"><input id="pick-scan" placeholder="${t('reviewScan')}" onkeydown="if(event.key==='Enter')handlePickScan()"><button onclick="handlePickScan()" aria-label="${t('scan')}">⌗</button></div>${state.pickMessage?`<div class="scan-result ${state.pickMessage.type}">${esc(state.pickMessage.text)}</div>`:''}</section><section class="work-card"><div class="list">${task.items.map((x,i)=>`<div class="list-card" style="padding:12px"><div class="list-top"><div><div class="list-title">${esc(x.sku)}</div><div class="list-meta">${x.picked} / ${x.qty} ${t('pieces')}${x.skipReason?` · ${esc(x.skipReason)}`:''}</div></div><span class="status ${x.status==='picked'?'good':x.status==='skipped'?'bad':x.status==='partial'?'blue':'wait'}">${t(x.status||'pending')}</span></div><div class="card-actions" style="justify-content:stretch;flex-wrap:wrap"><button class="btn secondary" onclick="pickOne(${i})">${t('confirmPick')}</button><button class="btn ghost" onclick="openPartialPick(${i})">${t('partialPick')}</button><button class="btn danger" onclick="skipPick(${i})">${t('skipPick')}</button>${x.status?`<button class="btn ghost" onclick="undoPick(${i})">${t('undo')}</button>`:''}</div></div>`).join('')}</div></section><div class="bottom-bar"><button class="btn primary" onclick="submitPick()" ${done===total?'':'disabled'}>${t('submitPick')}</button></div>`,t('pick'),t('pickDesc'),'workbench');}
function handlePickScan(){const value=(document.getElementById('pick-scan')?.value||'').trim();if(!value){state.pickMessage={type:'error',text:t('required')};render();return}const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items.find(x=>x.sku===value);if(!item){state.pickMessage={type:'error',text:t('unknownScan')};render();return}if(item.picked>=item.qty){state.pickMessage={type:'error',text:t('complete')};render();return}item.picked++;state.pickMessage={type:'good',text:`${t('scanSuccess')}: ${value}`};render();}
function pickOne(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];item.picked=item.qty;item.status='picked';delete item.skipReason;render();}
function openPartialPick(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];document.getElementById('modal-root').innerHTML=`<div class="modal-backdrop"><div class="modal"><h2>${t('partialPick')} · ${esc(item.sku)}</h2><p>${t('qty')}: 1 - ${item.qty-1}</p><div class="field"><input id="partial-qty" type="number" min="1" max="${item.qty-1}" value="${Math.max(1,Math.floor(item.qty/2))}"></div><div class="modal-actions"><button class="btn ghost" onclick="closeModal()">${t('cancel')}</button><button class="btn primary" onclick="confirmPartialPick(${index})">${t('yes')}</button></div></div></div>`;}
function confirmPartialPick(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];const qty=Number(document.getElementById('partial-qty')?.value);if(!Number.isInteger(qty)||qty<=0||qty>=item.qty){toast(`${t('qty')}: 1 - ${item.qty-1}`,'bad');return}item.picked=qty;item.status='partial';delete item.skipReason;closeModal();render();}
function skipPick(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];document.getElementById('modal-root').innerHTML=`<div class="modal-backdrop"><div class="modal"><h2>${t('skipReasonTitle')} · ${esc(item.sku)}</h2><div class="field"><label>${t('skipReasonTitle')}</label><select id="skip-reason"><option value="notFound">${t('skipReasonNotFound')}</option><option value="outOfStock">${t('skipReasonOutOfStock')}</option><option value="other">${t('skipReasonOther')}</option></select></div><div class="modal-actions"><button class="btn ghost" onclick="closeModal()">${t('cancel')}</button><button class="btn primary" onclick="confirmSkipPick(${index})">${t('yes')}</button></div></div></div>`;}
function confirmSkipPick(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];const reason=document.getElementById('skip-reason')?.value||'other';const labels={notFound:t('skipReasonNotFound'),outOfStock:t('skipReasonOutOfStock'),other:t('skipReasonOther')};item.picked=0;item.status='skipped';item.skipReason=labels[reason];closeModal();render();}
function undoPick(index){const task=pickTasks.find(x=>x.id===state.pickTaskId);const item=task.items[index];item.picked=0;delete item.status;delete item.skipReason;render();}
function submitPick(){const task=pickTasks.find(x=>x.id===state.pickTaskId);if(task.items.some(x=>!x.status&&x.picked<x.qty)){toast(t('pending'),'info');return}task.done=true;toast(t('taskDone'),'good');state.page='workbench';render();}
function reviewView(){const task=pickTasks.find(x=>x.id===state.pickTaskId)||pickTasks.find(x=>!x.done)||pickTasks[0];const scanned=state.reviewScanned.length;const target=30;const pct=Math.min(100,Math.round(scanned/target*100));return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('review')}</h2><p>${t('reviewDesc')}</p></div><span class="status blue">${scanned}</span></div><div class="detail-list"><div class="detail-row"><span>${t('driver')}</span><strong>${esc(task.driver)}</strong></div><div class="detail-row"><span>${t('plateNo')}</span><strong>${esc(task.plateNo)}</strong></div><div class="detail-row"><span>${t('vehicleType')}</span><strong>${t('vehicleTypeValue')}</strong></div><div class="detail-row"><span>${t('reviewPallets')}</span><strong>${t('palletReferenceValue')}</strong></div></div><div class="scan-box"><input id="review-scan" placeholder="${t('reviewScanPallet')}" onkeydown="if(event.key==='Enter')handleReviewScan()"><button onclick="handleReviewScan()" aria-label="${t('scan')}">⌗</button></div><div class="progress"><span style="width:${pct}%"></span></div><div class="progress-line"><span>${t('reviewPalletProgress')}</span><strong>${scanned} / ${target} ${t('palletCount')}</strong></div>${state.reviewScanned.length?`<div class="list">${state.reviewScanned.map(x=>`<div class="task-row"><span class="task-dot" style="background:var(--green)"></span><div class="task-main"><strong>${esc(x)}</strong><p>${t('scanSuccess')}</p></div><span class="status good">OK</span></div>`).join('')}</div>`:`<div class="empty">${t('empty')}</div>`}</section><div class="bottom-bar"><button class="btn primary" onclick="completeReview()" ${state.reviewScanned.length?'':'disabled'}>${t('confirmReview')}</button></div>`,t('review'),t('reviewDesc'),'workbench');}
function handleReviewScan(){const value=(document.getElementById('review-scan')?.value||'').trim();if(!value){toast(t('required'),'bad');return}if(!state.reviewScanned.includes(value))state.reviewScanned.push(value);render();}
function completeReview(){state.reviewScanned=[];toast(t('taskDone'),'good');state.page='workbench';render();}
const HANDOVER_PHOTO_STEPS=['licensePlatePhoto','vehicleCabPhoto','driverLicense','loadingSealPhoto','lockSealPhoto','bolDriverSigned','amazonAppointment'];
function handoverView(){const completed=HANDOVER_PHOTO_STEPS.filter(key=>state.handoverPhotos[key]).length;const totalPallets=pickTasks.reduce((sum,task)=>sum+Number(task.palletCount||0),0);const totalBoxes=pickTasks.reduce((sum,task)=>sum+Number(task.boxes||0),0);const totalWeight=pickTasks.reduce((sum,task)=>sum+Number(task.totalWeightLb||0),0);return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('handover')}</h2><p>${t('handoverDesc')}</p></div><span class="status ${state.handoverDone?'good':completed===7?'blue':'wait'}">${state.handoverDone?t('complete'):`${completed} / 7`}</span></div><div class="detail-list"><div class="detail-row"><span>${t('carrier')}</span><strong>Pacific Linehaul</strong></div><div class="detail-row"><span>${t('driver')}</span><strong>Michael Chen</strong></div><div class="detail-row"><span>${t('vehicle')}</span><strong>CA 8KX-217</strong></div><div class="detail-row"><span>${t('palletQty')}</span><strong>${totalPallets} ${t('palletCount')}</strong></div><div class="detail-row"><span>${t('totalBoxes')}</span><strong>${totalBoxes}</strong></div><div class="detail-row"><span>${t('totalWeight')}</span><strong>${totalWeight.toFixed(1)} lb</strong></div></div><div class="progress"><span style="width:${completed/7*100}%"></span></div><div class="progress-line"><span>${t('handoverPhotos')}</span><strong>${completed} / 7</strong></div></section><section class="work-card"><div class="photo-grid">${HANDOVER_PHOTO_STEPS.map(key=>`<div class="photo-step ${state.handoverPhotos[key]?'done':''}"><div class="photo-preview">${state.handoverPhotos[key]?.startsWith('data:')?`<img src="${state.handoverPhotos[key]}" alt="${t(key)}">`:state.handoverPhotos[key]?icon('check'):icon('camera')}</div><strong>${t(key)}</strong><label>${state.handoverPhotos[key]?t('retake'):t('takePhoto')}<input type="file" accept="image/*" capture="environment" onchange="handleHandoverPhoto('${key}',this)"></label></div>`).join('')}</div></section>${completed<7?`<div class="scan-result error">${t('handoverPhotosRequired')}</div>`:''}<div class="bottom-bar"><button class="btn primary" onclick="completeHandover()" ${state.handoverDone||completed<7?'disabled':''}>${t('handoverDone')}</button></div>`,t('handover'),t('handoverDesc'),'workbench');}
function handleHandoverPhoto(key,input){const file=input.files&&input.files[0];if(!file)return;const reader=new FileReader();reader.onload=()=>{state.handoverPhotos[key]=reader.result;render()};reader.readAsDataURL(file);}
function completeHandover(){if(HANDOVER_PHOTO_STEPS.some(key=>!state.handoverPhotos[key])){toast(t('handoverPhotosRequired'),'bad');return}state.handoverDone=true;toast(t('taskDone'),'good');render();}
function relabelView(){return layout(`<div class="toolbar"><span class="subtle">${plans.length} ${t('orderList')}</span></div><div class="list">${plans.map(p=>{const done=Boolean(state.relabelDoneOrders[p.id]);return `<article class="list-card"><div class="list-top"><div><div class="list-title">${esc(p.id)}</div><div class="list-meta">${t('container')}: ${esc(p.container)} · ${esc(p.customer)}</div></div><span class="status ${done?'good':'wait'}">${done?t('complete'):t('pending')}</span></div><div class="list-grid"><div><span>${t('inboundCode')}</span><strong>${esc(p.inboundCode||p.tickets)}</strong></div><div><span>${t('palletCount')}</span><strong>${p.expectedPallets} ${t('palletCount')}</strong></div><div><span>${t('relabelStatus')}</span><strong>${done?t('relabelDone'):t('pending')}</strong></div></div><div class="card-actions"><button class="btn ${done?'ghost':'primary'}" onclick="openRelabelPhoto('${p.id}')" ${done?'disabled':''}>${done?t('complete'):t('submitRelabel')}</button></div></article>`}).join('')}</div>`,t('relabelTitle'),t('relabelHint'),'workbench');}
function openRelabelPhoto(planId){if(!plans.some(p=>p.id===planId))return;state.selectedRelabelId=planId;state.page='relabel-photo';render();}
function selectedRelabelOrder(){return plans.find(p=>p.id===state.selectedRelabelId);}
function relabelPhotoView(){const p=selectedRelabelOrder();if(!p)return relabelView();const photo=state.relabelPhotos[p.id];return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('relabelPhotoTitle')}</h2><p>${esc(p.id)} · ${esc(p.customer)}</p></div><span class="status wait">${t('pending')}</span></div><div class="detail-list"><div class="detail-row"><span>${t('container')}</span><strong>${esc(p.container)}</strong></div><div class="detail-row"><span>${t('inboundCode')}</span><strong>${esc(p.inboundCode||p.tickets)}</strong></div><div class="detail-row"><span>${t('palletCount')}</span><strong>${p.expectedPallets} ${t('palletUnit')}</strong></div></div></section><section class="work-card"><div class="photo-step ${photo?'done':''}"><div class="photo-preview" style="height:180px">${photo?.startsWith('data:')?`<img src="${photo}" alt="${t('relabelPhotoTitle')}">`:icon('camera')}</div><strong>${t('relabelPhotoHint')}</strong><label>${photo?t('retake'):t('takePhoto')}<input type="file" accept="image/*" capture="environment" onchange="handleRelabelPhoto(this)"></label></div></section><div class="bottom-bar"><button class="btn primary" onclick="submitRelabelPhoto()" ${!photo?'disabled':''}>${t('submitRelabelPhoto')}</button></div>`,t('relabelPhotoTitle'),t('relabelPhotoHint'),'relabel');}
function handleRelabelPhoto(input){const order=selectedRelabelOrder();const file=input.files&&input.files[0];if(!order||!file)return;const reader=new FileReader();reader.onload=()=>{state.relabelPhotos[order.id]=reader.result;render()};reader.onerror=()=>toast(t('relabelPhotoRequired'),'bad');reader.readAsDataURL(file);}
function submitRelabelPhoto(){const order=selectedRelabelOrder();if(!order){state.page='relabel';render();return}if(!state.relabelPhotos[order.id]){toast(t('relabelPhotoRequired'),'bad');return}state.relabelDoneOrders[order.id]=true;state.relabelMessage={type:'good',text:`${t('relabelDone')}: ${order.id}`};state.page='relabel';toast(state.relabelMessage.text,'good');render();}
function exceptionView(){return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('exceptionTitle')}</h2></div><span class="status wait">${t('pending')}</span></div><div class="scan-box"><input id="exception-scan" placeholder="${t('scanBox')}" onkeydown="if(event.key==='Enter')handleExceptionScan()"><button onclick="handleExceptionScan()" aria-label="${t('scan')}">${icon('scan')}</button></div><div class="field"><label>${t('exceptionRef')}</label><input id="exception-ref" value="${esc(state.exceptionRefValue||'')}" placeholder="OUT-260819-004 / FBA-ONT8-260819-004 / D2222-TC-001"></div><div class="field"><label>${t('exceptionType')}</label><select id="exception-type"><option value="">${t('selectException')}</option><option value="damage">${t('damaged')}</option><option value="short">${t('short')}</option><option value="hold">${t('hold')}</option></select></div><div class="field"><label>${t('exceptionDesc')}</label><input id="exception-desc" maxlength="120" placeholder="${t('exceptionDesc')}"></div>${state.exceptionMessage?`<div class="scan-result ${state.exceptionMessage.type}">${esc(state.exceptionMessage.text)}</div>`:''}</section><div class="bottom-bar"><button class="btn primary" onclick="submitException()">${t('submitException')}</button></div>`,t('exceptionTitle'),t('exceptionDesc'),'workbench');}
function handleExceptionScan(){const value=(document.getElementById('exception-scan')?.value||'').trim();if(!value){state.exceptionMessage={type:'error',text:t('required')};render();return}state.exceptionRefValue=value;state.exceptionMessage={type:'good',text:`${t('scanSuccess')}: ${value}`};render();}
function submitException(){const type=(document.getElementById('exception-type')?.value||'').trim();const ref=(document.getElementById('exception-ref')?.value||'').trim();const desc=(document.getElementById('exception-desc')?.value||'').trim();if(!type||!ref||!desc){state.exceptionMessage={type:'error',text:t('required')};render();return}state.exceptionRefValue=ref;state.exceptionMessage={type:'good',text:`${t('exceptionDone')}: ${ref}`};render();}
function inventoryView(){const q=state.inventoryQuery.toLowerCase();const rows=inventory.filter(x=>!q||[x.palletNo,x.name].some(v=>String(v).toLowerCase().includes(q)));return layout(`<div class="search-row"><input id="inventory-search" value="${esc(state.inventoryQuery)}" aria-label="${t('inventory')}" placeholder="${t('queryHint')}" onkeydown="if(event.key==='Enter')searchInventory()"><button type="button" onclick="searchInventory()">${t('search')}</button></div>${q?`<div class="card-actions" style="margin-bottom:10px"><button type="button" class="btn ghost" onclick="state.inventoryQuery='';render()">${t('clear')}</button></div>`:''}${rows.length?`<div class="inventory-results">${rows.map(x=>`<article class="inventory-card"><div class="inventory-card-head"><div><div class="inventory-card-title">${esc(x.name)}</div><div class="inventory-card-customer">${esc(x.customer)}</div></div><span class="status ${x.status==='在库'?'good':'wait'}">${esc(x.status==='在库'?t('inStock'):t('inTransit'))}</span></div><div class="inventory-facts"><div class="inventory-fact"><span>${t('palletNo')}</span><strong>${esc(x.palletNo)}</strong></div><div class="inventory-fact qty"><span>${t('qty')}</span><strong>${x.qty}</strong></div></div></article>`).join('')}</div>`:`<div class="empty">${t('noResult')}</div>`}`,t('inventory'),t('inventoryDesc'),'workbench');}
function searchInventory(){state.inventoryQuery=(document.getElementById('inventory-search')?.value||'').trim();render();}
function profileView(){return layout(`<section class="work-card"><div class="profile-grid"><div class="profile-line"><span>${t('operator')}</span><strong>${esc(state.user?.name)}</strong></div><div class="profile-line"><span>${t('role')}</span><strong>${esc(state.user?.role)}</strong></div><div class="profile-line"><span>${t('warehouse')}</span><strong>${esc(state.user?.warehouse)}</strong></div><div class="profile-line"><span>${t('shiftLabel')}</span><strong>${t('shift')}</strong></div><div class="profile-line"><span>${t('language')}</span><div class="lang-select">${[['zh','中文'],['en','English'],['es','Español']].map(([v,l])=>`<button type="button" class="${state.lang===v?'active':''}" aria-pressed="${state.lang===v}" onclick="setLang('${v}')">${l}</button>`).join('')}</div></div><div class="profile-line"><span>${t('version')}</span><strong>V1.0.0</strong></div></div></section><div class="bottom-bar"><button type="button" class="btn danger" onclick="logout()">${t('logout')}</button></div>`,t('profile'),t('sub'),'workbench');}
let modalReturnFocus=null;
function showModal(message,onConfirm){modalReturnFocus=document.activeElement;const root=document.getElementById('modal-root');root.innerHTML=`<div class="modal-backdrop"><div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title"><h2 id="modal-title">${t('details')}</h2><p>${esc(message)}</p><div class="modal-actions"><button type="button" class="btn ghost" onclick="closeModal()">${t('cancel')}</button><button type="button" class="btn primary" id="modal-confirm">${t('yes')}</button></div></div></div>`;const confirm=document.getElementById('modal-confirm');confirm.onclick=onConfirm;root.onkeydown=event=>{if(event.key==='Escape')closeModal()};requestAnimationFrame(()=>confirm.focus());}
function closeModal(){const root=document.getElementById('modal-root');root.innerHTML='';root.onkeydown=null;if(modalReturnFocus?.focus)modalReturnFocus.focus();modalReturnFocus=null;}
let toastTimer;function toast(message,type='info'){const root=document.getElementById('toast-root');root.innerHTML=`<div class="toast ${type}" role="status" aria-live="polite">${esc(message)}</div>`;clearTimeout(toastTimer);toastTimer=setTimeout(()=>root.innerHTML='',2600);}
function palletPhotoKey(palletId){return palletId||state.pallet?.id||`DRAFT-${state.selectedPlanId||'pallet'}`;}
function palletPhotoItems(palletId){return state.palletPhotos[palletPhotoKey(palletId)]||[];}
function openPalletPhotos(palletId,planId){if(planId)state.selectedPlanId=planId;state.photoPalletId=palletId;state.page='photo-detail';render();}
function handlePalletPhoto(input){const file=input.files&&input.files[0];if(!file)return;const key=palletPhotoKey(state.photoPalletId||state.pallet?.id);const reader=new FileReader();reader.onload=()=>{state.palletPhotos[key]??=[];state.palletPhotos[key].push(reader.result);state.photoPalletId=key;render()};reader.readAsDataURL(file);}
function photoCaptureButton(){return `<label class="btn primary photo-capture">${icon('camera')}<span>${t('takePhoto')}</span><input type="file" accept="image/*" capture="environment" onchange="handlePalletPhoto(this)"></label>`;}
function photoListMarkup(palletId){const photos=palletPhotoItems(palletId);return photos.length?`<div class="photo-grid">${photos.map((src,index)=>`<div class="photo-step done"><div class="photo-preview"><img src="${src}" alt="${t('photoItem')} ${index+1}"></div><strong>${t('photoItem')} ${index+1}</strong></div>`).join('')}</div>`:`<div class="empty">${t('noPhotos')}</div>`;}
function photoDetailView(){const key=state.photoPalletId||palletPhotoKey();const saved=state.pallets.find(x=>x.id===key);const plan=plans.find(x=>x.id===saved?.planId)||selectedPlan();return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('photoDetails')}</h2><p>${esc(key)} · ${esc(saved?.destinationCode||plan?.destinationCode||'—')}</p></div><span class="status blue">${palletPhotoItems(key).length}</span></div>${photoListMarkup(key)}<div class="card-actions">${photoCaptureButton()}</div></section>`,t('photoDetails'),t('palletPhotoHint'),'pallet-list');}
function injectPalletListPhotoButtons(){if(state.page!=='pallet-list')return;const consolidating=state.palletMode==='consolidate';const activePlans=consolidating?plans:[selectedPlan()];const slots=activePlans.flatMap(plan=>Array.from({length:plan.expectedPallets},(_,index)=>({plan,no:plannedPalletNo(plan,index)})));document.querySelectorAll('.list-card').forEach((card,index)=>{const slot=slots[index];const actions=card.querySelector('.card-actions');if(!slot||!actions||actions.querySelector('.photo-details-btn'))return;const button=document.createElement('button');button.className='btn ghost photo-details-btn';button.textContent=t('photoDetails');button.onclick=()=>openPalletPhotos(slot.no,slot.plan.id);actions.prepend(button);});}
function injectPalletPhotoFeatures(){if(state.page!=='pallet'||!state.pallet||state.palletOrderType!=='overseas')return;const root=document.querySelector('.content');if(!root||root.querySelector('.pallet-site-photos'))return;const key=palletPhotoKey();const section=document.createElement('section');section.className='work-card pallet-site-photos';section.innerHTML=`<div class="work-head"><div><h2>${t('sitePhotos')}</h2><p>${t('palletPhotoHint')}</p></div><span class="status blue">${palletPhotoItems(key).length}</span></div>${photoListMarkup(key)}<div class="card-actions">${photoCaptureButton()}</div>`;const bottom=root.querySelector('.bottom-bar');if(bottom)root.insertBefore(section,bottom);else root.appendChild(section);}
function injectReservedEntries(){if(state.page!=='workbench')return;const grids=document.querySelectorAll('.entry-grid');const grid=grids[grids.length-1];if(!grid)return;const more=grid.querySelectorAll('.entry')[1];if(more)more.style.display='none';grid.insertAdjacentHTML('beforeend',`<button class="entry relabel-entry" onclick="navigate('relabel')"><div class="entry-icon">${icon('relabel')}</div><h3>${t('relabel')}</h3><p>${t('relabelHint')}</p></button><button class="entry exception-entry" onclick="navigate('exception')"><div class="entry-icon">${icon('exception')}</div><h3>${t('exception')}</h3><p>${t('exceptionDesc')}</p></button>`);}
function injectInboundPalletUnits(){if(state.page!=='workbench')return;document.querySelectorAll('.task-strip .task-main p').forEach((el,index)=>{const p=plans.filter(x=>x.status==='待卸货')[index];if(p)el.textContent=`${p.customer} · ${p.destinationCode} · ${p.expectedPallets} ${t('palletCount')}`});}
function injectOutboundEntryUnits(){if(state.page!=='workbench')return;document.querySelectorAll('.entry-grid')[1]?.classList.add('outbound-entry-grid');}
function injectInternalEntryUnits(){if(state.page!=='workbench')return;document.querySelectorAll('.entry-grid')[2]?.classList.add('internal-entry-grid');}
function injectWorkbenchCleanup(){if(state.page!=='workbench')return;document.querySelectorAll('.section-title > span').forEach(span=>span.remove());document.querySelector('.task-strip')?.remove();document.querySelectorAll('.entry .badge').forEach(badge=>badge.remove());}
function injectPalletProgress(){return;}
function injectReceiveControls(){if(state.page!=='receive')return;document.querySelectorAll('.work-card:nth-of-type(2) .task-row').forEach((row,index)=>{const item=cargo.filter(c=>c.planNo===selectedPlan().id&&c.received)[index];if(!item||row.querySelector('select'))return;const select=document.createElement('select');select.className='chip';select.innerHTML=[['normal','正常'],['short','短少'],['damaged','破损'],['hold','待处理']].map(([v,l])=>`<option value="${v}" ${state.receiveStatus[item.id]===v?'selected':''}>${t(v)||l}</option>`).join('');select.onchange=()=>{state.receiveStatus[item.id]=select.value;render()};row.appendChild(select)});}
function injectMergeColumn(){if(state.page!=='pallet'||!state.pallet?.items.length||state.palletMode==='consolidate'||state.palletOrderType!=='overseas')return;const table=document.querySelector('.table');if(!table)return;const head=table.querySelector('thead tr');const headers=head?.querySelectorAll('th');if(head&&headers?.length){const th=document.createElement('th');th.textContent=t('mergeFlag');head.insertBefore(th,headers[headers.length-1])}table.querySelectorAll('tbody tr').forEach((row,index)=>{const cells=row.querySelectorAll('td');const item=state.pallet.items[index];if(!cells.length||!item)return;const td=document.createElement('td');td.innerHTML=`<span class="status ${item.isMerged?'blue':'wait'}">${item.isMerged?t('yesMerge'):t('noMerge')}</span>`;row.insertBefore(td,cells[cells.length-1])});}
function injectPickDetails(){if(state.page!=='pick')return;const task=pickTasks.find(x=>x.id===state.pickTaskId)||pickTasks.find(x=>!x.done)||pickTasks[0];const list=document.querySelector('.work-card .detail-list');if(!task||!list)return;list.innerHTML=`<div class="detail-row"><span>${t('outboundOrderNo')}</span><strong>${esc(task.id)}</strong></div><div class="detail-row"><span>${t('inboundCode')}</span><strong>${esc(task.inboundCode)}</strong></div><div class="detail-row"><span>${t('plateNo')}</span><strong>${esc(task.plateNo)}</strong></div><div class="detail-row"><span>${t('driver')}</span><strong>${esc(task.driver)}</strong></div><div class="detail-row"><span>${t('outboundTime')}</span><strong>${esc(task.outboundTime)}</strong></div><div class="detail-row"><span>${t('totalPalletBoxes')}</span><strong>${task.totalPallets} ${t('palletCount')} / ${task.boxes} ${t('boxes')}</strong></div><div class="detail-row"><span>${t('linkedOrder')}</span><strong>${esc(task.orderInfo)}</strong></div>`;}
function injectPalletDetailColumns(){if(state.page!=='pallet'||!state.pallet?.items.length||state.palletMode==='consolidate'||state.palletOrderType!=='overseas')return;const table=document.querySelector('.table');if(!table)return;table.style.minWidth='560px';table.innerHTML=`<thead><tr><th>${t('boxMark')}</th><th>${t('inboundCode')}</th><th>${t('productName')}</th><th>${t('remove')}</th></tr></thead><tbody>${state.pallet.items.map((item,index)=>{const plan=plans.find(x=>x.id===item.planNo);return `<tr><td>${esc(item.boxNo)}</td><td>${esc(plan?.inboundCode||'—')}</td><td>${esc(item.name||'—')}</td><td><button class="btn danger" style="min-height:44px;padding:0 8px" onclick="removePalletItem(${index})">${t('remove')}</button></td></tr>`}).join('')}</tbody>`;}
function injectPalletReadonly(){if(state.page!=='pallet'||!state.pallet?.persisted)return;document.querySelector('.scan-box')?.remove();document.querySelector('.bottom-bar')?.remove();document.querySelectorAll('.fba-pallet-choice').forEach(button=>{button.disabled=true;button.setAttribute('aria-disabled','true')});}
function injectPalletNavigation(){if(state.page!=='pallet'||state.pallet?.persisted||state.palletMode==='consolidate')return;const button=document.querySelector('.bottom-bar .btn.ghost');if(!button)return;button.textContent=t('back');button.onclick=()=>navigate('plans');}
function palletLabelOption(plan,no){const saved=state.pallets.find(x=>x.id===no);const active=state.pallet?.id===no;const occupied=Boolean(saved||(active&&state.pallet.items.length));const destination=saved?.destinationCode||plan.destinationCode||'—';return `<option value="${esc(plan.id)}|${esc(no)}" ${active?'selected':''}>${esc(no)} · ${esc(destination)} · ${t(occupied?'occupied':'idle')}</option>`;}
function injectPalletLabelSelector(){if(state.page!=='pallet'||!state.pallet||state.palletMode==='consolidate'||state.palletOrderType!=='overseas')return;const plansForLabels=state.palletMode==='consolidate'?plans:[selectedPlan()];const labels=plansForLabels.flatMap(plan=>Array.from({length:plan.expectedPallets},(_,index)=>palletLabelOption(plan,plannedPalletNo(plan,index))));const cards=document.querySelectorAll('.work-card');const details=cards[1];if(!details)return;const section=document.createElement('section');section.className='work-card pallet-label-selector';section.innerHTML=`<div class="work-head"><div><h2>${t('selectPalletLabel')}</h2><p>${t('palletLabel')} · ${t('palletStatus')}</p></div></div><div class="field" style="margin:12px 0 0"><label for="pallet-label-select">${t('palletLabel')}</label><select id="pallet-label-select" onchange="handlePalletLabelChange(this)">${labels.join('')}</select></div>`;details.insertAdjacentElement('afterend',section);}
function handlePalletLabelChange(select){const [planId,palletId]=String(select.value).split('|');selectPalletLabel(palletId,planId);}
function selectPalletLabel(palletId,planId){if(state.pallet?.id===palletId)return;const switchLabel=()=>{state.pallet=null;closeModal();selectPallet(palletId,planId)};if(state.pallet?.items.length&&!state.pallet.persisted){showModal(t('exitConfirm'),switchLabel);return}switchLabel();}
function injectIconReplacements(){document.querySelectorAll('.scan-box button').forEach(button=>{button.innerHTML=icon('scan');});}
function injectPalletScanModal(){if(state.page!=='pallet'||state.palletMode==='consolidate')return;const input=document.getElementById('pallet-scan');if(state.palletOrderType!=='dropship'&&input&&!input.dataset.quantityModal){input.dataset.quantityModal='true';input.addEventListener('click',openPalletScanModal);}}
function render(){const app=document.getElementById('app');if(!state.user&&state.page!=='login')state.page='login';app.dataset.page=state.page;let view=state.page==='login'?loginView():state.page==='workbench'?workbenchView():state.page==='profile'?profileView():state.page==='plans'?plansPalletView():state.page==='receive-list'?receiveListView():state.page==='receive'?receivePhotoView():state.page==='pallet-list'?palletListView():state.page==='pallet'?palletView():state.page==='pick'?pickView():state.page==='review'?reviewView():state.page==='handover'?handoverView():state.page==='relabel'?relabelView():state.page==='relabel-photo'?relabelPhotoView():state.page==='exception'?exceptionView():state.page==='inventory'?inventoryView():loginView();app.innerHTML=view;injectReservedEntries();injectInboundPalletUnits();injectOutboundEntryUnits();injectInternalEntryUnits();injectWorkbenchCleanup();injectPalletProgress();injectPalletDetailColumns();injectMergeColumn();injectPickDetails();injectPalletLabelSelector();injectPalletNavigation();injectPalletReadonly();injectIconReplacements();injectPalletScanModal();document.documentElement.lang=state.lang==='zh'?'zh-CN':state.lang==='es'?'es':'en';}
WORK_LABELS.zh.directEnter='拆托';WORK_LABELS.en.directEnter='Depalletize';WORK_LABELS.es.directEnter='Despaletizar';
Object.assign(WORK_LABELS.zh,{palletOrderType:'页面类型',fbaWarehouse:'FBA仓',overseasWarehouse:'海外仓',oneClickDropship:'一键代发',selectPalletLabels:'选择托盘标(多选)',selectAtLeastOnePallet:'请至少选择一个托盘标'});Object.assign(WORK_LABELS.en,{palletOrderType:'Page type',fbaWarehouse:'FBA',overseasWarehouse:'Overseas warehouse',oneClickDropship:'One-click fulfillment',selectPalletLabels:'Select pallet labels (multiple)',selectAtLeastOnePallet:'Select at least one pallet label'});Object.assign(WORK_LABELS.es,{palletOrderType:'Tipo de página',fbaWarehouse:'FBA',overseasWarehouse:'Almacén exterior',oneClickDropship:'Envío con un clic',selectPalletLabels:'Seleccionar etiquetas (múltiple)',selectAtLeastOnePallet:'Selecciona al menos una etiqueta'});
Object.assign(WORK_LABELS.zh,{cargoInfo:'货物信息'});Object.assign(WORK_LABELS.en,{cargoInfo:'Cargo information'});Object.assign(WORK_LABELS.es,{cargoInfo:'Información de carga'});
Object.assign(WORK_LABELS.zh,{weight:'重量',newGeneratedPallet:'新生成托盘标',generatePalletLabel:'生成托盘标',palletizeFooter:'打托',scanCartonQty:'箱数',remainingSkuCartons:'该 SKU 剩余箱数'});Object.assign(WORK_LABELS.en,{weight:'Weight',newGeneratedPallet:'Generate pallet label',generatePalletLabel:'Generate pallet label',palletizeFooter:'Palletize',scanCartonQty:'Cartons',remainingSkuCartons:'Remaining cartons for SKU'});Object.assign(WORK_LABELS.es,{weight:'Peso',newGeneratedPallet:'Generar etiqueta de pallet',generatePalletLabel:'Generar etiqueta de pallet',palletizeFooter:'Paletizar',scanCartonQty:'Cajas',remainingSkuCartons:'Cajas restantes del SKU'});
Object.assign(EXTRA_LABELS.zh,{vehicleType:'车型',vehicleTypeValue:'大车',reviewPallets:'托数',palletReferenceValue:'30托',reviewPalletProgress:'托数进度',reviewScanPallet:'扫描托盘标'});
Object.assign(EXTRA_LABELS.en,{vehicleType:'Vehicle type',vehicleTypeValue:'Large truck',reviewPallets:'Pallets',palletReferenceValue:'30 pallets',reviewPalletProgress:'Pallet progress',reviewScanPallet:'Scan pallet label'});
Object.assign(EXTRA_LABELS.es,{vehicleType:'Tipo de vehículo',vehicleTypeValue:'Camión grande',reviewPallets:'Pallets',palletReferenceValue:'30 pallets',reviewPalletProgress:'Progreso de pallets',reviewScanPallet:'Escanea la etiqueta del pallet'});
Object.assign(EXTRA_LABELS.zh,{skipReasonTitle:'跳拣原因',skipReasonNotFound:'未找到其他货物',skipReasonOutOfStock:'缺货',skipReasonOther:'其他'});
Object.assign(EXTRA_LABELS.en,{skipReasonTitle:'Skip reason',skipReasonNotFound:'No other freight found',skipReasonOutOfStock:'Out of stock',skipReasonOther:'Other'});
Object.assign(EXTRA_LABELS.es,{skipReasonTitle:'Motivo de omisión',skipReasonNotFound:'No se encontró otra carga',skipReasonOutOfStock:'Sin stock',skipReasonOther:'Otro'});
Object.assign(EXTRA_LABELS.zh,{photoDetails:'照片明细',sitePhotos:'现场照片',photoItem:'照片',noPhotos:'暂无照片',palletPhotoHint:'查看或拍摄托盘现场照片'});
Object.assign(EXTRA_LABELS.en,{photoDetails:'Photo details',sitePhotos:'Site photos',photoItem:'Photo',noPhotos:'No photos yet',palletPhotoHint:'View or capture pallet site photos'});
Object.assign(EXTRA_LABELS.es,{photoDetails:'Detalles de fotos',sitePhotos:'Fotos en sitio',photoItem:'Foto',noPhotos:'Aún no hay fotos',palletPhotoHint:'Ver o capturar fotos del pallet'});
Object.assign(EXTRA_LABELS.zh,{newConsolidation:'新增合托',noConsolidationPallets:'暂无合托托盘',reconsolidate:'重新合托',submitConsolidation:'提交合托',consolidationUpdated:'合托已更新'});
Object.assign(EXTRA_LABELS.en,{newConsolidation:'New consolidation',noConsolidationPallets:'No consolidated pallets',reconsolidate:'Re-consolidate',submitConsolidation:'Submit consolidation',consolidationUpdated:'Consolidation updated'});
Object.assign(EXTRA_LABELS.es,{newConsolidation:'Nueva consolidación',noConsolidationPallets:'No hay pallets consolidados',reconsolidate:'Reconsolidar',submitConsolidation:'Enviar consolidación',consolidationUpdated:'Consolidación actualizada'});
Object.assign(EXTRA_LABELS.zh,{consolidationItems:'合托明细',scanFreight:'扫描货物',destinationWarehouseShort:'目的仓'});
Object.assign(EXTRA_LABELS.en,{consolidationItems:'Consolidation details',scanFreight:'Scan freight',destinationWarehouseShort:'Destination WH'});
Object.assign(EXTRA_LABELS.es,{consolidationItems:'Detalles de consolidación',scanFreight:'Escanear carga',destinationWarehouseShort:'Almacén destino'});
Object.assign(EXTRA_LABELS.zh,{palletUnit:'托',pieceUnit:'件',orderCount:'{count} 个订单'});
Object.assign(EXTRA_LABELS.en,{palletUnit:'pallets',pieceUnit:'pcs',orderCount:'{count} orders'});
Object.assign(EXTRA_LABELS.es,{palletUnit:'pallets',pieceUnit:'uds.',orderCount:'{count} pedidos'});
Object.assign(EXTRA_LABELS.zh,{relabelPhotoTitle:'换标拍照',relabelPhotoHint:'拍摄换标完成后的现场照片',submitRelabelPhoto:'提交照片',relabelPhotoRequired:'请先完成换标拍照'});
Object.assign(EXTRA_LABELS.en,{relabelPhotoTitle:'Relabel photo',relabelPhotoHint:'Capture the completed relabeling',submitRelabelPhoto:'Submit photo',relabelPhotoRequired:'Take a relabel photo before submitting'});
Object.assign(EXTRA_LABELS.es,{relabelPhotoTitle:'Foto de reetiquetado',relabelPhotoHint:'Captura el reetiquetado terminado',submitRelabelPhoto:'Enviar foto',relabelPhotoRequired:'Toma una foto antes de enviar'});
const PAGE_TITLES={
zh:{'receive-list':'待卸货集装箱',receive:'拆柜收货',plans:'入库打托','pallet-list':'托盘列表',pallet:'入库打托',pick:'出库拣货',handover:'出库交接',inventory:'库存查询',relabel:'换标作业','relabel-photo':'换标拍照',exception:'异常处理',profile:'个人中心','photo-detail':'照片明细'},
en:{'receive-list':'Unloading',receive:'Receiving',plans:'Palletizing','pallet-list':'Pallets',pallet:'Pallet',pick:'Picking',handover:'Handover',inventory:'Inventory',relabel:'Relabeling','relabel-photo':'Relabel photo',exception:'Exceptions',profile:'Profile','photo-detail':'Photos'},
es:{'receive-list':'Descarga',receive:'Recepción',plans:'Paletizado','pallet-list':'Pallets',pallet:'Pallet',pick:'Picking',handover:'Entrega',inventory:'Inventario',relabel:'Reetiquetado','relabel-photo':'Foto de reetiquetado',exception:'Excepciones',profile:'Perfil','photo-detail':'Fotos'}
};
function compactPageTitle(title){if(state.page==='pallet'&&state.palletMode==='consolidate'&&state.pallet)return t('mergeOnly');return PAGE_TITLES[state.lang]?.[state.page]||title;}
const basePalletListView=palletListView;
palletListView=function(){const consolidating=state.palletMode==='consolidate';if(!consolidating)return basePalletListView();const created=state.pallets.filter(pallet=>pallet.mode==='consolidate'&&pallet.items?.length>0);return layout(`<div class="toolbar"><span class="subtle">${created.length} ${t('palletCount')}</span></div>${created.length?`<div class="list">${created.map(pallet=>{const sourcePlan=plans.find(plan=>plan.id===pallet.items[0]?.planNo);return `<article class="list-card"><div class="list-top"><div><div class="list-title">${esc(pallet.id)}</div><div class="list-meta">${esc(sourcePlan?.id||'—')} · ${esc(pallet.container||sourcePlan?.container||'—')}</div></div></div><div class="list-grid"><div><span>${t('destination')}</span><strong>${esc(pallet.destinationCode||'—')}</strong></div><div><span>${t('cartonCount')}</span><strong>${pallet.items.length}</strong></div><div><span>${t('weight')}</span><strong>${palletWeight(pallet).toFixed(1)} kg</strong></div></div><div class="card-actions"><button class="btn ghost" style="margin-right:auto" onclick="openPalletPhotos('${esc(pallet.id)}','${esc(sourcePlan?.id||'')}')">${t('photoDetails')}</button><button class="btn primary" onclick="reconsolidatePallet('${esc(pallet.id)}')">${t('reconsolidate')}</button></div></article>`}).join('')}</div>`:`<div class="empty">${t('noConsolidationPallets')}</div>`}<div class="bottom-bar"><button class="btn primary" onclick="startNewConsolidation()">${t('newConsolidation')}</button></div>`,t('mergeOnly'),t('mergeDesc'),'workbench');};
function startNewConsolidation(){state.palletMode='consolidate';state.palletMessage=null;state.photoPalletId='';resetPalletTypeInputs();const slot=plans.flatMap(plan=>Array.from({length:plan.expectedPallets},(_,index)=>({plan,no:plannedPalletNo(plan,index)}))).find(entry=>!state.pallets.some(pallet=>pallet.id===entry.no));if(!slot){state.pallet=null;startPallet();return}state.selectedPlanId=slot.plan.id;state.pallet={id:slot.no,mode:'consolidate',planId:null,targetPallets:null,destinationCode:'',destinationAddress:'',items:[]};state.page='pallet';render();}
function reconsolidatePallet(palletId){const saved=state.pallets.find(pallet=>pallet.id===palletId&&pallet.mode==='consolidate');if(!saved)return;state.palletMode='consolidate';resetPalletTypeInputs();state.pallet={...JSON.parse(JSON.stringify(saved)),originalPalletId:palletId};state.palletMessage=null;state.photoPalletId='';state.page='pallet';render();}
function submitConsolidation(){if(!state.pallet?.items.length){state.palletMessage={type:'error',text:t('emptyPallet')};render();return}const originalPalletId=state.pallet.originalPalletId;const originalIndex=state.pallets.findIndex(pallet=>pallet.id===originalPalletId);if(originalIndex<0){submitPallet();return}const updated=JSON.parse(JSON.stringify(state.pallet));delete updated.originalPalletId;delete updated.persisted;state.pallets[originalIndex]=updated;updated.items.forEach(item=>{const source=cargo.find(c=>c.id===item.id);if(source)source.palletNo=updated.id});state.pallet=null;state.palletMessage=null;toast(t('consolidationUpdated'),'good');state.page='pallet-list';render();}
photoCaptureButton=function(id='pallet-photo-capture-input'){return `<button type="button" class="btn primary photo-capture" onclick="document.getElementById('${id}').click()" style="display:flex;align-items:center;justify-content:center;gap:7px;white-space:nowrap">${icon('camera')}<span>${t('takePhoto')}</span></button><input id="${id}" type="file" accept="image/*" capture="environment" onchange="handlePalletPhoto(this)" style="display:none">`;};
photoAddButton=function(){const id='pallet-site-photo-input';return `<button type="button" class="photo-add-tile" onclick="document.getElementById('${id}').click()" aria-label="${t('takePhoto')}" style="display:grid;place-items:center;width:76px;height:76px;border:1px solid #d0d5dd;border-radius:10px;background:#fff;color:#98a2b3;font-size:32px;line-height:1;cursor:pointer"><span aria-hidden="true">+</span></button><input id="${id}" type="file" accept="image/*" capture="environment" onchange="handlePalletPhoto(this)" style="display:none">`;};
photoDetailView=function(){const key=state.photoPalletId||palletPhotoKey();const saved=state.pallets.find(x=>x.id===key);const plan=plans.find(x=>x.id===saved?.planId)||selectedPlan();return layout(`<section class="work-card"><div class="work-head"><div><h2>${t('photoDetails')}</h2><p>${esc(key)} · ${esc(saved?.destinationCode||plan?.destinationCode||'—')}</p></div><span class="status blue">${palletPhotoItems(key).length}</span></div>${photoListMarkup(key)}</section><div class="bottom-bar">${photoCaptureButton()}</div>`,t('photoDetails'),t('palletPhotoHint'),'pallet-list');};
injectPalletPhotoFeatures=function(){if(state.page!=='pallet'||!state.pallet||state.palletOrderType!=='overseas')return;const root=document.querySelector('.content');if(!root||root.querySelector('.pallet-site-photos'))return;const key=palletPhotoKey();const photos=palletPhotoItems(key);const photoTiles=photos.length?`<div class="photo-grid">${photos.map((src,index)=>`<div class="photo-step done"><div class="photo-preview"><img src="${src}" alt="${t('photoItem')} ${index+1}"></div><strong>${t('photoItem')} ${index+1}</strong></div>`).join('')}</div>`:'';const section=document.createElement('section');section.className='work-card pallet-site-photos';section.innerHTML=`<div class="work-head"><div><h2>${t('sitePhotos')}</h2><p>${t('palletPhotoHint')}</p></div><span class="status blue">${photos.length}</span></div>${photoTiles}<div style="margin-top:12px">${photoAddButton()}</div>`;const bottom=root.querySelector('.bottom-bar');if(bottom)root.insertBefore(section,bottom);else root.appendChild(section);};
injectPalletListPhotoButtons=function(){if(state.page!=='pallet-list')return;document.querySelectorAll('.list-card .card-actions').forEach(actions=>{actions.querySelector('.btn.ghost')?.style.setProperty('margin-right','auto');});};
function injectReconsolidationSubmit(){if(state.page!=='pallet'||!state.pallet?.originalPalletId)return;const button=document.querySelector('.bottom-bar .btn.primary');if(!button)return;button.textContent=t('submitConsolidation');button.disabled=!state.pallet.items.length;button.onclick=submitConsolidation;}
function injectCurrentPalletCleanup(){if(state.page!=='pallet'||!state.pallet)return;document.querySelector('.content > .work-card:first-child .work-head > .status')?.remove();}
function consolidationDetailView(){const pallet=state.pallet;const labels=plans.flatMap(plan=>Array.from({length:plan.expectedPallets},(_,index)=>palletLabelOption(plan,plannedPalletNo(plan,index))));const saved=state.pallets.find(item=>item.id===pallet.id);const status=saved||pallet.items.length?t('occupied'):t('idle');const photos=palletPhotoItems();const submitAction=pallet.originalPalletId?'submitConsolidation()':'submitPallet()';return layout(`<section class="work-card pallet-label-selector"><div class="work-head"><div><h2>${t('selectPalletLabel')}</h2><p>${t('palletLabel')} · ${t('palletStatus')}</p></div></div><div class="field" style="margin:12px 0 0"><label for="pallet-label-select">${t('palletLabel')}</label><select id="pallet-label-select" onchange="handlePalletLabelChange(this)">${labels.join('')}</select></div></section><section class="work-card current-pallet-card"><div class="work-head"><div><h2>${t('currentPallet')}</h2><p>${t('mergeDesc')}</p></div><span class="status ${status===t('occupied')?'blue':'good'}">${status}</span></div><div class="detail-list"><div class="detail-row"><span>${t('palletNo')}</span><strong>${esc(pallet.id||'DRAFT')}</strong></div><div class="detail-row"><span>${t('destinationWarehouseShort')}</span><strong>${esc(pallet.destinationCode||'—')}</strong></div><div class="detail-row"><span>${t('palletStatus')}</span><strong>${status}</strong></div><div class="detail-row"><span>${t('cartonCount')}</span><strong>${pallet.items.length}</strong></div></div></section><section class="work-card consolidation-scan-card"><div class="work-head"><div><h2>${t('scanFreight')}</h2><p>${t('sameDest')}</p></div></div><div class="scan-box"><input id="pallet-scan" placeholder="${t('scanFreight')}" onkeydown="if(event.key==='Enter')handlePalletScan()"><button onclick="handlePalletScan()" aria-label="${t('scan')}">${icon('scan')}</button></div>${state.palletMessage?`<div class="scan-result ${state.palletMessage.type}">${esc(state.palletMessage.text)}</div>`:''}</section><section class="work-card consolidation-items-card"><div class="work-head"><div><h2>${t('consolidationItems')}</h2><p>${t('trace')}</p></div><span class="status blue">${pallet.items.length}</span></div>${pallet.items.length?`<div class="table-wrap"><table class="table"><thead><tr><th>${t('boxMark')}</th><th>${t('inboundCode')}</th><th>${t('productName')}</th><th>${t('remove')}</th></tr></thead><tbody>${pallet.items.map((item,index)=>{const plan=plans.find(entry=>entry.id===item.planNo);return `<tr><td>${esc(item.boxNo)}</td><td>${esc(plan?.inboundCode||'—')}</td><td>${esc(item.name||'—')}</td><td><button class="btn danger" style="min-height:44px;padding:0 8px" onclick="removePalletItem(${index})">${t('remove')}</button></td></tr>`}).join('')}</tbody></table></div>`:`<div class="empty">${t('emptyPallet')}</div>`}</section><section class="work-card pallet-site-photos"><div class="work-head"><div><h2>${t('sitePhotos')}</h2><p>${t('palletPhotoHint')}</p></div><span class="status blue">${photos.length}</span></div>${photoListMarkup()}<div style="margin-top:12px">${photoAddButton()}</div></section><div class="bottom-bar"><div style="display:flex;gap:8px"><button class="btn ghost" style="flex:1" onclick="navigate('pallet-list')">${t('back')}</button><button class="btn primary" style="flex:2" onclick="${submitAction}" ${pallet.items.length?'':'disabled'}>${t('submitConsolidation')}</button></div></div>`,t('mergeOnly'),t('mergeDesc'),'pallet-list');}
const basePalletView=palletView;
palletView=function(){return state.palletMode==='consolidate'&&state.pallet?consolidationDetailView():basePalletView();};
injectPalletPhotoFeatures=function(){if(state.page!=='pallet'||!state.pallet||state.palletMode==='consolidate'||state.palletOrderType!=='overseas')return;const root=document.querySelector('.content');if(!root||root.querySelector('.pallet-site-photos'))return;const key=palletPhotoKey();const photos=palletPhotoItems(key);const photoTiles=photos.length?`<div class="photo-grid">${photos.map((src,index)=>`<div class="photo-step done"><div class="photo-preview"><img src="${src}" alt="${t('photoItem')} ${index+1}"></div><strong>${t('photoItem')} ${index+1}</strong></div>`).join('')}</div>`:'';const section=document.createElement('section');section.className='work-card pallet-site-photos';section.innerHTML=`<div class="work-head"><div><h2>${t('sitePhotos')}</h2><p>${t('palletPhotoHint')}</p></div><span class="status blue">${photos.length}</span></div>${photoTiles}<div style="margin-top:12px">${photoAddButton()}</div>`;const bottom=root.querySelector('.bottom-bar');if(bottom)root.insertBefore(section,bottom);else root.appendChild(section);};
let lastRenderedPage='';
function settlePageScroll(){if(lastRenderedPage===state.page)return;lastRenderedPage=state.page;requestAnimationFrame(()=>window.scrollTo({top:0,left:0,behavior:'auto'}));}
function injectFinalCopy(){
if(state.page==='relabel'){
const count=document.querySelector('.toolbar .subtle');
if(count)count.textContent=t('orderCount',{count:plans.length});
}
const replacements=[[t('palletCount'),t('palletUnit')],[t('pieces'),t('pieceUnit')]];
document.querySelectorAll('strong,.toolbar .subtle,.progress-line strong,.scan-result.info,.list-meta').forEach(element=>{
replacements.forEach(([label,unit])=>{element.textContent=element.textContent.split(label).join(unit)});
});
}
const baseRender=render;
render=function(){if(state.page==='photo-detail'){const app=document.getElementById('app');app.dataset.page=state.page;app.innerHTML=photoDetailView();injectIconReplacements();injectFinalCopy();settlePageScroll();document.documentElement.lang=state.lang==='zh'?'zh-CN':state.lang==='es'?'es':'en';return}baseRender();injectPalletListPhotoButtons();injectPalletPhotoFeatures();injectReconsolidationSubmit();injectCurrentPalletCleanup();injectFinalCopy();settlePageScroll();};
render();
</script>
</body>
</html>