@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.
Files changed (64) hide show
  1. package/dist/__tests__/google-client-id-env.test.js +3 -2
  2. package/package.json +1 -1
  3. package/payload/platform/neo4j/schema.cypher +4 -1
  4. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +14 -10
  5. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  6. package/payload/platform/plugins/docs/references/google-guide.md +10 -6
  7. package/payload/platform/plugins/docs/references/voice-mirror-guide.md +2 -2
  8. package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
  9. package/payload/platform/plugins/google/PLUGIN.md +25 -14
  10. package/payload/platform/plugins/google/references/auth.md +250 -365
  11. package/payload/platform/plugins/voice-mirror/PLUGIN.md +1 -1
  12. package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +3 -3
  13. package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts +14 -1
  15. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts.map +1 -1
  16. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js +15 -0
  17. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js.map +1 -1
  18. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
  19. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +3 -1
  20. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
  21. package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +51 -4
  22. package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +4 -0
  23. package/payload/platform/scripts/backfill-task-ids.sh +206 -0
  24. package/payload/server/public/activity.html +6 -6
  25. package/payload/server/public/assets/{AdminLoginScreens-B7TQZV8u.js → AdminLoginScreens-B44KbEuC.js} +1 -1
  26. package/payload/server/public/assets/{AdminShell-DAuri4Yh.js → AdminShell-BjxV2qkb.js} +2 -2
  27. package/payload/server/public/assets/{Checkbox-B1XhnhPk.js → Checkbox-DSeZNGNA.js} +1 -1
  28. package/payload/server/public/assets/{activity-kRwzxEKU.js → activity-CdipMTDp.js} +1 -1
  29. package/payload/server/public/assets/{admin-BdcccSb5.js → admin-B4LpTCur.js} +1 -1
  30. package/payload/server/public/assets/{browser-B6woQ2qX.js → browser-CEFvaWwI.js} +1 -1
  31. package/payload/server/public/assets/{calendar-CalCeg5T.js → calendar-B3zo6V34.js} +1 -1
  32. package/payload/server/public/assets/chat-5tJdo9i_.js +1 -0
  33. package/payload/server/public/assets/chevron-left-BJllrPBj.js +1 -0
  34. package/payload/server/public/assets/data-Du79EgR-.js +1 -0
  35. package/payload/server/public/assets/{graph-HeZaatSQ.js → graph-RaOrnSf2.js} +1 -1
  36. package/payload/server/public/assets/{graph-labels-4W7JuOgG.js → graph-labels-GlTEIYJj.js} +1 -1
  37. package/payload/server/public/assets/{maximize-2-DKx-aTPb.js → maximize-2-CHDa8p5h.js} +1 -1
  38. package/payload/server/public/assets/{operator-DD9L2VJM.js → operator-DtqHY4jV.js} +1 -1
  39. package/payload/server/public/assets/{page-tJxw0zNO.js → page-CkhFj8GQ.js} +1 -1
  40. package/payload/server/public/assets/{page-Ceado9Kr.js → page-DxnWgPtd.js} +1 -1
  41. package/payload/server/public/assets/{public-BYFWxyeC.js → public-D5TUfQ3I.js} +1 -1
  42. package/payload/server/public/assets/{rotate-ccw-DrzdV9h7.js → rotate-ccw-BJjVAQQY.js} +1 -1
  43. package/payload/server/public/assets/tasks-CM9pYYG1.js +1 -0
  44. package/payload/server/public/assets/{time-entry-format-Bn79w6tq.js → time-entry-format-C5RYEYjM.js} +1 -1
  45. package/payload/server/public/assets/{triangle-alert-CjDVh6r-.js → triangle-alert-DLfIsuo_.js} +1 -1
  46. package/payload/server/public/assets/{useCopyFeedback-DXHkkJw-.js → useCopyFeedback-7yKeiAzV.js} +1 -1
  47. package/payload/server/public/assets/{useSelectionMode-D2H8jAhE.js → useSelectionMode-CWlh6AEq.js} +1 -1
  48. package/payload/server/public/assets/{useSubAccountSwitcher-Br7Z6Cpu.css → useSubAccountSwitcher-B2n9QPVI.css} +1 -1
  49. package/payload/server/public/assets/{useVoiceRecorder-zUvPe55I.js → useVoiceRecorder-j3ZArZ2H.js} +1 -1
  50. package/payload/server/public/browser.html +5 -5
  51. package/payload/server/public/calendar.html +6 -6
  52. package/payload/server/public/chat.html +12 -12
  53. package/payload/server/public/data.html +11 -11
  54. package/payload/server/public/graph.html +11 -11
  55. package/payload/server/public/index.html +14 -14
  56. package/payload/server/public/operator.html +14 -14
  57. package/payload/server/public/public.html +12 -12
  58. package/payload/server/public/tasks.html +5 -5
  59. package/payload/server/server.js +65 -13
  60. package/payload/server/public/assets/chat-9s2-L06v.js +0 -1
  61. package/payload/server/public/assets/chevron-left-C3ho8SnB.js +0 -1
  62. package/payload/server/public/assets/data-Cfr-7ERI.js +0 -1
  63. package/payload/server/public/assets/tasks-DSwoxGzh.js +0 -1
  64. /package/payload/server/public/assets/{useSubAccountSwitcher-CUJpgkKI.js → useSubAccountSwitcher-CBlKgwms.js} +0 -0
@@ -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
- } else {
10176
- const reason = r.reason === "neo4j-unreachable" ? "neo4j-down" : "classify-miss";
10177
- console.error(`[admin-sessions-list] op=channel-name-unresolved sessionId=${row.sessionId.slice(0, 8)} channel=${row.channel} senderId=${rowSenderId} reason=${reason}`);
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: rec.get("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) return c.json({ error: "taskId required" }, 400);
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) return c.json({ error: "taskId required" }, 400);
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) return c.json({ error: "taskId required" }, 400);
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
- console.error(`${TAG41} op=reject reason=no-task`);
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,{}));