-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathowner.html
More file actions
208 lines (182 loc) · 10.6 KB
/
Copy pathowner.html
File metadata and controls
208 lines (182 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Owner View — Bossman</title>
<link rel="stylesheet" href="brand.css">
<style>
body { padding: 1rem; }
.page { max-width: 480px; margin: 0 auto; }
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.header h1 { font-family: var(--serif); font-size: 1.3rem; font-weight: 700; color: var(--cream); }
.header p { font-size: 0.72rem; color: var(--cream-dim); margin-top: 0.15rem; letter-spacing: 0.05em; }
.refresh-btn { background: var(--moss); border: 1px solid var(--line); border-radius: 0.6rem; padding: 0.5rem 0.8rem; font-size: 0.8rem; color: var(--gold); cursor: pointer; font-weight: 500; font-family: var(--sans); }
.refresh-btn:active { background: var(--fern); }
.summary { display: flex; gap: 0.75rem; margin-bottom: 1rem; }
.sum-card { flex: 1; background: var(--moss); border: 1px solid var(--line); border-radius: 0.75rem; box-shadow: var(--shadow); padding: 0.85rem; text-align: center; }
.sum-val { font-family: var(--serif); font-size: 1.6rem; font-weight: 800; color: var(--gold); }
.sum-label { font-size: 0.66rem; color: var(--cream-dim); margin-top: 0.15rem; letter-spacing: 0.05em; text-transform: uppercase; }
.barbers { display: flex; flex-direction: column; gap: 1rem; }
.barber-card { background: var(--moss); border: 1px solid var(--line); border-radius: 1rem; box-shadow: var(--shadow); overflow: hidden; }
.barber-header { padding: 0.95rem 1rem; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
.barber-name { font-family: var(--serif); font-size: 1.05rem; font-weight: 700; color: var(--cream); }
.barber-meta { font-size: 0.75rem; color: var(--cream-dim); margin-top: 0.15rem; letter-spacing: 0.02em; }
.status-pill { font-size: 0.7rem; font-weight: 600; padding: 0.28rem 0.7rem; border-radius: 999px; letter-spacing: 0.03em; }
.pill-active { background: rgba(126,126,56,0.25); color: var(--gold-bright); }
.pill-inactive { background: var(--pine); color: var(--cream-dim); }
.pill-busy { background: var(--gold); color: var(--pine); }
.serving-row { background: rgba(201,167,106,0.1); padding: 0.7rem 1rem; display: flex; align-items: center; gap: 0.6rem; border-bottom: 1px solid var(--line); }
.serving-label { font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--gold); min-width: 3rem; }
.serving-name { font-size: 0.9rem; font-weight: 600; color: var(--cream); }
.serving-phone { font-size: 0.74rem; color: var(--cream-dim); }
.queue-rows { padding: 0.4rem 0; }
.queue-row { display: flex; align-items: center; gap: 0.65rem; padding: 0.55rem 1rem; }
.queue-row:not(:last-child) { border-bottom: 1px solid rgba(233,221,193,0.06); }
.q-num { width: 1.6rem; height: 1.6rem; border-radius: 50%; background: var(--pine); border: 1px solid var(--fern); display: flex; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 700; color: var(--gold); flex-shrink: 0; }
.q-name { font-size: 0.86rem; font-weight: 600; color: var(--cream); }
.q-phone { font-size: 0.73rem; color: var(--cream-dim); }
.q-wait { margin-left: auto; font-size: 0.72rem; color: var(--cream-dim); white-space: nowrap; }
.eod { background: linear-gradient(135deg, var(--gold), var(--gold-bright)); color: var(--pine); border-radius: 1rem; padding: 1.1rem 1.25rem; margin-bottom: 1rem; }
.eod-head { display: flex; align-items: center; justify-content: space-between; }
.eod-title { font-family: var(--serif); font-weight: 800; font-size: 1.05rem; }
.eod-date { font-size: 0.7rem; opacity: 0.75; letter-spacing: 0.05em; }
.eod-nums { display: flex; gap: 2rem; margin-top: 0.6rem; }
.eod-val { font-family: var(--serif); font-weight: 800; font-size: 1.9rem; line-height: 1; }
.eod-lbl { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.8; margin-top: 0.15rem; }
.eod-workers { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; margin-top: 0.85rem; padding-top: 0.7rem; border-top: 1px solid rgba(14,40,24,0.2); }
.eod-workers span { font-size: 0.78rem; }
.eod-workers b { font-family: var(--serif); }
.empty-queue { padding: 1rem; text-align: center; color: var(--cream-dim); opacity: 0.6; font-size: 0.85rem; }
.refresh-note { text-align: center; font-size: 0.68rem; color: var(--cream-dim); opacity: 0.5; padding: 1rem 0 0.5rem; letter-spacing: 0.05em; }
</style>
</head>
<body>
<div class="page">
<div class="header">
<div>
<h1>Bossman — Owner</h1>
<p id="clock">🕐 --:--:-- MYT</p>
<p id="last-refresh" style="font-size:0.68rem;color:var(--cream-dim);opacity:0.7;margin-top:0.1rem;">Loading…</p>
</div>
<button class="refresh-btn" onclick="loadAll()">↻ Refresh</button>
</div>
<div class="summary">
<div class="sum-card"><div class="sum-val" id="sum-total">–</div><div class="sum-label">total waiting</div></div>
<div class="sum-card"><div class="sum-val" id="sum-serving">–</div><div class="sum-label">being served</div></div>
<div class="sum-card"><div class="sum-val" id="sum-active">–</div><div class="sum-label">barbers active</div></div>
</div>
<div id="eod" class="eod hidden">
<div class="eod-head">
<span class="eod-title">📊 Today's Total</span>
<span class="eod-date" id="eod-date"></span>
</div>
<div class="eod-nums">
<div><div class="eod-val" id="eod-served">–</div><div class="eod-lbl">served</div></div>
<div><div class="eod-val" id="eod-joined">–</div><div class="eod-lbl">total joined</div></div>
</div>
<div id="eod-workers" class="eod-workers"></div>
</div>
<div class="barbers" id="barbers-container">
<p style="text-align:center;color:var(--cream-dim);padding:2rem">Loading…</p>
</div>
<p class="refresh-note">Auto-refreshes every 30 seconds</p>
</div>
<script src="api.js"></script>
<script>
function remainingCut(serving, avg) {
if (!serving) return 0;
var duration = serving.durationMinutes || avg;
if (!serving.calledAt) return duration;
var elapsedMin = (Date.now() - new Date(serving.calledAt).getTime()) / 60000;
return Math.max(0, Math.round(duration - elapsedMin));
}
function tickClock() {
var el = document.getElementById("clock");
if (!el) return;
var t = new Date().toLocaleTimeString("en-GB", { timeZone: "Asia/Kuala_Lumpur", hour: "2-digit", minute: "2-digit", second: "2-digit" });
el.textContent = "🕐 " + t + " MYT";
}
setInterval(tickClock, 1000);
var OWNER_PIN = new URLSearchParams(window.location.search).get("pin") || "";
function loadAll() {
call({ action: "getOwnerView", pin: OWNER_PIN }).then(function(res) {
if (!res || res.error) {
document.getElementById("barbers-container").innerHTML = "<p style='text-align:center;color:#e88;padding:2rem'>"
+ (res && res.error === "bad_pin" ? "Invalid owner PIN — check your link." : "Failed to load. Please refresh.") + "</p>";
return;
}
render(res.workers || []);
}).catch(function() {
document.getElementById("barbers-container").innerHTML = "<p style='text-align:center;color:#e88;padding:2rem'>Failed to load. Please refresh.</p>";
});
}
function render(workers) {
try {
var now = new Date();
document.getElementById("last-refresh").textContent = "Updated " + now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
var totalWaiting = 0, totalServing = 0, totalActive = 0;
var html = "";
workers.forEach(function(b) {
var queue = b.queue || [];
var waiting = queue.filter(function(e) { return e.status === "waiting"; });
var serving = queue.find(function(e) { return e.status === "called"; });
totalWaiting += waiting.length;
if (serving) totalServing++;
if (b.isActive) totalActive++;
var pillClass = !b.isActive ? "pill-inactive" : serving ? "pill-busy" : "pill-active";
var pillText = !b.isActive ? "Inactive" : serving ? "Cutting" : "Active";
var avg = 35; // flat average wait estimate per customer
var remaining = remainingCut(serving, avg);
html += '<div class="barber-card"><div class="barber-header">';
html += '<div><div class="barber-name">' + b.name + '</div>';
var lastWait = waiting.length === 0 ? 0 : (serving ? remaining : 0) + (waiting.length - 1) * avg;
html += '<div class="barber-meta">' + waiting.length + ' waiting · ~' + lastWait + ' min total</div></div>';
html += '<span class="status-pill ' + pillClass + '">' + pillText + '</span></div>';
if (serving) {
html += '<div class="serving-row"><span class="serving-label">Now</span>';
html += '<div><div class="serving-name">' + serving.customerName + '</div>';
html += '<div class="serving-phone">' + serving.phone + '</div></div></div>';
}
html += '<div class="queue-rows">';
if (waiting.length === 0 && !serving) {
html += '<div class="empty-queue">Queue is empty</div>';
} else {
waiting.forEach(function(entry, i) {
var waitMins = (serving ? remaining : 0) + i * avg;
html += '<div class="queue-row"><div class="q-num">' + (i + 1) + '</div>';
html += '<div><div class="q-name">' + entry.customerName + '</div>';
html += '<div class="q-phone">' + entry.phone + '</div></div>';
html += '<div class="q-wait">~' + waitMins + ' min</div></div>';
});
}
html += '</div></div>';
});
document.getElementById("barbers-container").innerHTML = html;
document.getElementById("sum-total").textContent = totalWaiting;
document.getElementById("sum-serving").textContent = totalServing;
document.getElementById("sum-active").textContent = totalActive;
} catch (err) {
document.getElementById("barbers-container").innerHTML = "<p style='text-align:center;color:#e88;padding:2rem'>Display error. Please refresh.</p>";
}
}
function loadStats() {
call({ action: "getDailyStats" }).then(function(s) {
if (!s || s.error || typeof s.totalJoined === "undefined") return; // needs redeploy
document.getElementById("eod").classList.remove("hidden");
document.getElementById("eod-date").textContent = s.date || "";
document.getElementById("eod-served").textContent = s.totalServed;
document.getElementById("eod-joined").textContent = s.totalJoined;
var w = (s.perWorker || []).map(function(p) {
return "<span><b>" + p.name + "</b> — " + p.served + " served</span>";
}).join("");
document.getElementById("eod-workers").innerHTML = w;
}).catch(function() {});
}
loadAll();
loadStats();
setInterval(loadAll, 30000);
setInterval(loadStats, 60000);
</script>
</body>
</html>