@rubytech/create-maxy-code 0.1.473 → 0.1.474
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/dist/__tests__/google-client-id-env.test.js +3 -2
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +4 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +14 -10
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/google-guide.md +10 -6
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +2 -2
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/google/PLUGIN.md +25 -14
- package/payload/platform/plugins/google/references/auth.md +250 -365
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +3 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts +14 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js +15 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +3 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +51 -4
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +4 -0
- package/payload/platform/scripts/backfill-task-ids.sh +206 -0
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-B7TQZV8u.js → AdminLoginScreens-B44KbEuC.js} +1 -1
- package/payload/server/public/assets/{AdminShell-DAuri4Yh.js → AdminShell-BjxV2qkb.js} +2 -2
- package/payload/server/public/assets/{Checkbox-B1XhnhPk.js → Checkbox-DSeZNGNA.js} +1 -1
- package/payload/server/public/assets/{activity-kRwzxEKU.js → activity-CdipMTDp.js} +1 -1
- package/payload/server/public/assets/{admin-BdcccSb5.js → admin-B4LpTCur.js} +1 -1
- package/payload/server/public/assets/{browser-B6woQ2qX.js → browser-CEFvaWwI.js} +1 -1
- package/payload/server/public/assets/{calendar-CalCeg5T.js → calendar-B3zo6V34.js} +1 -1
- package/payload/server/public/assets/chat-5tJdo9i_.js +1 -0
- package/payload/server/public/assets/chevron-left-BJllrPBj.js +1 -0
- package/payload/server/public/assets/data-Du79EgR-.js +1 -0
- package/payload/server/public/assets/{graph-HeZaatSQ.js → graph-RaOrnSf2.js} +1 -1
- package/payload/server/public/assets/{graph-labels-4W7JuOgG.js → graph-labels-GlTEIYJj.js} +1 -1
- package/payload/server/public/assets/{maximize-2-DKx-aTPb.js → maximize-2-CHDa8p5h.js} +1 -1
- package/payload/server/public/assets/{operator-DD9L2VJM.js → operator-DtqHY4jV.js} +1 -1
- package/payload/server/public/assets/{page-tJxw0zNO.js → page-CkhFj8GQ.js} +1 -1
- package/payload/server/public/assets/{page-Ceado9Kr.js → page-DxnWgPtd.js} +1 -1
- package/payload/server/public/assets/{public-BYFWxyeC.js → public-D5TUfQ3I.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DrzdV9h7.js → rotate-ccw-BJjVAQQY.js} +1 -1
- package/payload/server/public/assets/tasks-CM9pYYG1.js +1 -0
- package/payload/server/public/assets/{time-entry-format-Bn79w6tq.js → time-entry-format-C5RYEYjM.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-CjDVh6r-.js → triangle-alert-DLfIsuo_.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-DXHkkJw-.js → useCopyFeedback-7yKeiAzV.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-D2H8jAhE.js → useSelectionMode-CWlh6AEq.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-Br7Z6Cpu.css → useSubAccountSwitcher-B2n9QPVI.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-zUvPe55I.js → useVoiceRecorder-j3ZArZ2H.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +65 -13
- package/payload/server/public/assets/chat-9s2-L06v.js +0 -1
- package/payload/server/public/assets/chevron-left-C3ho8SnB.js +0 -1
- package/payload/server/public/assets/data-Cfr-7ERI.js +0 -1
- package/payload/server/public/assets/tasks-DSwoxGzh.js +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-CUJpgkKI.js → useSubAccountSwitcher-CBlKgwms.js} +0 -0
package/payload/server/server.js
CHANGED
|
@@ -8448,6 +8448,23 @@ function conversationCounterpartyPhone(remoteJid, earliestSenderTelephone) {
|
|
|
8448
8448
|
function counterpartyBoundToSubAccount(managerPhones, counterpartyPhone) {
|
|
8449
8449
|
return counterpartyPhone != null && managerPhones.some((mp) => phonesMatch(mp, counterpartyPhone));
|
|
8450
8450
|
}
|
|
8451
|
+
function storePushNamesBySender(summaries) {
|
|
8452
|
+
const best = /* @__PURE__ */ new Map();
|
|
8453
|
+
const out = /* @__PURE__ */ new Map();
|
|
8454
|
+
for (const [remoteJid, { first, last, firstNamedSender }] of summaries) {
|
|
8455
|
+
if (!firstNamedSender) continue;
|
|
8456
|
+
const phone = conversationCounterpartyPhone(remoteJid, first.senderTelephone);
|
|
8457
|
+
if (!phone) continue;
|
|
8458
|
+
const key = normalizeE164(phone);
|
|
8459
|
+
if (key.length === 0) continue;
|
|
8460
|
+
const at = last.dateSent ?? "";
|
|
8461
|
+
const seen = best.get(key);
|
|
8462
|
+
if (seen !== void 0 && (seen.at > at || seen.at === at && seen.jid <= remoteJid)) continue;
|
|
8463
|
+
best.set(key, { at, jid: remoteJid });
|
|
8464
|
+
out.set(key, firstNamedSender);
|
|
8465
|
+
}
|
|
8466
|
+
return out;
|
|
8467
|
+
}
|
|
8451
8468
|
function buildStoreConversationRows(accountId, summaries) {
|
|
8452
8469
|
const rows = [];
|
|
8453
8470
|
for (const [remoteJid, { first, last, firstNamedSender }] of summaries) {
|
|
@@ -10121,7 +10138,8 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
10121
10138
|
personName: null,
|
|
10122
10139
|
model: lastAssistantModel(body),
|
|
10123
10140
|
accountId: rowAccountId,
|
|
10124
|
-
senderId
|
|
10141
|
+
senderId,
|
|
10142
|
+
whatsappName: null
|
|
10125
10143
|
});
|
|
10126
10144
|
if ((sidecarChannel === "whatsapp" || sidecarChannel === "telegram") && resolved.source === "prefix") {
|
|
10127
10145
|
if (senderId) channelSenderBySession.set(sessionId, senderId);
|
|
@@ -10162,20 +10180,36 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
10162
10180
|
);
|
|
10163
10181
|
}
|
|
10164
10182
|
let channelNamesResolved = 0;
|
|
10183
|
+
let operatorResolvedCount = 0;
|
|
10184
|
+
let storeResolvedCount = 0;
|
|
10165
10185
|
const channelNameCandidates = channelSenderBySession.size + channelMissNoSender.length;
|
|
10166
10186
|
if (account && channelSenderBySession.size > 0) {
|
|
10167
10187
|
const resolvedNames = await resolveOperatorNamesBySender(channelUsers, channelAdmins, channelSenderBySession.values());
|
|
10188
|
+
let pushNames = /* @__PURE__ */ new Map();
|
|
10189
|
+
try {
|
|
10190
|
+
pushNames = storePushNamesBySender(readConversationSummaries(account.accountId));
|
|
10191
|
+
} catch (err) {
|
|
10192
|
+
console.error(`[admin-sessions-list] op=store-unreadable reason=${err instanceof Error ? err.message : String(err)}`);
|
|
10193
|
+
}
|
|
10168
10194
|
for (const row of rows) {
|
|
10169
10195
|
const rowSenderId = channelSenderBySession.get(row.sessionId);
|
|
10170
10196
|
if (!rowSenderId) continue;
|
|
10171
10197
|
const r = resolvedNames.get(rowSenderId) ?? { name: null, reason: "no-binding" };
|
|
10172
10198
|
if (r.name) {
|
|
10173
10199
|
row.personName = r.name;
|
|
10200
|
+
operatorResolvedCount += 1;
|
|
10174
10201
|
channelNamesResolved += 1;
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10202
|
+
continue;
|
|
10203
|
+
}
|
|
10204
|
+
const pushName = row.channel === "whatsapp" && r.reason === "no-binding" ? pushNames.get(normalizeE164(rowSenderId)) ?? null : null;
|
|
10205
|
+
if (pushName) {
|
|
10206
|
+
row.whatsappName = pushName;
|
|
10207
|
+
storeResolvedCount += 1;
|
|
10208
|
+
channelNamesResolved += 1;
|
|
10209
|
+
continue;
|
|
10178
10210
|
}
|
|
10211
|
+
const reason = r.reason === "neo4j-unreachable" ? "neo4j-down" : "classify-miss";
|
|
10212
|
+
console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${row.sessionId.slice(0, 8)} channel=${row.channel} senderId=${rowSenderId} reason=${reason}`);
|
|
10179
10213
|
}
|
|
10180
10214
|
}
|
|
10181
10215
|
for (const m of channelMissNoSender) {
|
|
@@ -10183,7 +10217,7 @@ app4.get("/", requireAdminSession, async (c) => {
|
|
|
10183
10217
|
}
|
|
10184
10218
|
rows.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
|
|
10185
10219
|
console.log(
|
|
10186
|
-
`[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} channelInList=whatsapp:${channelInList.whatsapp},telegram:${channelInList.telegram},webchat:${channelInList.webchat} channelNameCandidates=${channelNameCandidates} channelNamesResolved=${channelNamesResolved} unknownChannel=${unknownChannelCount} excludedPublicWebchat=${excludedPublicWebchatCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} personNames=${personNameCount} adminNames=${adminNameCount} accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"}`
|
|
10220
|
+
`[admin-sessions-list] rows=${rows.length} live=${liveCount} subagents=${subagentCount} archived=${archivedCount} channelInList=whatsapp:${channelInList.whatsapp},telegram:${channelInList.telegram},webchat:${channelInList.webchat} channelNameCandidates=${channelNameCandidates} channelNamesResolved=${channelNamesResolved} (operator=${operatorResolvedCount} store=${storeResolvedCount}) unknownChannel=${unknownChannelCount} excludedPublicWebchat=${excludedPublicWebchatCount} titles user=${titleSourceCounts.user} ai=${titleSourceCounts.ai} first=${titleSourceCounts["first-message"]} prefix=${titleSourceCounts.prefix} personNames=${personNameCount} adminNames=${adminNameCount} accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"}`
|
|
10187
10221
|
);
|
|
10188
10222
|
console.log(
|
|
10189
10223
|
`[admin-sessions-list] accountId=${scopeAccountId ? scopeAccountId.slice(0, 8) : "unset"} scope=${multiAccount ? "scoped" : "all"} rows=${rows.length}`
|
|
@@ -20894,7 +20928,7 @@ function buildInitialMessage(env) {
|
|
|
20894
20928
|
"Conservative-extraction rules apply (see Task 234 spec):",
|
|
20895
20929
|
"- One :KnowledgeDocument MERGE keyed on sourceUrl=" + env.pageUrl + " with the verbatim transcript as body, plus one :Section:Note child with the summary.",
|
|
20896
20930
|
"- (:KD)-[:PARTICIPANT]->(:Person) for each participant. MERGE :Person on (name, profileUrl) where present.",
|
|
20897
|
-
"- MERGE any derived entity only when assertively stated. Whatever ontology label fits: :Event {startDate, endDate} with ATTENDED_BY edges for scheduled meetings; :Task {kind, name, status} with RAISED_BY/ABOUT edges for actions without a scheduled time; :Service/:PriceSpecification when an offer or price is discussed. Each carries sourceUrl=" + env.pageUrl + ".",
|
|
20931
|
+
"- MERGE any derived entity only when assertively stated. Whatever ontology label fits: :Event {startDate, endDate} with ATTENDED_BY edges for scheduled meetings; :Task {taskId: randomUUID(), kind, name, status} with RAISED_BY/ABOUT edges for actions without a scheduled time; :Service/:PriceSpecification when an offer or price is discussed. Each carries sourceUrl=" + env.pageUrl + ".",
|
|
20898
20932
|
"- (:KD)-[:MENTIONS]->(<derived entity>) for every derived entity. Person-attached insights stay in the :Section:Note body plus a (:KD)-[:MENTIONS]->(:Person) edge \u2014 no standalone insight node.",
|
|
20899
20933
|
"- No :Communication, :ConversationArchive rows (chat transcripts route through the conversation-archive skill), or :Message nodes \u2014 those belong to other flows.",
|
|
20900
20934
|
"",
|
|
@@ -21933,7 +21967,7 @@ app47.get("/", requireAdminSession, async (c) => {
|
|
|
21933
21967
|
WHERE te.endedAt IS NULL
|
|
21934
21968
|
WITH t, te ORDER BY te.startedAt
|
|
21935
21969
|
WITH t, head(collect(te)) AS openTe
|
|
21936
|
-
RETURN t.taskId AS taskId, t.name AS name, t.clientName AS clientName,
|
|
21970
|
+
RETURN t.taskId AS taskId, elementId(t) AS rowKey, t.name AS name, t.clientName AS clientName,
|
|
21937
21971
|
t.propertyAddress AS propertyAddress, t.fee AS fee, t.status AS status,
|
|
21938
21972
|
coalesce(t.secondsLogged, 0) AS secondsLogged,
|
|
21939
21973
|
openTe.startedAt AS runningStartedAt
|
|
@@ -21942,12 +21976,17 @@ app47.get("/", requireAdminSession, async (c) => {
|
|
|
21942
21976
|
);
|
|
21943
21977
|
const open = [];
|
|
21944
21978
|
const completed = [];
|
|
21979
|
+
let missingTaskId = 0;
|
|
21945
21980
|
for (const rec of result.records) {
|
|
21946
21981
|
const status = rec.get("status") ?? "pending";
|
|
21947
21982
|
const runningStartedAt = rec.get("runningStartedAt") ?? null;
|
|
21948
21983
|
const fee = rec.get("fee");
|
|
21984
|
+
const rawTaskId = rec.get("taskId");
|
|
21985
|
+
const taskId = typeof rawTaskId === "string" && rawTaskId !== "" ? rawTaskId : null;
|
|
21986
|
+
if (taskId === null) missingTaskId++;
|
|
21949
21987
|
const row = {
|
|
21950
|
-
taskId
|
|
21988
|
+
taskId,
|
|
21989
|
+
rowKey: rec.get("rowKey"),
|
|
21951
21990
|
name: rec.get("name") ?? "",
|
|
21952
21991
|
clientName: rec.get("clientName") ?? null,
|
|
21953
21992
|
propertyAddress: rec.get("propertyAddress") ?? null,
|
|
@@ -21960,7 +21999,7 @@ app47.get("/", requireAdminSession, async (c) => {
|
|
|
21960
21999
|
if (COMPLETED.has(status)) completed.push(row);
|
|
21961
22000
|
else if (!HIDDEN_FROM_OPEN.has(status)) open.push(row);
|
|
21962
22001
|
}
|
|
21963
|
-
console.log(`${TAG37} op=list account=${accountId} open=${open.length} completed=${completed.length}`);
|
|
22002
|
+
console.log(`${TAG37} op=list account=${accountId} open=${open.length} completed=${completed.length} missingTaskId=${missingTaskId}`);
|
|
21964
22003
|
return c.json({ open, completed });
|
|
21965
22004
|
} catch (err) {
|
|
21966
22005
|
console.error(`${TAG37} op=list error="${err instanceof Error ? err.message : String(err)}"`);
|
|
@@ -21986,7 +22025,11 @@ app48.post("/", requireAdminSession, async (c) => {
|
|
|
21986
22025
|
return c.json({ error: "invalid-json" }, 400);
|
|
21987
22026
|
}
|
|
21988
22027
|
const taskId = typeof body.taskId === "string" ? body.taskId : "";
|
|
21989
|
-
if (!taskId)
|
|
22028
|
+
if (!taskId) {
|
|
22029
|
+
const reason = body.taskId === null ? "null-taskId" : "missing-taskId";
|
|
22030
|
+
console.error(`${TAG38} op=timer-start rejected reason=${reason} account=${accountId}`);
|
|
22031
|
+
return c.json({ error: "taskId required" }, 400);
|
|
22032
|
+
}
|
|
21990
22033
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21991
22034
|
const session = getSession();
|
|
21992
22035
|
try {
|
|
@@ -22043,7 +22086,11 @@ app49.post("/", requireAdminSession, async (c) => {
|
|
|
22043
22086
|
return c.json({ error: "invalid-json" }, 400);
|
|
22044
22087
|
}
|
|
22045
22088
|
const taskId = typeof body.taskId === "string" ? body.taskId : "";
|
|
22046
|
-
if (!taskId)
|
|
22089
|
+
if (!taskId) {
|
|
22090
|
+
const reason = body.taskId === null ? "null-taskId" : "missing-taskId";
|
|
22091
|
+
console.error(`${TAG39} op=timer-stop rejected reason=${reason} account=${accountId}`);
|
|
22092
|
+
return c.json({ error: "taskId required" }, 400);
|
|
22093
|
+
}
|
|
22047
22094
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22048
22095
|
const session = getSession();
|
|
22049
22096
|
try {
|
|
@@ -22111,7 +22158,11 @@ app50.post("/", requireAdminSession, async (c) => {
|
|
|
22111
22158
|
return c.json({ error: "invalid-json" }, 400);
|
|
22112
22159
|
}
|
|
22113
22160
|
const taskId = typeof body.taskId === "string" ? body.taskId : "";
|
|
22114
|
-
if (!taskId)
|
|
22161
|
+
if (!taskId) {
|
|
22162
|
+
const reason = body.taskId === null ? "null-taskId" : "missing-taskId";
|
|
22163
|
+
console.error(`${TAG40} op=complete rejected reason=${reason} account=${accountId}`);
|
|
22164
|
+
return c.json({ error: "taskId required" }, 400);
|
|
22165
|
+
}
|
|
22115
22166
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22116
22167
|
const session = getSession();
|
|
22117
22168
|
try {
|
|
@@ -22154,7 +22205,8 @@ app51.post("/", requireAdminSession, async (c) => {
|
|
|
22154
22205
|
}
|
|
22155
22206
|
const taskId = typeof body.taskId === "string" ? body.taskId : "";
|
|
22156
22207
|
if (!taskId) {
|
|
22157
|
-
|
|
22208
|
+
const reason = body.taskId === null ? "null-taskId" : "missing-taskId";
|
|
22209
|
+
console.error(`${TAG41} op=time-adjust rejected reason=${reason} account=${accountId}`);
|
|
22158
22210
|
return c.json({ error: "taskId required" }, 400);
|
|
22159
22211
|
}
|
|
22160
22212
|
const newSeconds = body.newSeconds;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{S as e,T as t}from"./useSubAccountSwitcher-CUJpgkKI.js";import"./useVoiceRecorder-zUvPe55I.js";import"./AdminShell-DAuri4Yh.js";import{n}from"./page-Ceado9Kr.js";import"./useCopyFeedback-DXHkkJw-.js";import"./useSelectionMode-D2H8jAhE.js";var r=t(),i=e();(0,r.createRoot)(document.getElementById(`root`)).render((0,i.jsx)(n,{}));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{_ as e}from"./useSubAccountSwitcher-CUJpgkKI.js";var t=e(`chevron-left`,[[`path`,{d:`m15 18-6-6 6-6`,key:`1wnfg3`}]]);export{t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{S as e,T as t}from"./useSubAccountSwitcher-CUJpgkKI.js";import"./useVoiceRecorder-zUvPe55I.js";import{t as n}from"./page-tJxw0zNO.js";import"./useSelectionMode-D2H8jAhE.js";import"./graph-labels-4W7JuOgG.js";var r=t(),i=e();(0,r.createRoot)(document.getElementById(`root`)).render((0,i.jsx)(n,{}));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-TPjIAO9U.js";import{D as t,S as n,T as r,_ as i,a,d as o,h as s,l as c,n as l,r as u,s as d,t as f,w as p}from"./useSubAccountSwitcher-CUJpgkKI.js";import{n as m,t as h}from"./time-entry-format-Bn79w6tq.js";import{t as g}from"./rotate-ccw-DrzdV9h7.js";var _=i(`circle-check`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m9 12 2 2 4-4`,key:`dzmm74`}]]),v=i(`circle`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),y=r(),b=e(t(),1);function x(e=3e4){let[t,n]=(0,b.useState)(()=>Date.now());return(0,b.useEffect)(()=>{let t=setInterval(()=>n(Date.now()),e);return()=>clearInterval(t)},[e]),t}function S(e,t){let n=new Map,r=(e,t,r)=>{let i=n.get(e)??{openCount:0,totalSeconds:0};i.totalSeconds+=t,r&&(i.openCount+=1),n.set(e,i)};for(let t of e)t.clientName&&r(t.clientName,t.secondsLogged,!0);for(let e of t)e.clientName&&r(e.clientName,e.secondsLogged,!1);let i=[...n.entries()].map(([e,t])=>({name:e,openCount:t.openCount,totalSeconds:t.totalSeconds}));return i.sort((e,t)=>t.openCount-e.openCount||e.name.localeCompare(t.name)),C(e,i),i}function C(e,t){let n=t.reduce((e,t)=>e+t.openCount,0),r=e.filter(e=>!e.clientName).length,i=n+r===e.length;return i||console.warn(`[tasks-clients] reconcile-mismatch assigned=${n} unassigned=${r} openTotal=${e.length}`),i}var w=n();function T(e){let t=[];return e.clientName&&t.push(e.clientName),e.propertyAddress&&t.push(e.propertyAddress),e.fee!=null&&t.push(`£${e.fee}`),t.join(` · `)}function E(e){return[e.clientName,e.propertyAddress].filter(Boolean).join(` · `)}function D(e,t){if(e.running&&e.runningStartedAt){let n=Date.parse(e.runningStartedAt);if(Number.isFinite(n))return e.secondsLogged+Math.max(0,(t-n)/1e3)}return e.secondsLogged}function O({clients:e,onSelect:t}){return(0,w.jsxs)(`section`,{className:`tasks-card`,children:[(0,w.jsxs)(`div`,{className:`tasks-card-head`,children:[(0,w.jsx)(`span`,{className:`tasks-card-title`,children:`Clients`}),(0,w.jsxs)(`span`,{className:`tasks-card-count`,children:[e.length,` total`]})]}),e.length===0&&(0,w.jsx)(`div`,{className:`tasks-empty`,children:`No clients yet.`}),e.map(e=>(0,w.jsx)(`button`,{className:`tasks-row tasks-client-row`,onClick:()=>t(e.name),children:(0,w.jsxs)(`div`,{className:`tasks-row-main`,children:[(0,w.jsx)(`div`,{className:`tasks-row-name`,children:e.name}),(0,w.jsxs)(`div`,{className:`tasks-row-sub`,children:[e.openCount,` open · `,h(e.totalSeconds),` logged`]})]})},e.name))]})}function k({adminFetch:e}){let[t,n]=(0,b.useState)({open:[],completed:[]}),[r,i]=(0,b.useState)(!1),[l,u]=(0,b.useState)(!1),f=(0,b.useRef)(!1),p=(0,b.useRef)(``),y=x(1e3),[C,k]=(0,b.useState)(`open`),[A,j]=(0,b.useState)(null),[M,N]=(0,b.useState)(null),[P,F]=(0,b.useState)(``),I=(0,b.useMemo)(()=>S(t.open,t.completed),[t]),L=(0,b.useCallback)(e=>{j(e),k(`open`)},[]);(0,b.useEffect)(()=>{A&&!I.some(e=>e.name===A)&&j(null)},[I,A]);let R=A?t.open.filter(e=>e.clientName===A||e.running):t.open,z=A?t.completed.filter(e=>e.clientName===A):t.completed,B=(0,b.useCallback)(async()=>{let t=await e(`/api/admin/tasks-list`);t.ok&&n(await t.json()),i(!0)},[e]);(0,b.useEffect)(()=>{B()},[B]);let V=(0,b.useCallback)(async(t,n,r)=>{if(!f.current){f.current=!0,u(!0);try{await e(t,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({taskId:n,...r})}),await B()}finally{f.current=!1,u(!1)}}},[e,B]),H=(0,b.useCallback)(e=>{let t=String(Math.round(e.secondsLogged/60));p.current=t,N(e.taskId),F(t)},[]),U=(0,b.useCallback)(()=>{N(null),F(``)},[]),W=(0,b.useCallback)(async e=>{let t=P.trim(),n=Number(t);if(!(t===``||!Number.isInteger(n)||n<0)){if(t===p.current){U();return}N(null),F(``),await V(`/api/admin/task-time-adjust`,e,{newSeconds:n*60})}},[P,V,U]),G=(0,b.useCallback)(e=>{V(`/api/admin/task-time-adjust`,e,{newSeconds:0})},[V]);if(!r)return(0,w.jsxs)(`div`,{className:`tasks-loading`,children:[(0,w.jsx)(o,{size:18,className:`spin`}),` Loading…`]});let K=e=>M===e.taskId?(0,w.jsxs)(`div`,{className:`tasks-edit`,children:[(0,w.jsx)(`input`,{className:`tasks-edit-input`,type:`number`,min:0,step:1,"aria-label":`Logged minutes`,value:P,onChange:e=>F(e.target.value)}),(0,w.jsx)(`span`,{className:`tasks-edit-unit`,children:`min`}),(0,w.jsxs)(`button`,{className:`tasks-btn tasks-btn-start`,disabled:l,onClick:()=>void W(e.taskId),children:[(0,w.jsx)(s,{size:14}),` Save`]}),(0,w.jsxs)(`button`,{className:`tasks-btn tasks-btn-stop`,onClick:U,children:[(0,w.jsx)(a,{size:14}),` Cancel`]})]}):(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(`button`,{className:`tasks-time-btn`,"aria-label":`Edit time`,disabled:l||e.running,title:e.running?`Stop the timer before editing time`:void 0,onClick:()=>H(e),children:(0,w.jsx)(c,{size:14})}),(0,w.jsx)(`button`,{className:`tasks-time-btn`,"aria-label":`Reset time`,disabled:l||e.running,title:e.running?`Stop the timer before resetting time`:void 0,onClick:()=>G(e.taskId),children:(0,w.jsx)(g,{size:14})})]});return(0,w.jsxs)(`div`,{className:`tasks-body`,children:[(0,w.jsxs)(`div`,{className:`tasks-tabs`,children:[(0,w.jsxs)(`button`,{className:`tasks-tab ${C===`open`?`tasks-tab-active`:``}`,onClick:()=>k(`open`),children:[`Open `,(0,w.jsx)(`span`,{className:`tasks-tab-badge`,children:t.open.length})]}),(0,w.jsxs)(`button`,{className:`tasks-tab ${C===`completed`?`tasks-tab-active`:``}`,onClick:()=>k(`completed`),children:[`Completed `,(0,w.jsx)(`span`,{className:`tasks-tab-badge`,children:t.completed.length})]}),(0,w.jsxs)(`button`,{className:`tasks-tab ${C===`clients`?`tasks-tab-active`:``}`,onClick:()=>k(`clients`),children:[`Clients `,(0,w.jsx)(`span`,{className:`tasks-tab-badge`,children:I.length})]})]}),C===`clients`?(0,w.jsx)(O,{clients:I,onSelect:L}):(0,w.jsxs)(w.Fragment,{children:[A&&(0,w.jsxs)(`div`,{className:`tasks-filter-banner`,children:[(0,w.jsxs)(`span`,{children:[`Showing `,A]}),(0,w.jsx)(`button`,{className:`tasks-filter-clear`,onClick:()=>j(null),children:`Clear`})]}),C===`open`?(0,w.jsxs)(`section`,{className:`tasks-card`,children:[(0,w.jsxs)(`div`,{className:`tasks-card-head`,children:[(0,w.jsx)(`span`,{className:`tasks-card-title`,children:`Open tasks`}),(0,w.jsxs)(`span`,{className:`tasks-card-count`,children:[R.length,` to do`]})]}),R.length===0&&(0,w.jsx)(`div`,{className:`tasks-empty`,children:`No open tasks.`}),R.map(e=>(0,w.jsxs)(`div`,{className:`tasks-row`,children:[(0,w.jsx)(`button`,{className:`tasks-check`,"aria-label":`Mark complete`,disabled:l||e.running,title:e.running?`Stop the timer before completing`:void 0,onClick:()=>void V(`/api/admin/task-complete`,e.taskId),children:(0,w.jsx)(v,{size:18})}),(0,w.jsxs)(`div`,{className:`tasks-row-main`,children:[(0,w.jsx)(`div`,{className:`tasks-row-name`,children:e.name}),T(e)&&(0,w.jsx)(`div`,{className:`tasks-row-sub`,children:T(e)})]}),(0,w.jsx)(`div`,{className:`tasks-row-elapsed`,children:h(D(e,y))}),K(e),M===e.taskId?null:e.running?(0,w.jsxs)(`button`,{className:`tasks-btn tasks-btn-stop`,disabled:l,onClick:()=>void V(`/api/admin/task-timer-stop`,e.taskId),children:[(0,w.jsx)(d,{size:14}),` Stop`]}):(0,w.jsxs)(`button`,{className:`tasks-btn tasks-btn-start`,disabled:l,onClick:()=>void V(`/api/admin/task-timer-start`,e.taskId),children:[(0,w.jsx)(m,{size:14}),` Start`]})]},e.taskId))]}):(0,w.jsxs)(`section`,{className:`tasks-card`,children:[(0,w.jsxs)(`div`,{className:`tasks-card-head`,children:[(0,w.jsx)(`span`,{className:`tasks-card-title`,children:`Completed`}),(0,w.jsxs)(`span`,{className:`tasks-card-count`,children:[z.length,` done`]})]}),z.length===0&&(0,w.jsx)(`div`,{className:`tasks-empty`,children:`Nothing completed yet.`}),z.map(e=>(0,w.jsxs)(`div`,{className:`tasks-row tasks-row-done`,children:[(0,w.jsx)(`span`,{className:`tasks-check tasks-check-done`,children:(0,w.jsx)(_,{size:18})}),(0,w.jsxs)(`div`,{className:`tasks-row-main`,children:[(0,w.jsx)(`div`,{className:`tasks-row-name`,children:e.name}),E(e)&&(0,w.jsx)(`div`,{className:`tasks-row-sub`,children:E(e)})]}),(0,w.jsxs)(`div`,{className:`tasks-row-elapsed`,children:[h(e.secondsLogged),e.fee==null?``:` · £${e.fee}`]}),K(e)]},e.taskId))]})]})]})}function A({cacheKey:e}){let{adminFetch:t,cacheKey:n}=p({initialCacheKey:e,surface:`tasks`}),{subAccounts:r,activeAccountId:i,switching:a,switchAccount:o}=f(n),s=(0,b.useCallback)(()=>{try{sessionStorage.removeItem(`maxy-admin-session-key`)}catch{}window.location.href=`/`},[]);return(0,w.jsxs)(`main`,{className:`tasks-main`,children:[(0,w.jsx)(l,{variant:`operator`,onNavigate:(0,b.useCallback)(e=>{e===`chat`&&(window.location.href=`/`)},[]),onToggleSidebar:()=>{},sidebarOpen:!1,onLogout:s,onDisconnect:async()=>!0,disconnecting:!1,cacheKey:n,subAccounts:r,activeAccountId:i,switchingAccount:a,onSwitchAccount:o}),(0,w.jsx)(k,{adminFetch:t})]})}function j(){let[e,t]=(0,b.useState)(null),[n,r]=(0,b.useState)(!1);return(0,b.useEffect)(()=>{let e=!1,n=null;try{n=sessionStorage.getItem(`maxy-admin-session-key`)}catch{}if(!n){t(null),r(!0);return}return fetch(`/api/admin/session?session_key=${encodeURIComponent(n)}`).then(i=>{if(!e){if(i.status===401){try{sessionStorage.removeItem(`maxy-admin-session-key`)}catch{}window.location.href=`/`;return}t(n),r(!0)}}).catch(()=>{e||(t(n),r(!0))}),()=>{e=!0}},[]),n?e?(0,w.jsx)(u,{cacheKey:e,surface:`tasks`,onSessionExpired:({code:e,path:t})=>{console.warn(`[admin-auth] outcome=session-expired-redirect code=${e} path=${t} surface=tasks`);try{sessionStorage.removeItem(`maxy-admin-session-key`)}catch{}window.location.href=`/`},children:(0,w.jsx)(`div`,{className:`tasks-page tasks-page-full`,children:(0,w.jsx)(A,{cacheKey:e})})}):(0,w.jsx)(`div`,{className:`tasks-page`,children:(0,w.jsxs)(`div`,{className:`tasks-empty`,children:[(0,w.jsx)(`p`,{children:`You are not signed in.`}),(0,w.jsxs)(`p`,{children:[`Open the `,(0,w.jsx)(`a`,{href:`/`,className:`tasks-link`,children:`main admin page`}),` and log in, then return here.`]})]})}):(0,w.jsx)(`div`,{className:`tasks-page`,children:(0,w.jsxs)(`div`,{className:`tasks-loading`,children:[(0,w.jsx)(o,{size:18,className:`spin`}),` Loading…`]})})}(0,y.createRoot)(document.getElementById(`root`)).render((0,w.jsx)(j,{}));
|
|
File without changes
|