@javierfrancoleal/dsh-supervisor 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard.html +546 -8
- package/package.json +2 -5
- package/server.js +1 -2
- package/dashboard.css +0 -171
- package/dashboard.js +0 -361
- package/i18n.js +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@javierfrancoleal/dsh-supervisor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "The Veilleux central control plane of the J.Franco agentic workflow suite: user accounts, the live conversation flow (listen → per-person summary → confirm → orders), telemetry, and the universal sector dashboard with 10 languages. Created by J.Franco.",
|
|
5
5
|
"author": "J.Franco",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,10 +17,7 @@
|
|
|
17
17
|
"icons",
|
|
18
18
|
"sector-packs",
|
|
19
19
|
"bin",
|
|
20
|
-
"README.md"
|
|
21
|
-
"dashboard.css",
|
|
22
|
-
"dashboard.js",
|
|
23
|
-
"i18n.js"
|
|
20
|
+
"README.md"
|
|
24
21
|
],
|
|
25
22
|
"engines": {
|
|
26
23
|
"node": ">=24"
|
package/server.js
CHANGED
|
@@ -44,7 +44,7 @@ const LIVE_FILE = path.join(DATA_DIR, "live.json");
|
|
|
44
44
|
const SECTOR_FILE = path.join(DATA_DIR, "sector.json");
|
|
45
45
|
const REG_FILE = path.join(DATA_DIR, "register.json");
|
|
46
46
|
const PRICE_PER_MILLION = Number(process.env.DSH_PRICE_PER_MILLION || 0.28);
|
|
47
|
-
const VERSION = "1.0.
|
|
47
|
+
const VERSION = "1.0.39";
|
|
48
48
|
// Homeostasis: the daily token budget (metabolic limit). The brain rests
|
|
49
49
|
// when the budget is spent — no runaway costs.
|
|
50
50
|
const DAILY_BUDGET_USD = Number(process.env.DSH_DAILY_BUDGET_USD || 0); // 0 = unlimited: consumption is the business
|
|
@@ -519,7 +519,6 @@ const MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; cha
|
|
|
519
519
|
function serveStatic(req, res, pathname) {
|
|
520
520
|
let p;
|
|
521
521
|
if (pathname === "/" || pathname === "/dashboard" || pathname === "/dashboard.html") p = path.join(__dirname, "dashboard.html");
|
|
522
|
-
else if (pathname === "/dashboard.css" || pathname === "/dashboard.js" || pathname === "/i18n.js") p = path.join(__dirname, pathname.replace(/^\//, ""));
|
|
523
522
|
else if (pathname === "/panel" || pathname === "/panel.html") p = path.join(UI_DIR, "panel.html");
|
|
524
523
|
else { const rel = pathname.replace(/^\/+/, ""); p = path.join(UI_DIR, rel); if (!p.startsWith(UI_DIR)) { res.writeHead(403); return res.end(); } }
|
|
525
524
|
fs.readFile(p, (err, data) => {
|
package/dashboard.css
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/* Le Veilleux dashboard — stage 1 of the structural refactor.
|
|
2
|
-
Extracted from dashboard.html. One design language per EL FUEGO. */
|
|
3
|
-
:root{--blue:#4D6BFE;--violet:#7c5cff;--green:#22c55e;--amber:#facc15;--red:#ef4444;--ink:#101828;--muted:#667085;--bg:#eef2ff;--card:rgba(255,255,255,.78);--card2:rgba(255,255,255,.62);--line:rgba(15,23,42,.10);--line2:rgba(15,23,42,.06)}
|
|
4
|
-
body.dark{background:linear-gradient(165deg,#0b1220 0%,#141f31 50%,#1e1b4b 100%);background-attachment:fixed;--ink:#e6ecf7;--muted:#94a3b8;--bg:#0b1220;--card:rgba(20,31,49,.8);--card2:rgba(24,37,58,.62);--line:rgba(148,163,184,.16);--line2:rgba(148,163,184,.10)}
|
|
5
|
-
.anim{animation:slidein .28s ease}
|
|
6
|
-
@keyframes slidein{from{opacity:0;transform:translateX(26px)}to{opacity:1;transform:none}}
|
|
7
|
-
.empty{text-align:center;padding:26px 12px;color:var(--muted);font-size:14px;border:1px dashed var(--line);border-radius:14px}
|
|
8
|
-
.empty .ico{font-size:30px;display:block;margin-bottom:6px}
|
|
9
|
-
.skel{background:linear-gradient(90deg,var(--line) 25%,var(--line2) 50%,var(--line) 75%);background-size:200% 100%;animation:shimmer 1.2s infinite;border-radius:10px;min-height:16px}
|
|
10
|
-
@keyframes shimmer{to{background-position:-200% 0}}
|
|
11
|
-
.toast{position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:#111827;color:#fff;padding:10px 18px;border-radius:999px;font-size:14px;font-weight:600;z-index:60;opacity:0;transition:opacity .25s;pointer-events:none;max-width:86%;text-align:center}
|
|
12
|
-
.toast.show{opacity:1}
|
|
13
|
-
body.dark .toast{background:#e6ecf7;color:#0b1220}
|
|
14
|
-
.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--line);display:none;justify-content:space-around;padding:6px 0 8px;z-index:50}
|
|
15
|
-
.bnav button{background:none;border:none;color:var(--muted);font-size:11px;display:flex;flex-direction:column;align-items:center;gap:3px;padding:6px 12px;border-radius:12px;min-height:44px;font-family:inherit}
|
|
16
|
-
.bnav button.active{color:var(--blue);font-weight:700}
|
|
17
|
-
.checkpop{animation:pop .3s ease}
|
|
18
|
-
@keyframes pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.15)}100%{transform:scale(1);opacity:1}}
|
|
19
|
-
@keyframes flick{0%,100%{opacity:1}42%{opacity:.82}50%{opacity:1}58%{opacity:.9}}
|
|
20
|
-
@keyframes drawStroke{from{stroke-dashoffset:100}to{stroke-dashoffset:0}}
|
|
21
|
-
/* Visible by default; the self-draw runs only when motion is allowed */
|
|
22
|
-
.mark svg path{stroke-dasharray:100}
|
|
23
|
-
@media (prefers-reduced-motion: no-preference){.mark svg path{stroke-dashoffset:100;animation:drawStroke 1.6s cubic-bezier(.4,0,.2,1) forwards}}
|
|
24
|
-
|
|
25
|
-
.person{animation:rise .3s ease backwards}
|
|
26
|
-
.person:nth-child(1){animation-delay:.03s}
|
|
27
|
-
.person:nth-child(2){animation-delay:.09s}
|
|
28
|
-
.person:nth-child(3){animation-delay:.15s}
|
|
29
|
-
.person:nth-child(4){animation-delay:.21s}
|
|
30
|
-
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
|
|
31
|
-
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
|
32
|
-
body.loginpage{background:linear-gradient(135deg,#4D6BFE 0%,#7c5cff 55%,#a78bfa 100%);min-height:100vh}
|
|
33
|
-
body.loginpage.dark{background:linear-gradient(135deg,#0b1220 0%,#1e1b4b 100%)}
|
|
34
|
-
body.loginpage header{background:transparent;box-shadow:none}
|
|
35
|
-
#loginView{backdrop-filter:blur(14px);background:rgba(255,255,255,.93);border:1px solid rgba(255,255,255,.55);box-shadow:0 24px 60px rgba(15,23,42,.28);max-width:420px;margin:6vh auto 0;padding:26px 22px;animation:rise .5s ease backwards;animation-delay:.12s}
|
|
36
|
-
body.dark #loginView{background:rgba(20,31,49,.93)}
|
|
37
|
-
.loginbrand{text-align:center;margin-bottom:16px;animation:rise .5s ease backwards;animation-delay:.02s}
|
|
38
|
-
.loginbrand .mark{width:64px;height:64px;border-radius:18px;background:linear-gradient(135deg,#4D6BFE,#7c5cff);display:inline-flex;align-items:center;justify-content:center;font-size:30px;box-shadow:0 10px 24px rgba(77,107,254,.45)}
|
|
39
|
-
.loginbrand h2{margin:10px 0 2px;font-size:20px;font-weight:800}
|
|
40
|
-
.loginbrand p{color:var(--muted);font-size:13px;margin:0}
|
|
41
|
-
.spin{display:inline-block;width:15px;height:15px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:rot .7s linear infinite;vertical-align:-3px;margin-right:8px}
|
|
42
|
-
@keyframes rot{to{transform:rotate(360deg)}}
|
|
43
|
-
.shake{animation:shake .4s ease}
|
|
44
|
-
@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-6px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(4px)}}
|
|
45
|
-
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(77,107,254,.18)}
|
|
46
|
-
@media(max-width:560px){.bnav{display:flex}.tabs{display:none}body{padding-bottom:70px}button,.btn,.pill,.confirmBtn{min-height:44px}}
|
|
47
|
-
*{box-sizing:border-box}
|
|
48
|
-
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:linear-gradient(165deg,#e8edff 0%,#f6f7fb 45%,#fdf1f8 100%);background-attachment:fixed;color:var(--ink);-webkit-tap-highlight-color:transparent}
|
|
49
|
-
header{background:linear-gradient(135deg,#4D6BFE,#7c5cff);box-shadow:0 4px 18px rgba(77,107,254,.28);color:#fff;padding:12px 16px;display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}
|
|
50
|
-
header h1{margin:0;font-size:18px}
|
|
51
|
-
header .who{font-size:13px;opacity:.9}
|
|
52
|
-
button{font-family:inherit;cursor:pointer}
|
|
53
|
-
.btn{border:none;border-radius:12px;padding:12px 16px;font-size:15px;font-weight:600;color:#fff;background:linear-gradient(135deg,#4D6BFE,#7c5cff);box-shadow:0 4px 12px rgba(77,107,254,.22)}
|
|
54
|
-
.btn.gray{background:var(--line2);color:var(--ink)}
|
|
55
|
-
.btn.small{padding:8px 12px;font-size:13px}
|
|
56
|
-
main{max-width:960px;margin:0 auto;padding:14px}
|
|
57
|
-
.card{background:var(--card);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--line);border-radius:20px;box-shadow:0 8px 24px rgba(15,23,42,.06);padding:16px;margin:12px 0}
|
|
58
|
-
.card h2{margin:0 0 10px;font-size:16px}
|
|
59
|
-
input,select,textarea{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;font-size:16px;margin-bottom:8px;font-family:inherit;background:var(--card)}
|
|
60
|
-
textarea{resize:vertical;min-height:80px}
|
|
61
|
-
pre{background:#0f172a;color:#d7e3f4;border-radius:12px;padding:12px;font-size:12.5px;overflow:auto;max-height:320px;white-space:pre-wrap;margin:0}
|
|
62
|
-
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px}
|
|
63
|
-
.metric{background:var(--card);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--line);border-radius:16px;box-shadow:0 4px 14px rgba(15,23,42,.05);padding:12px;text-align:center}
|
|
64
|
-
.metric .v{font-size:26px;font-weight:700;color:var(--blue)}
|
|
65
|
-
.metric .l{font-size:12px;color:var(--muted);margin-top:2px}
|
|
66
|
-
.tabs{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}
|
|
67
|
-
.tabs button{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:10px 16px;font-size:15px;font-weight:600}
|
|
68
|
-
.tabs button.active{background:var(--blue);color:#fff;border-color:var(--blue)}
|
|
69
|
-
table{width:100%;border-collapse:collapse;font-size:14px}
|
|
70
|
-
td,th{border-bottom:1px solid var(--line);padding:9px 6px;text-align:left}
|
|
71
|
-
#splash{position:fixed;inset:0;background:linear-gradient(135deg,#4D6BFE,#7c5cff);display:flex;align-items:center;justify-content:center;z-index:100;transition:opacity .5s,visibility .5s}
|
|
72
|
-
#splash.done{opacity:0;visibility:hidden}
|
|
73
|
-
.splash-inner{text-align:center;color:#fff;padding:20px}
|
|
74
|
-
.splash-inner b{display:block;font-size:22px;margin-top:14px;letter-spacing:.02em}
|
|
75
|
-
.splash-inner span{font-size:12.5px;opacity:.85}
|
|
76
|
-
@media (prefers-reduced-motion: no-preference){.splash-inner svg path{stroke-dasharray:100;stroke-dashoffset:100;animation:drawStroke 1.2s cubic-bezier(.4,0,.2,1) forwards}}
|
|
77
|
-
@media (prefers-reduced-motion: reduce){#splash{display:none}}
|
|
78
|
-
.hidden{display:none}
|
|
79
|
-
.kban{background:#fef3c7;border:1px solid #f59e0b;color:#92400e;border-radius:12px;padding:12px 14px;margin:10px 0;font-weight:700;font-size:15px}
|
|
80
|
-
.chip{background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.35);border-radius:999px;padding:5px 12px;font-size:13px;font-weight:600}
|
|
81
|
-
.foot{text-align:center;padding:6px 16px 16px}
|
|
82
|
-
.foot .copyr{font-size:12px;color:var(--muted)}
|
|
83
|
-
.eye{position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;font-size:18px;cursor:pointer;padding:4px}
|
|
84
|
-
.passwrap{position:relative}
|
|
85
|
-
.loginerr{background:#fee2e2;color:#b91c1c;border-radius:10px;padding:10px 12px;margin:8px 0;font-size:14px;display:block}
|
|
86
|
-
.loginok{background:#dcfce7;color:#15803d;border-radius:10px;padding:10px 12px;margin:8px 0;font-size:14px;display:block}
|
|
87
|
-
.note{color:var(--muted);font-size:13px}
|
|
88
|
-
.hint{font-size:12.5px;color:var(--muted);margin-top:4px}
|
|
89
|
-
.listen{display:block;width:100%;border:none;border-radius:24px;padding:34px 16px;font-size:28px;font-weight:800;color:#fff;background:var(--green);box-shadow:0 6px 20px rgba(34,197,94,.4);margin:14px 0}
|
|
90
|
-
.listen:active{transform:scale(.98)}
|
|
91
|
-
.listen.pulse{animation:pulse 1.6s infinite}
|
|
92
|
-
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(34,197,94,.55)}70%{box-shadow:0 0 0 26px rgba(34,197,94,0)}100%{box-shadow:0 0 0 0 rgba(34,197,94,0)}}
|
|
93
|
-
.stopbtn{background:var(--red)!important;box-shadow:0 6px 20px rgba(239,68,68,.35)!important}
|
|
94
|
-
.row{display:flex;gap:8px;flex-wrap:wrap}
|
|
95
|
-
.row .btn{flex:1;min-width:110px}
|
|
96
|
-
.switchuser{margin-top:26px;text-align:center}
|
|
97
|
-
.langs{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:8px 0 14px}
|
|
98
|
-
.langov{position:fixed;inset:0;background:linear-gradient(160deg,#4D6BFE,#7c5cff 60%,#a78bfa);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:110;animation:rise .3s ease}
|
|
99
|
-
.langov .top{position:absolute;top:22px;right:26px}
|
|
100
|
-
.langov .close{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.3);color:#fff;border-radius:999px;width:44px;height:44px;font-size:18px;cursor:pointer}
|
|
101
|
-
.orow{width:min(520px,92vw);display:flex;justify-content:space-between;align-items:center;padding:15px 22px;border-radius:14px;font-size:19px;color:#fff;cursor:pointer;transition:background .15s,transform .1s}
|
|
102
|
-
.orow:hover{background:rgba(255,255,255,.12)}
|
|
103
|
-
.orow:active{transform:scale(.98)}
|
|
104
|
-
.orow .sub{font-size:13px;opacity:.7}
|
|
105
|
-
.orow.center{background:rgba(255,255,255,.30);font-size:23px;font-weight:800;border:1px solid rgba(255,255,255,.45)}
|
|
106
|
-
.orow.center .sub{opacity:.9}
|
|
107
|
-
#langStep{position:fixed;inset:0;background:linear-gradient(160deg,#4D6BFE,#7c5cff 60%,#a78bfa);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:95;transition:opacity .45s,visibility .45s;animation:stepin .55s ease}
|
|
108
|
-
@keyframes stepin{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
|
|
109
|
-
#langStep.done{opacity:0;visibility:hidden}
|
|
110
|
-
.langstep-inner{text-align:center}
|
|
111
|
-
.langstep-hint{color:rgba(255,255,255,.9);font-size:14px;margin:18px 0 4px}
|
|
112
|
-
.langstep-inner .langreel{width:min(340px,86vw);background:rgba(167,139,250,.55)!important;border:1px solid rgba(255,255,255,.6)!important;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
|
|
113
|
-
.langstep-inner svg{width:150px;height:150px}
|
|
114
|
-
.langstep-inner svg path.draw{stroke-dasharray:100;animation:drawStroke 1.2s cubic-bezier(.4,0,.2,1) forwards}
|
|
115
|
-
@media (prefers-reduced-motion: reduce){.langstep-inner svg path.draw{animation:none}}
|
|
116
|
-
#loginView{background:transparent;backdrop-filter:none;-webkit-backdrop-filter:none;border:none;box-shadow:none;text-align:center}
|
|
117
|
-
#loginView h2,#loginView .loginbrand h2{color:#fff}
|
|
118
|
-
#loginView .loginbrand p,#loginView .hint,#loginView .note{color:rgba(255,255,255,.82)}
|
|
119
|
-
#loginView input,#loginView select{background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.35);color:#fff}
|
|
120
|
-
#loginView input::placeholder{color:rgba(255,255,255,.7)}
|
|
121
|
-
#loginView .loginerr{background:rgba(239,68,68,.25);color:#fff;border:1px solid rgba(255,255,255,.3)}
|
|
122
|
-
#loginView .eye{color:#fff}
|
|
123
|
-
#loginView .btn.gray{background:rgba(255,255,255,.16);color:#fff;border:1px solid rgba(255,255,255,.3)}
|
|
124
|
-
.langreel{width:min(340px,86vw);height:58px;margin:10px auto;background:rgba(167,139,250,.45);border:1px solid rgba(255,255,255,.55);border-radius:10px;backdrop-filter:blur(14px);overflow:hidden;cursor:pointer;position:relative;box-shadow:0 10px 30px rgba(76,29,149,.20)}
|
|
125
|
-
body.dark .langreel{background:rgba(124,92,255,.35)}
|
|
126
|
-
.ticks{display:flex;flex-direction:column;will-change:transform}
|
|
127
|
-
.tick{height:58px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:600;opacity:.85;color:#fff}
|
|
128
|
-
.pill{background:var(--card);border:1px solid var(--line);border-radius:999px;padding:8px 13px;font-size:13px;cursor:pointer}
|
|
129
|
-
.pill.active{background:var(--blue);color:#fff;border-color:var(--blue)}
|
|
130
|
-
.person{background:var(--card2);border:1px solid var(--line);border-radius:16px;padding:14px;margin:10px 0}
|
|
131
|
-
.person .pname{font-size:17px;font-weight:700;margin-bottom:8px}
|
|
132
|
-
.req{display:flex;align-items:center;gap:10px;padding:10px;border-radius:12px;background:var(--card);border:1px solid var(--line2);margin:6px 0}
|
|
133
|
-
.req .txt{flex:1;font-size:15px}
|
|
134
|
-
.req .tier{font-size:12px;font-weight:700;padding:4px 8px;border-radius:999px}
|
|
135
|
-
.sentline{font-size:12px;color:#16a34a;margin-top:4px;font-weight:600}
|
|
136
|
-
.pcnt{font-size:12.5px;color:var(--muted);font-weight:600;margin-left:6px}
|
|
137
|
-
.tier-GREEN{background:#dcfce7;color:#15803d}
|
|
138
|
-
.tier-SEMI{background:#fef9c3;color:#a16207}
|
|
139
|
-
.tier-YELLOW{background:#fef9c3;color:#a16207}
|
|
140
|
-
.tier-RED{background:#fee2e2;color:#b91c1c}
|
|
141
|
-
.confirmBtn{background:var(--blue);color:#fff;border:none;border-radius:12px;padding:10px 14px;font-size:14px;font-weight:700;white-space:nowrap}
|
|
142
|
-
.confirmBtn.done{background:#dcfce7;color:#15803d}
|
|
143
|
-
.order{display:flex;align-items:center;gap:10px;padding:10px;border-radius:12px;background:var(--card2);border:1px solid var(--line);margin:6px 0;font-size:14px}
|
|
144
|
-
.order .stat{font-size:12px;font-weight:700;padding:4px 10px;border-radius:999px;background:#e0e7ff;color:#3730a3}
|
|
145
|
-
#wallView{max-width:1100px}
|
|
146
|
-
.wall-head{font-size:34px;font-weight:800;color:#fff;text-align:center;margin:10px 0}
|
|
147
|
-
.wall-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}
|
|
148
|
-
.wall-card{background:#1b2436;color:#fff;border-radius:16px;padding:20px;text-align:center}
|
|
149
|
-
.wall-card .v{font-size:44px;font-weight:800}
|
|
150
|
-
.wall-card .l{font-size:15px;opacity:.85}
|
|
151
|
-
.wall-top{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:6px 0 16px;flex-wrap:wrap}
|
|
152
|
-
.wall-brand{display:flex;align-items:center;gap:10px;color:#fff}
|
|
153
|
-
.wall-brand b{font-size:20px;display:block}
|
|
154
|
-
.wall-brand span{font-size:12px;opacity:.75;text-transform:uppercase;letter-spacing:.06em}
|
|
155
|
-
.wall-clock{text-align:right;color:#fff}
|
|
156
|
-
.wall-clock b{font-size:20px;display:block}
|
|
157
|
-
.wall-clock span{font-size:12px;opacity:.75}
|
|
158
|
-
.live-dot{display:flex;align-items:center;gap:7px;color:#4ade80;font-weight:800;font-size:13px;letter-spacing:.08em}
|
|
159
|
-
.live-dot::before{content:"";width:9px;height:9px;border-radius:50%;background:#4ade80;animation:pulse 1.6s infinite}
|
|
160
|
-
.wtick{background:#1b2436;color:#d7e3f4;border-radius:10px;padding:8px 12px;font-size:13.5px;margin:6px 0}
|
|
161
|
-
.wtick.dim{color:#64748b;background:none;border:1px dashed #334155}
|
|
162
|
-
.wall-note{color:#64748b;font-size:12px;text-align:center;margin-top:10px}
|
|
163
|
-
.welcome{position:relative;border-left:4px solid var(--blue);animation:rise .4s ease}
|
|
164
|
-
.welcome .wclose{position:absolute;top:10px;right:10px;background:none;border:none;font-size:16px;color:var(--muted);cursor:pointer}
|
|
165
|
-
button{transition:transform .12s ease}
|
|
166
|
-
button:active{transform:scale(.97)}
|
|
167
|
-
button:focus-visible,.pill:focus-visible,input:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
|
|
168
|
-
body.wall{background:linear-gradient(165deg,#0b1220 0%,#131c30 60%,#1e1b4b 100%);background-attachment:fixed}
|
|
169
|
-
body.firstboot header,body.firstboot main,body.firstboot footer{display:none}
|
|
170
|
-
body.wall pre{max-height:40vh}
|
|
171
|
-
@media (max-width:560px){header h1{font-size:15px}.listen{font-size:24px;padding:26px 12px}input,select,textarea,button{font-size:16px}}
|
package/dashboard.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
// Le Veilleux dashboard logic — stage 2 of the structural refactor.
|
|
2
|
-
// Extracted from dashboard.html. i18n comes from i18n.js (global S).
|
|
3
|
-
"use strict";
|
|
4
|
-
const LANG={es:"ES",en:"EN",fr:"FR",de:"DE",it:"IT",pt:"PT",zh:"中文",ja:"日本語",ko:"한국어",ar:"العربية"};
|
|
5
|
-
|
|
6
|
-
function haptic(){try{if(navigator.vibrate)navigator.vibrate(25);}catch(e){}if(soundOn){try{const C=window.AudioContext||window.webkitAudioContext;if(!window.__ac)window.__ac=new C();const c=window.__ac;const o=c.createOscillator(),g=c.createGain();o.frequency.value=880;o.connect(g);g.connect(c.destination);g.gain.setValueAtTime(.12,c.currentTime);g.gain.exponentialRampToValueAtTime(.001,c.currentTime+.12);o.start();o.stop(c.currentTime+.12);}catch(e){}}}
|
|
7
|
-
const $=id=>document.getElementById(id);
|
|
8
|
-
let token=null,me=null,auto=false,cur="en",livePoll=null,PACK=null,recording=false,rec=null,soundOn=true,darkOn=false,kitchenPoll=null,lastOrders=0;
|
|
9
|
-
function t(k){return (S[cur]&&S[cur][k])||S.en[k]||k;}
|
|
10
|
-
function applyToggles(){document.body.classList.toggle("dark",darkOn);$("darkBtn").textContent=darkOn?"☀️":"🌙";$("soundBtn").textContent=soundOn?"🔊":"🔇";}
|
|
11
|
-
$("darkBtn").onclick=()=>{darkOn=!darkOn;try{localStorage.setItem("dsh-dark",darkOn?"1":"0");}catch(e){}applyToggles();};
|
|
12
|
-
$("soundBtn").onclick=()=>{soundOn=!soundOn;try{localStorage.setItem("dsh-sound",soundOn?"1":"0");}catch(e){}applyToggles();};
|
|
13
|
-
try{darkOn=localStorage.getItem("dsh-dark")==="1";soundOn=localStorage.getItem("dsh-sound")!=="0";}catch(e){}
|
|
14
|
-
applyToggles();
|
|
15
|
-
document.body.classList.add("loginpage");
|
|
16
|
-
const firstRun=(()=>{try{return !localStorage.getItem("dsh-lang");}catch(e){return true;}})();
|
|
17
|
-
if(firstRun){
|
|
18
|
-
$("splash").style.display="none";
|
|
19
|
-
$("loginView").style.display="none";
|
|
20
|
-
document.body.classList.add("firstboot");
|
|
21
|
-
$("langStep").style.display="flex";
|
|
22
|
-
}else{
|
|
23
|
-
setTimeout(()=>{$("splash").classList.add("done");},1300);
|
|
24
|
-
}
|
|
25
|
-
async function reRenderDynamic(){
|
|
26
|
-
if(!me)return;
|
|
27
|
-
if(me.role==="owner"){loadMetrics($("ownerMetrics"));loadUsers();loadUsage();loadField();renderOrders();renderSuggest();}
|
|
28
|
-
else if(me.role==="lead"){loadMetrics($("leadMetrics"));loadLead();renderOrders();}
|
|
29
|
-
else{$("workerHi").textContent=t("hi")+(me.label||me.name);workerFlow();}
|
|
30
|
-
if(location.search.indexOf("wall=1")>=0)wallTick();
|
|
31
|
-
}
|
|
32
|
-
async function kitchenWatch(){
|
|
33
|
-
const live=await api("/api/live");
|
|
34
|
-
const n=(live.orders||[]).length;
|
|
35
|
-
if(n>lastOrders&&lastOrders>0){
|
|
36
|
-
const o=live.orders[n-1];
|
|
37
|
-
$("kitchenBanner").textContent="🔔 "+t("newOrder")+": "+o.person+" — "+o.request;
|
|
38
|
-
$("kitchenBanner").classList.remove("hidden");
|
|
39
|
-
haptic();
|
|
40
|
-
try{if("Notification" in window&&Notification.permission==="granted"){new Notification(t("newOrder"),{body:o.person+" — "+o.request});}}catch(e){}
|
|
41
|
-
setTimeout(()=>$("kitchenBanner").classList.add("hidden"),6000);
|
|
42
|
-
}
|
|
43
|
-
lastOrders=n;
|
|
44
|
-
}
|
|
45
|
-
$("notifBtn").onclick=()=>{
|
|
46
|
-
try{
|
|
47
|
-
if("Notification" in window&&Notification.permission!=="granted"){
|
|
48
|
-
Notification.requestPermission().then(()=>{renderOrders();});
|
|
49
|
-
}
|
|
50
|
-
}catch(e){}
|
|
51
|
-
};
|
|
52
|
-
async function doExport(type){
|
|
53
|
-
try{
|
|
54
|
-
const r=await fetch("/api/export?type="+type,{headers:{"Authorization":"Bearer "+token}});
|
|
55
|
-
if(!r.ok){toast("Error");return;}
|
|
56
|
-
const blob=await r.blob();
|
|
57
|
-
const a=document.createElement("a");a.href=URL.createObjectURL(blob);
|
|
58
|
-
const cd=r.headers.get("Content-Disposition")||"";
|
|
59
|
-
const m=/filename="?([^";]+)"?/.exec(cd);
|
|
60
|
-
a.download=m?m[1]:("le-veilleux-"+type+".md");
|
|
61
|
-
document.body.appendChild(a);a.click();a.remove();
|
|
62
|
-
toast(t("exportDone"));
|
|
63
|
-
}catch(e){toast(t("cannotReach"));}
|
|
64
|
-
}
|
|
65
|
-
async function loadRegister(){
|
|
66
|
-
const r=await api("/api/register");
|
|
67
|
-
if(!r||r.error)return;
|
|
68
|
-
const open=r.status==="open";
|
|
69
|
-
$("regClosed").classList.toggle("hidden",open);
|
|
70
|
-
$("regOpenState").classList.toggle("hidden",!open);
|
|
71
|
-
if(open){
|
|
72
|
-
$("regItems").textContent=(r.items||[]).map(x=>x.name+" — $"+x.price).join(" · ");
|
|
73
|
-
const cur=Math.round((r.items||[]).reduce((a,x)=>a+x.price,0)*100)/100;
|
|
74
|
-
$("regTotal").textContent=t("regTotal")+": $"+cur;
|
|
75
|
-
}
|
|
76
|
-
if(me&&me.role==="owner"){
|
|
77
|
-
if(r.status==="closed"){
|
|
78
|
-
$("regDay").innerHTML='<p class="note">'+t("regRecorded")+": $"+r.recorded+" · "+t("regDeclared2")+": $"+(r.declared==null?"—":r.declared)+'</p><p class="note" style="'+(r.variance!==null&&Math.abs(r.variance)>0.01?"color:#b45309;font-weight:700":"")+'">'+t("regVariance")+": $"+(r.variance==null?"—":r.variance)+'</p>';
|
|
79
|
-
} else {
|
|
80
|
-
$("regDay").innerHTML='<p class="hint">—</p>';
|
|
81
|
-
}
|
|
82
|
-
renderRegSales(r);
|
|
83
|
-
}
|
|
84
|
-
if(me&&me.role==="employee"){$("regCount").textContent=(r.sales||[]).length+" "+t("salesToday");}
|
|
85
|
-
try{api("/api/health").then(hp=>{if(hp&&hp.version)$("verBadge").textContent="supervisor v"+hp.version+" · app 0.1.0 · dsh "+((hp.workers!==undefined)?"":"")+"runtime";});}catch(e){}
|
|
86
|
-
}
|
|
87
|
-
function renderRegSales(r){
|
|
88
|
-
const q=($("regSearch").value||"").toLowerCase();
|
|
89
|
-
const m=$("regMethod").value;
|
|
90
|
-
const list=(r.sales||[]).slice().reverse().filter(x=>{
|
|
91
|
-
if(m!=="all"&&x.method!==m)return false;
|
|
92
|
-
if(q&&!(x.items||[]).some(it=>it.name.toLowerCase().indexOf(q)>=0))return false;
|
|
93
|
-
return true;
|
|
94
|
-
});
|
|
95
|
-
$("regSales").innerHTML=list.length?list.map(x=>'<div class="order"><b>'+esc(x.ts.slice(11,16))+'</b><span class="txt">'+esc((x.items||[]).map(it=>it.name).join(", "))+' · '+esc(x.worker||"?")+'</span><span class="stat">$ '+x.total+'</span></div>').join(""):empty("🧾","—");
|
|
96
|
-
}
|
|
97
|
-
$("regSearch").oninput=()=>{api("/api/register").then(renderRegSales);};
|
|
98
|
-
$("regMethod").onchange=()=>{api("/api/register").then(renderRegSales);};
|
|
99
|
-
$("regOpen").onclick=async()=>{await api("/api/register/open",{worker:me?me.name:"worker"});loadRegister();};
|
|
100
|
-
$("regAdd").onclick=async()=>{const n=$("regItem").value.trim();const p=$("regPrice").value;if(!n||!p)return;await api("/api/register/add",{name:n,price:Number(p)});$("regItem").value="";$("regPrice").value="";loadRegister();};
|
|
101
|
-
$("regSale").onclick=async()=>{await api("/api/register/charge",{method:$("regSaleMethod").value});loadRegister();toast(t("regSale"));};
|
|
102
|
-
$("regClose").onclick=async()=>{await api("/api/register/close",{declared:Number($("regDeclared").value)||0});loadRegister();};
|
|
103
|
-
$("exportRegisterBtn").onclick=()=>doExport("register");
|
|
104
|
-
$("deepSend").onclick=async()=>{const v=$("deepText").value.trim();if(!v)return;const r=await api("/api/deep",{worker:me?me.name:"owner",text:v});if(r.error){toast(String(r.error).indexOf("budget")>=0?t("budgetReached"):"Error");return;}$("deepText").value="";toast(t("deepSent"));};
|
|
105
|
-
$("exportBtn").onclick=()=>doExport("reports");
|
|
106
|
-
$("exportOrdersBtn").onclick=()=>doExport("orders");
|
|
107
|
-
function panelLinks(){const u=me?encodeURIComponent(me.name):"",r=me?encodeURIComponent(me.role):"",lb=me?encodeURIComponent(me.label||me.name):"";$("openPanelLink").setAttribute("href","/panel?user="+u+"&role="+r+"&label="+lb+"&lang="+cur);$("leadOpenPanelLink").setAttribute("href","/panel?user="+u+"&role="+r+"&label="+lb+"&lang="+cur);}
|
|
108
|
-
function applyLang(){
|
|
109
|
-
// (the language is persisted only on an explicit human choice)
|
|
110
|
-
$("welcomeClose").onclick=()=>{$("welcomeCard").style.display="none";try{localStorage.setItem("lv-welcome","1");}catch(e){}};
|
|
111
|
-
$("splashTag").textContent=t("tagline");
|
|
112
|
-
$("welcomeTitle").textContent=t("welcomeTitle");$("welcomeBody").textContent=t("welcomeBody");$("wallNote").textContent=t("wallNote");
|
|
113
|
-
$("setupUser").placeholder=t("setupUser");$("setupPass").placeholder=t("setupPass");$("setupBtn").textContent=t("setupBtn");$("setupHint").textContent=t("setupHint");
|
|
114
|
-
$("loginTitle").textContent=t("login");$("tagline").textContent=t("tagline");$("loginUser").placeholder=t("user");
|
|
115
|
-
["loginUser","loginPass"].forEach(id=>$(id).addEventListener("input",()=>$("loginError").classList.add("hidden")));$("loginPass").placeholder=t("password");
|
|
116
|
-
$("loginBtn").textContent=t("loginBtn");$("loginHint").textContent=t("loginHint");$("openWall").textContent=t("wallOpen");$("logoutBtn").textContent=t("logout");
|
|
117
|
-
$("tabOverview").textContent=t("overview");$("tabField").textContent=t("field");$("tabRules").textContent=t("rules");
|
|
118
|
-
$("workersTitle").textContent=t("workers");$("thUser").textContent=t("thUser");$("thRole").textContent=t("thRole");
|
|
119
|
-
$("addWorkerTitle").textContent=t("addWorker");$("newUser").placeholder=t("userName");$("addUserBtn").textContent=t("addBtn");
|
|
120
|
-
$("ordersTitle").textContent=t("ordersTitle");$("leadOrdersTitle").textContent=t("ordersTitle");$("ordersTitle2").textContent=t("ordersTitle");
|
|
121
|
-
$("usageTitle").textContent=t("usage");$("usageSub").textContent=t("usageSub");
|
|
122
|
-
$("thWorker").textContent=t("thWorker");$("thSessions").textContent=t("thSessions");$("thTokens").textContent=t("thTokens");$("thCost").textContent=t("thCost");
|
|
123
|
-
$("panelTitle").textContent=t("panel");$("panelNote").textContent=t("panelNote");$("openPanel").textContent=t("openPanel");
|
|
124
|
-
$("fieldReportsTitle").textContent=t("fieldReports");$("feedbackTitle").textContent=t("feedbackQueue");$("feedbackHint").textContent=t("feedbackHint");
|
|
125
|
-
$("regSearch").placeholder=t("regSearch");$("regMethod").options[0].textContent=t("allMethods");$("regMethod").options[1].textContent=t("cash");$("regMethod").options[2].textContent=t("card");$("regMethod").options[3].textContent=t("terminal");$("regSaleMethod").options[0].textContent=t("terminal");$("regSaleMethod").options[1].textContent=t("cash");$("regSaleMethod").options[2].textContent=t("card");
|
|
126
|
-
$("regTitle").textContent=t("regTitle");$("regOpen").textContent=t("regOpen");$("regItem").placeholder=t("regItem");$("regPrice").placeholder=t("regPrice");$("regAdd").textContent=t("regAdd");$("regSale").textContent=t("regSale");$("regHint").textContent=t("regHint");$("regClose").textContent=t("regClose");$("regDeclared").placeholder=t("regDeclared");$("regDayTitle").textContent=t("regDayTitle");$("exportRegisterBtn").textContent=t("exportReg");
|
|
127
|
-
$("deepTitle").textContent=t("deepTitle");$("deepText").placeholder=t("deepPh");$("deepSend").textContent=t("deepSend");$("deepHint").textContent=t("deepHint");
|
|
128
|
-
$("refreshReports").textContent=t("refresh");$("exportBtn").textContent=t("export");$("exportOrdersBtn").textContent=t("exportOrders");if(!auto)$("autoRefresh").textContent=t("autoOff");
|
|
129
|
-
$("rulesTitle").textContent=t("rulesTitle");$("rulesHint").textContent=t("rulesHint");$("saveDecisions").textContent=t("saveRules");
|
|
130
|
-
$("leadReportsTitle").textContent=t("fieldReports");$("leadFeedbackTitle").textContent=t("feedbackQueue");$("leadRefresh").textContent=t("refresh");$("leadOpenPanel").textContent=t("openPanel");
|
|
131
|
-
$("startBtn").textContent=t("start");$("listeningText").textContent=t("listening");$("listeningHint").textContent=t("listeningHint");
|
|
132
|
-
$("stopBtn").textContent=t("stopListening");$("summaryTitle").textContent=t("summaryTitle");$("newConvBtn").textContent=t("newConv");$("toHomeBtn").textContent=t("back");
|
|
133
|
-
$("reportTitle").textContent=t("reportTitle");$("reportText").placeholder=t("reportPh");$("reportBtn").textContent=t("sendReport");
|
|
134
|
-
$("fbTitle").textContent=t("fbTitle");$("empFbText").placeholder=t("fbPh");$("empFbBtn").textContent=t("sendFeedback");$("switchBtn").textContent=t("switchUser");
|
|
135
|
-
$("wallPulseTitle").textContent=t("wallPulse");
|
|
136
|
-
$("notifBtn").textContent=t("enableNotif");$("notifHint").textContent=t("notifHint");
|
|
137
|
-
$("optEmployee").textContent=t("lvlEmployee");$("optLead").textContent=t("lvlLead");$("optOwner").textContent=t("lvlOwner");$("newLabel").placeholder=t("labelPh");
|
|
138
|
-
$("tabSector").textContent=t("sectorTab");$("bnavOverview").textContent=t("overview");$("bnavField").textContent=t("field");$("bnavRules").textContent=t("rules");$("bnavSector").textContent=t("sectorTab");$("sectorPresetsTitle").textContent=t("sectorPresets");$("customSectorTitle").textContent=t("customSector");$("saveSectorBtn").textContent=t("saveSector");$("aiSectorBtn").textContent=t("aiSuggest");
|
|
139
|
-
$("trailTitle").textContent=t("conversationTrail");$("trailHint").textContent=t("trailHint");
|
|
140
|
-
$("sayText").placeholder=t("sayPh");$("sayBtn").textContent=t("sendPhrase");$("aiHint").textContent=t("aiHint");if(!recording)$("micBtn").textContent=t("dictate");
|
|
141
|
-
document.querySelectorAll(".pill").forEach(p=>p.classList.toggle("active",p.dataset.l===cur));
|
|
142
|
-
panelLinks();
|
|
143
|
-
reRenderDynamic();
|
|
144
|
-
}
|
|
145
|
-
const LANGNAMES={es:"Español",en:"English",fr:"Français",de:"Deutsch",it:"Italiano",pt:"Português",zh:"中文",ja:"日本語",ko:"한국어",ar:"العربية"};
|
|
146
|
-
const LANGORDER=["it","zh","de","es","en","fr","ja","pt","ko","ar"];
|
|
147
|
-
function buildLangMenu(){
|
|
148
|
-
const rows=$("langRows");
|
|
149
|
-
for(const l of LANGORDER){
|
|
150
|
-
const r=document.createElement("div");
|
|
151
|
-
r.className="orow"+(l==="en"?" center":"");
|
|
152
|
-
r.innerHTML='<span>'+LANGNAMES[l]+'</span><span class="sub">'+l.toUpperCase()+'</span>';
|
|
153
|
-
r.onclick=()=>{cur=l;try{localStorage.setItem("dsh-lang",cur);}catch(e){}applyLang();$("langOverlay").style.display="none";markReel();$("langStep").classList.add("done");const sp=$("splash");sp.classList.remove("done");const p=sp.querySelector(".draw");if(p){p.style.animation="none";void p.offsetWidth;p.style.animation="";}setTimeout(()=>{$("loginView").style.display="";document.body.classList.remove("firstboot");},2100);setTimeout(()=>{sp.classList.add("done");},2600);};
|
|
154
|
-
rows.appendChild(r);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function markReel(){
|
|
158
|
-
document.querySelectorAll("#langStepTicks .tick").forEach(t=>t.classList.toggle("active",t.dataset.l===cur));
|
|
159
|
-
}
|
|
160
|
-
function buildReel(ticksId){
|
|
161
|
-
for(let pass=0;pass<2;pass++){
|
|
162
|
-
for(const l of Object.keys(LANG)){
|
|
163
|
-
const d=document.createElement("div");
|
|
164
|
-
d.className="tick";d.dataset.l=l;d.textContent=LANGNAMES[l];
|
|
165
|
-
$(ticksId).appendChild(d);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const N=Object.keys(LANG).length,H=58,CYCLE=20000;
|
|
169
|
-
const t0=performance.now();
|
|
170
|
-
(function frame(now){
|
|
171
|
-
const t=(now-t0)%CYCLE;
|
|
172
|
-
$(ticksId).style.transform="translateY(-"+((t/CYCLE)*N*H)+"px)";
|
|
173
|
-
requestAnimationFrame(frame);
|
|
174
|
-
})(t0);
|
|
175
|
-
}
|
|
176
|
-
buildLangMenu();
|
|
177
|
-
buildReel("langStepTicks");
|
|
178
|
-
markReel();
|
|
179
|
-
$("langStepReel").onclick=()=>{$("langOverlay").style.display="flex";const en=document.querySelector("#langRows .center");if(en)en.scrollIntoView({block:"center"});};
|
|
180
|
-
$("closeOverlay").onclick=()=>{$("langOverlay").style.display="none";};
|
|
181
|
-
// The Apple flow: after the splash, the language menu opens once (until chosen).
|
|
182
|
-
|
|
183
|
-
function pill(l){for(const host of ["footLangs"]){const el=$(host);if(!el)continue;const b=document.createElement("button");b.className="pill";b.dataset.l=l;b.textContent=LANG[l];b.onclick=()=>{cur=l;try{localStorage.setItem("dsh-lang",cur);}catch(e){}applyLang();};$(host).appendChild(b);}}
|
|
184
|
-
for(const l of Object.keys(LANG))pill(l);
|
|
185
|
-
try{const q=new URLSearchParams(location.search);const ql=q.get("lang");if(ql&&S[ql])cur=ql;}catch(e){}
|
|
186
|
-
try{const saved=localStorage.getItem("dsh-lang");if(saved&&S[saved])cur=saved;}catch(e){}
|
|
187
|
-
applyLang();
|
|
188
|
-
function applyPack(){if(!PACK)return;$("tableInput").placeholder=(PACK.tableLabel&&PACK.tableLabel[cur])||"Table";const ot=(PACK.ordersTarget&&PACK.ordersTarget[cur])||t("ordersTitle");$("ordersTitle").textContent=ot;$("leadOrdersTitle").textContent=ot;$("ordersTitle2").textContent=ot;}
|
|
189
|
-
const api=async(p,body)=>{try{const r=await fetch(p,{method:body?"POST":"GET",headers:{"Content-Type":"application/json",...(token?{Authorization:"Bearer "+token}:{})},body:body?JSON.stringify(body):undefined});return r.json();}catch(e){return {error:"network"};}};
|
|
190
|
-
function show(view){["loginView","ownerView","leadView","employeeView","wallView"].forEach(v=>$(v).classList.add("hidden"));const el=$(view);el.classList.remove("hidden");el.classList.remove("anim");void el.offsetWidth;el.classList.add("anim");document.body.classList.toggle("loginpage",view==="loginView");}
|
|
191
|
-
const LVL={employee:"lvlEmployee",lead:"lvlLead",owner:"lvlOwner"};
|
|
192
|
-
function setWho(){const w=$("who");w.textContent=me?(me.label||me.name):"";const c=$("idChip");if(me){c.style.display="inline-block";c.textContent=me.name.slice(0,1).toUpperCase()+" · "+(me.label||me.name)+" · "+t(LVL[me.role]||"lvlEmployee");}else{c.style.display="none";}$("logoutBtn").style.display=me?"inline-block":"none";}
|
|
193
|
-
function toast(m){const el=$("toast");el.textContent=m;el.classList.add("show");clearTimeout(el.__t);el.__t=setTimeout(()=>el.classList.remove("show"),2200);}
|
|
194
|
-
function skel(n){let out="";for(let i=0;i<n;i++)out+='<div class="skel" style="height:38px;margin:8px 0"></div>';return out;}
|
|
195
|
-
const esc=(s)=>String(s==null?"":s).replace(/[&<>"']/g,(c)=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[c]));
|
|
196
|
-
function empty(ico,txt){return '<div class="empty"><span class="ico">'+ico+'</span>'+txt+'</div>';}
|
|
197
|
-
function showMsg(x){$("msg").textContent=x;toast(x);setTimeout(()=>$("msg").textContent="",2500);}
|
|
198
|
-
function countUp(el,target,dur){dur=dur||650;const t0=performance.now();const step=(now)=>{const p=Math.min(1,(now-t0)/dur);el.textContent=Math.round(target*p).toLocaleString();if(p<1)requestAnimationFrame(step);};requestAnimationFrame(step);}
|
|
199
|
-
async function loadMetrics(into){into.innerHTML=skel(2);const m=await api("/api/metrics");into.innerHTML="";const cards=[[t("reports"),m.reports],[t("week"),m.reportsWeek],[t("feedback"),m.feedback],[t("workersActive"),m.workers]];for(const [l,v] of cards){const d=document.createElement("div");d.className="metric";d.innerHTML='<div class="v">–</div><div class="l">'+l+'</div>';into.appendChild(d);if(typeof v==="number")countUp(d.querySelector(".v"),v);}}
|
|
200
|
-
async function loadUsage(){const tb=$("usageTable")?$("usageTable").querySelector("tbody"):null;if(!tb)return;tb.innerHTML='<tr><td colspan="4">'+skel(2)+'</td></tr>';const r=await api("/api/usage");tb.innerHTML="";
|
|
201
|
-
if(!(r.rows||[]).length){tb.innerHTML='<tr><td colspan="4">'+empty("📊",t("noUsage"))+'</td></tr>';return;}
|
|
202
|
-
(r.rows||[]).forEach(x=>{const tr=document.createElement("tr");tr.innerHTML="<td>"+esc(x.worker)+"</td><td>"+x.sessions+"</td><td>"+(x.tokens||0).toLocaleString()+"</td><td>$"+(x.cost||0).toFixed(2)+"</td>";tb.appendChild(tr);});try{const hp=await api("/api/health");if(hp&&hp.budgetUsd!==undefined){const spent=hp.spentTodayUsd||0;if(hp.budgetUsd>0){const pct=hp.budgetPct||0;$("usageBudget").textContent=t("budget")+": $"+spent+" / $"+hp.budgetUsd+" ("+pct+"%)";$("usageBudget").style.color=pct>=80?"#b45309":"";}else{$("usageBudget").textContent=t("budget")+": $"+spent+" ∞";$("usageBudget").style.color="";}}}catch(e){}}
|
|
203
|
-
function orderRow(o,canUpdate){
|
|
204
|
-
const d=document.createElement("div");d.className="order";
|
|
205
|
-
const stat=document.createElement("span");stat.className="stat";const ICON={sent:"📤",prep:"🍳",ready:"✅",delivered:"🛎️"};
|
|
206
|
-
stat.textContent=(ICON[o.status]||"📤")+" "+t(o.status||"sent");
|
|
207
|
-
if(o.paid){const p=document.createElement("span");p.className="stat";p.style.background="#dcfce7";p.style.color="#15803d";p.textContent=t("paid");d.appendChild(p);}
|
|
208
|
-
d.innerHTML="<b>"+(o.table?((PACK&&PACK.tableLabel&&PACK.tableLabel[cur]?PACK.tableLabel[cur]:"Table")+" "+o.table+" · "):"")+esc(o.person)+"</b>";
|
|
209
|
-
const txt=document.createElement("span");txt.className="txt";txt.textContent=o.request;
|
|
210
|
-
d.appendChild(txt);d.appendChild(stat);
|
|
211
|
-
if(canUpdate&&!o.paid){const pb=document.createElement("button");pb.className="btn small";pb.textContent=t("markPaid");pb.style.background="#16a34a";pb.onclick=async()=>{await api("/api/live/order",{orderId:o.id,status:"paid"});haptic();renderOrders();};d.appendChild(pb);}
|
|
212
|
-
if(canUpdate&&o.status!=="delivered"){
|
|
213
|
-
const next=o.status==="sent"?"prep":o.status==="prep"?"ready":"delivered";
|
|
214
|
-
const b=document.createElement("button");b.className="btn small";b.textContent=t(next)+" →";
|
|
215
|
-
b.onclick=async()=>{await api("/api/live/order",{orderId:o.id,status:next});haptic();renderOrders();};
|
|
216
|
-
d.appendChild(b);
|
|
217
|
-
}
|
|
218
|
-
return d;
|
|
219
|
-
}
|
|
220
|
-
async function renderOrders(){
|
|
221
|
-
for(const id of ["ownerOrders","leadOrders","workerOrders"]){const el=$(id);if(el)el.innerHTML=skel(2);}
|
|
222
|
-
const live=await api("/api/live");
|
|
223
|
-
for(const id of ["ownerOrders","leadOrders","workerOrders"]){
|
|
224
|
-
const el=$(id);if(!el)continue;
|
|
225
|
-
el.innerHTML="";
|
|
226
|
-
(live.orders||[]).forEach(o=>el.appendChild(orderRow(o,id!=="workerOrders")));
|
|
227
|
-
if(!(live.orders||[]).length)el.innerHTML=empty("📦",t("noOrders"));
|
|
228
|
-
if(id==="ownerOrders"){const os=live.orders||[];const paid=os.filter(o=>o.paid).length;$("billSummary").textContent=t("billSummary").replace("X",paid).replace("Y",os.length);}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
$("sayBtn").onclick=async()=>{const v=$("sayText").value.trim();if(!v)return;const r=await api("/api/live/transcript",{worker:me?me.name:"worker",text:v});if(r&&r.error){toast(String(r.error).indexOf("budget")>=0?t("budgetReached"):r.error);return;}$("sayText").value="";showMsg(t("processing"));};
|
|
232
|
-
const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
|
|
233
|
-
if(SR){$("micBtn").onclick=()=>{
|
|
234
|
-
if(rec){rec.stop();return;}
|
|
235
|
-
rec=new SR();rec.lang=(cur==="zh"?"zh-CN":cur==="ja"?"ja-JP":cur==="ko"?"ko-KR":cur==="ar"?"ar-SA":cur+"-"+(cur==="en"?"US":cur==="pt"?"PT":cur.toUpperCase()));
|
|
236
|
-
rec.continuous=false;rec.interimResults=false;recording=true;$("micBtn").textContent="⏹";
|
|
237
|
-
rec.onresult=async(e)=>{let txt="";for(let i=0;i<e.results.length;i++)txt+=e.results[i][0].transcript;await api("/api/live/transcript",{worker:me?me.name:"worker",text:txt});showMsg(t("processing"));};
|
|
238
|
-
rec.onend=()=>{recording=false;$("micBtn").textContent=t("dictate");rec=null;};
|
|
239
|
-
rec.start();
|
|
240
|
-
};}else{$("micBtn").disabled=true;$("micBtn").textContent=t("micUnsupported");}
|
|
241
|
-
function packFieldsHTML(){if(!PACK||!PACK.fields||!PACK.fields.length)return "";return PACK.fields.map(f=>'<input class="packField" data-fid="'+f.id+'" placeholder="'+(f.label&&f.label[cur]?f.label[cur]:f.id)+'">').join("");}
|
|
242
|
-
async function workerFlow(){
|
|
243
|
-
const live=await api("/api/live");
|
|
244
|
-
const wv=$("employeeView");
|
|
245
|
-
if(!me){return;}
|
|
246
|
-
$("wHome").classList.add("hidden");$("wListen").classList.add("hidden");$("wSummary").classList.add("hidden");
|
|
247
|
-
if(live.status==="listening"){$("wListen").classList.remove("hidden");$("listeningBtn").textContent="🎙️ "+ (live.worker||"");$("packFields").innerHTML=packFieldsHTML();$("tableInput").value=live.table||"";}
|
|
248
|
-
else if(live.status==="summary"){$("wSummary").classList.remove("hidden");$("summaryTitle").textContent=t("summaryTitle")+(live.table?(" — "+(PACK&&PACK.tableLabel&&PACK.tableLabel[cur]?PACK.tableLabel[cur]:"Table")+" "+live.table):"")+Object.entries(live.fields||{}).filter(([k,v])=>v).map(([k,v])=>" · "+v).join("");renderSummary(live);}
|
|
249
|
-
else{$("wHome").classList.remove("hidden");}
|
|
250
|
-
renderOrders();
|
|
251
|
-
}
|
|
252
|
-
function renderSummary(live){
|
|
253
|
-
const body=$("summaryBody");body.innerHTML="";
|
|
254
|
-
if(!(live.speakers||[]).length){body.innerHTML='<div class="card"><p class="note">'+t("noSummary")+'</p></div>';return;}
|
|
255
|
-
for(const p of live.speakers){
|
|
256
|
-
const d=document.createElement("div");d.className="person";
|
|
257
|
-
const done=p.requests.filter(r=>r.status==="confirmed").length;
|
|
258
|
-
let h='<div class="pname">'+t("person")+" "+(p.id.replace("p",""))+" — "+esc(p.name)+'<span class="pcnt">('+done+'/'+p.requests.length+')</span></div>';
|
|
259
|
-
for(const r of p.requests){
|
|
260
|
-
const confirmed=r.status==="confirmed";
|
|
261
|
-
h+='<div class="req"><span class="txt">'+esc(r.text)+'</span>'+
|
|
262
|
-
'<span class="tier tier-'+r.tier+'">'+t("tier"+r.tier)+'</span>'+
|
|
263
|
-
'<button class="confirmBtn'+(confirmed?" done":"")+'" data-req="'+r.id+'">'+(confirmed?t("confirmed"):t("confirm"))+'</button></div>'+
|
|
264
|
-
(confirmed?'<div class="sentline">✓ '+t("sentNote")+'</div>':'');
|
|
265
|
-
}
|
|
266
|
-
d.innerHTML=h;
|
|
267
|
-
d.querySelectorAll(".confirmBtn").forEach(b=>b.onclick=async()=>{
|
|
268
|
-
if(b.classList.contains("done"))return;
|
|
269
|
-
b.textContent=t("confirmed");b.classList.add("done","checkpop");haptic();toast(t("confirmed"));
|
|
270
|
-
await api("/api/live/confirm",{requestId:b.dataset.req});
|
|
271
|
-
workerFlow();
|
|
272
|
-
});
|
|
273
|
-
body.appendChild(d);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
$("startBtn").onclick=async()=>{const fields={};document.querySelectorAll(".packField").forEach(i=>{fields[i.dataset.fid]=i.value.trim();});await api("/api/live/start",{worker:me.name,table:$("tableInput").value.trim()||null,fields});workerFlow();};
|
|
277
|
-
$("stopBtn").onclick=async()=>{const live=await api("/api/live");if((live.speakers||[]).length){live.status="summary";await api("/api/live/summary",{speakers:live.speakers});}else{await api("/api/live/close",{});}workerFlow();};
|
|
278
|
-
$("newConvBtn").onclick=async()=>{await api("/api/live/close",{});workerFlow();};
|
|
279
|
-
$("toHomeBtn").onclick=async()=>{await api("/api/live/close",{});workerFlow();};
|
|
280
|
-
async function enter(){
|
|
281
|
-
if(!PACK){PACK=await api("/api/sector");applyPack();}
|
|
282
|
-
setWho();
|
|
283
|
-
if(me.role==="owner"){show("ownerView");try{if(!localStorage.getItem("lv-welcome"))$("welcomeCard").style.display="block";}catch(e){}loadRegister();loadMetrics($("ownerMetrics"));loadUsers();loadField();loadRules();loadUsage();renderOrders();loadPresets();}
|
|
284
|
-
else if(me.role==="lead"){show("leadView");loadMetrics($("leadMetrics"));loadLead();renderOrders();if(kitchenPoll)clearInterval(kitchenPoll);kitchenPoll=setInterval(kitchenWatch,4000);}
|
|
285
|
-
else{show("employeeView");$("workerHi").textContent=t("hi")+(me.label||me.name);loadRegister();workerFlow();if(livePoll)clearInterval(livePoll);livePoll=setInterval(()=>{if(me&&me.role==="employee")workerFlow();},3000);}
|
|
286
|
-
}
|
|
287
|
-
let pwVisible=false;
|
|
288
|
-
$("eyeBtn").onclick=()=>{pwVisible=!pwVisible;$("loginPass").type=pwVisible?"text":"password";$("eyeOn").classList.toggle("hidden",pwVisible);$("eyeOff").classList.toggle("hidden",!pwVisible);};
|
|
289
|
-
$("setupBtn").onclick=async()=>{
|
|
290
|
-
const user=$("setupUser").value.trim();
|
|
291
|
-
const password=$("setupPass").value;
|
|
292
|
-
if(!user||password.length<8){toast(t("fillBoth"));return;}
|
|
293
|
-
$("setupBtn").disabled=true;$("setupBtn").innerHTML='<span class="spin"></span>'+t("loggingIn");
|
|
294
|
-
const r=await api("/api/setup",{user,password,sector:$("setupSector").value});
|
|
295
|
-
if(r.error){$("setupBtn").disabled=false;$("setupBtn").textContent=t("setupBtn");toast(String(r.error).indexOf("already")>=0?t("loginFailed"):r.error);return;}
|
|
296
|
-
token=r.token;me={name:r.name,role:r.role,label:r.label||r.name};
|
|
297
|
-
sessionStorage.setItem("dsh-sup-token",token);sessionStorage.setItem("dsh-sup-me",JSON.stringify(me));
|
|
298
|
-
enter();
|
|
299
|
-
};
|
|
300
|
-
$("loginBtn").onclick=async()=>{
|
|
301
|
-
const err=$("loginError");
|
|
302
|
-
err.classList.add("hidden");
|
|
303
|
-
const u=$("loginUser").value.trim(),p=$("loginPass").value;
|
|
304
|
-
if(!u||!p){err.textContent=t("fillBoth");err.classList.remove("hidden");return;}
|
|
305
|
-
$("loginBtn").disabled=true;$("loginBtn").innerHTML='<span class="spin"></span>'+t("loggingIn");
|
|
306
|
-
const r=await api("/api/login",{user:u,password:p});
|
|
307
|
-
$("loginBtn").disabled=false;$("loginBtn").textContent=t("loginBtn");
|
|
308
|
-
if(r.error){err.textContent=r.error==="network"?t("cannotReach"):t("loginFailed");err.classList.remove("hidden");err.classList.remove("shake");void err.offsetWidth;err.classList.add("shake");return;}
|
|
309
|
-
err.classList.add("hidden");
|
|
310
|
-
token=r.token;me={name:r.name,role:r.role,label:r.label||r.name};
|
|
311
|
-
sessionStorage.setItem("dsh-sup-token",token);sessionStorage.setItem("dsh-sup-me",JSON.stringify(me));
|
|
312
|
-
$("loginPass").value="";enter();
|
|
313
|
-
};
|
|
314
|
-
function logout(){api("/api/logout",{});token=null;me=null;sessionStorage.clear();if(livePoll)clearInterval(livePoll);if(kitchenPoll)clearInterval(kitchenPoll);setWho();show("loginView");}
|
|
315
|
-
$("logoutBtn").onclick=logout;
|
|
316
|
-
const sb=$("switchBtn");if(sb)sb.onclick=logout;
|
|
317
|
-
async function loadUsers(){const tb=$("usersTable").querySelector("tbody");tb.innerHTML='<tr><td colspan="2">'+skel(2)+'</td></tr>';const r=await api("/api/users");tb.innerHTML="";(r.users||[]).forEach(u=>{const tr=document.createElement("tr");tr.innerHTML="<td>"+esc(u.name)+"</td><td>"+esc(u.label||u.name)+" · "+t(LVL[u.role]||"lvlEmployee")+"</td>";tb.appendChild(tr);});}
|
|
318
|
-
$("addUserBtn").onclick=async()=>{const r=await api("/api/users",{name:$("newUser").value,password:$("newPass").value,role:$("newRole").value,label:$("newLabel").value});showMsg(r.error?"Error: "+r.error:t("workerAdded"));if(!r.error){loadUsers();$("newUser").value=$("newPass").value="";}};
|
|
319
|
-
async function loadField(){$("reports").innerHTML=skel(2);$("feedback").innerHTML=skel(1);$("trail").innerHTML=skel(1);const rc=(await api("/api/reports")).content;if(rc){$("reports").innerHTML="<pre>"+esc(rc)+"</pre>";}else{$("reports").innerHTML=empty("📋",t("noReports"));}const fc=(await api("/api/feedback")).content;if(fc){$("feedback").innerHTML="<pre>"+esc(fc)+"</pre>";}else{$("feedback").innerHTML=empty("💬",t("feedbackQueue"));}const live=await api("/api/live");const ib=(live.inbox||[]).map(x=>"[•] "+esc(x.worker)+": "+esc(x.text)).join("\n");if(ib){$("trail").innerHTML="<pre>"+ib+"</pre>";}else{$("trail").innerHTML=empty("🗒️",t("conversationTrail"));}}
|
|
320
|
-
async function loadLead(){$("leadReports").textContent=(await api("/api/reports")).content||"(empty)";$("leadFeedback").textContent=(await api("/api/feedback")).content||"(empty)";}
|
|
321
|
-
$("refreshReports").onclick=loadField;$("leadRefresh").onclick=loadLead;
|
|
322
|
-
const ar=$("autoRefresh");
|
|
323
|
-
if(ar)ar.onclick=()=>{auto=!auto;ar.textContent=auto?t("autoOn"):t("autoOff");if(auto){const iv=setInterval(()=>{if(!auto){clearInterval(iv);return;}if(me&&me.role==="owner"){loadField();renderOrders();}},30000);}};
|
|
324
|
-
async function loadPresets(){
|
|
325
|
-
const r=await api("/api/sector-presets");
|
|
326
|
-
const g=$("presetGrid");if(!g)return;g.innerHTML="";
|
|
327
|
-
(r.presets||[]).forEach(p=>{const b=document.createElement("button");b.className="btn small gray";b.textContent=p.id;b.onclick=async()=>{await api("/api/sector",p);PACK=p;applyPack();showMsg(t("sectorSaved"));renderSuggest();};g.appendChild(b);});
|
|
328
|
-
renderSuggest();
|
|
329
|
-
}
|
|
330
|
-
function renderSuggest(){const el=$("suggestUsers");if(!el||!PACK||!PACK.suggestedUsers)return;el.innerHTML="";(PACK.suggestedUsers||[]).forEach(su=>{const b=document.createElement("button");b.className="btn small gray";b.textContent="+ "+(su.label&&(su.label[cur]||su.label.en)||su.level);b.onclick=()=>{$("newUser").value="";$("newLabel").value=su.label&&(su.label[cur]||su.label.en)||"";$("newRole").value=su.level;};el.appendChild(b);});}
|
|
331
|
-
$("saveSectorBtn").onclick=async()=>{
|
|
332
|
-
const pack={id:$("csId").value.trim()||"custom",
|
|
333
|
-
tableLabel:{en:$("csLabelEn").value.trim()||"Table",fr:$("csLabelFr").value.trim(),es:$("csLabelEn").value.trim()||"Table"},
|
|
334
|
-
ordersTarget:{en:$("csOrdersEn").value.trim()||"Orders",fr:$("csOrdersFr").value.trim()},
|
|
335
|
-
entity:{en:"person",fr:"personne"},
|
|
336
|
-
fields:[]};
|
|
337
|
-
if($("csF1Id").value.trim())pack.fields.push({id:$("csF1Id").value.trim(),type:"text",label:{en:$("csF1Label").value.trim()||$("csF1Id").value.trim()}});
|
|
338
|
-
if($("csF2Id").value.trim())pack.fields.push({id:$("csF2Id").value.trim(),type:"text",label:{en:$("csF2Label").value.trim()||$("csF2Id").value.trim()}});
|
|
339
|
-
const r=await api("/api/sector",pack);showMsg(r.error?"Error":t("sectorSaved"));PACK=pack;applyPack();
|
|
340
|
-
};
|
|
341
|
-
$("aiSectorBtn").onclick=async()=>{
|
|
342
|
-
const prompt="Live mirror mode (workflow-master). Design a sector pack JSON for this business, in this exact shape: {\"id\":\"<sector-id>\",\"tableLabel\":{\"en\":\"<entity label>\",\"es\":\"…\",\"fr\":\"…\"},\"ordersTarget\":{\"en\":\"<orders title>\",\"fr\":\"…\"},\"entity\":{\"en\":\"<entity>\",\"fr\":\"…\"},\"fields\":[{\"id\":\"<field>\",\"type\":\"number|text\",\"label\":{\"en\":\"<label>\",\"fr\":\"…\"}}]}. Describe your business here: "+($("csLabelEn").value||"(your business)")+". Reply ONLY with the JSON.";
|
|
343
|
-
try{await navigator.clipboard.writeText(prompt);}catch(e){}
|
|
344
|
-
showMsg(t("aiSectorCopied"));
|
|
345
|
-
};
|
|
346
|
-
async function loadRules(){$("decisions").value=(await api("/api/decisions")).content||"";}
|
|
347
|
-
$("saveDecisions").onclick=async()=>{await api("/api/decisions",{content:$("decisions").value});$("decisionsSaved").textContent=t("saved");setTimeout(()=>$("decisionsSaved").textContent="",2000);};
|
|
348
|
-
document.querySelectorAll(".tabs button, .bnav button").forEach(b=>b.onclick=()=>{document.querySelectorAll(".tabs button, .bnav button").forEach(x=>x.classList.remove("active"));b.classList.add("active");["overview","field","rules","sector"].forEach(x=>$("tab-"+x).classList.add("hidden"));$("tab-"+b.dataset.tab).classList.remove("hidden");});
|
|
349
|
-
$("reportBtn").onclick=async()=>{const r=await api("/api/report",{text:$("reportText").value});if(r.error){showMsg("Error: "+r.error);return;}$("reportText").value="";showMsg(t("reportSent"));};
|
|
350
|
-
$("empFbBtn").onclick=async()=>{const r=await api("/api/feedback",{worker:me.name,role:me.role,category:$("empFbCat").value,text:$("empFbText").value});if(r.error){showMsg("Error: "+r.error);return;}$("empFbText").value="";showMsg(t("feedbackSent"));};
|
|
351
|
-
api("/api/setup-status").then((st)=>{if(st&&st.users===0){$("setupSection").classList.remove("hidden");$("loginUser").parentNode.style.display="none";$("loginBtn").style.display="none";$("loginPass").closest(".passwrap").style.display="none";$("loginTitle").textContent=t("setupTitle");}}).catch(()=>{});
|
|
352
|
-
const st=sessionStorage.getItem("dsh-sup-token");
|
|
353
|
-
if(st){token=st;me=JSON.parse(sessionStorage.getItem("dsh-sup-me")||"null");(async()=>{const r=await api("/api/me");if(!r||!r.name){logout();return;}enter();})();}
|
|
354
|
-
else setWho();
|
|
355
|
-
async function wallTick(){try{const r=await (await fetch("/api/wall")).json();const cards=[[t("week"),r.reportsWeek],[t("ordersTitle"),r.orders],[t("feedback"),r.feedback],[t("workersActive"),r.workers],[t("reportsTotal"),r.reports]];$("wallCards").innerHTML=cards.map(c=>'<div class="wall-card"><div class="v">'+(c[1]===undefined?"–":c[1])+'</div><div class="l">'+c[0]+'</div></div>').join("");const ticks=[...(r.ordersTail||[]).map(x=>"🔔 "+esc(x)),...(r.feedbackTail||"").split("\n").filter(Boolean).slice(-4).map(esc)];$("wallFeed").innerHTML=ticks.length?ticks.map(x=>'<div class="wtick">'+x+'</div>').join(""):'<div class="wtick dim">'+t("noActivity")+'</div>';try{const s=await (await fetch("/api/sector")).json();$("wallSector").textContent=(s&&s.id)?s.id:"";}catch(e){}}catch(e){$("wallFeed").innerHTML='<div class="wtick dim">'+t("cannotReach")+'</div>';}}
|
|
356
|
-
function clockTick(){const d=new Date();$("wallClock").textContent=d.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});$("wallDate").textContent=d.toLocaleDateString([],{weekday:"short",day:"numeric",month:"short"});}
|
|
357
|
-
if(location.search.indexOf("wall=1")>=0){
|
|
358
|
-
document.body.classList.add("wall");show("wallView");
|
|
359
|
-
clockTick();setInterval(clockTick,1000);
|
|
360
|
-
wallTick();setInterval(wallTick,30000);
|
|
361
|
-
}
|