@bonginkan/maria-lite 6.2.4 → 6.3.1

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.
@@ -359,7 +359,8 @@ var init_helpers = __esm({
359
359
  "google-gmail": "mail",
360
360
  "google-calendar": "calendar",
361
361
  "google-drive": "hard-drive",
362
- "google-meet": "video"
362
+ "google-meet": "video",
363
+ "resource-manager": "building"
363
364
  };
364
365
  LUCIDE_APP_ICONS = {
365
366
  // Basic Tools
@@ -380,6 +381,7 @@ var init_helpers = __esm({
380
381
  "daily-insights": "lightbulb",
381
382
  "discord-setup": "bell",
382
383
  "billing-pl": "wallet",
384
+ "dev-adviser": "user-check",
383
385
  // CXO Agents
384
386
  ceo: "crown",
385
387
  coo: "target",
@@ -456,6 +458,7 @@ var init_helpers = __esm({
456
458
  gcal: "calendar",
457
459
  gdrive: "hard-drive",
458
460
  gmeet: "video",
461
+ "resource-manager": "building",
459
462
  // System
460
463
  help: "life-buoy",
461
464
  version: "info",
@@ -472,7 +475,8 @@ var init_helpers = __esm({
472
475
  __ui_design: "paintbrush",
473
476
  __file_manager: "folder-open",
474
477
  __task_manager: "activity",
475
- __log_viewer: "scroll-text"
478
+ __log_viewer: "scroll-text",
479
+ __resource_manager: "building"
476
480
  };
477
481
  LUCIDE_FILE_EXT_ICONS = {
478
482
  ts: "file-code",
@@ -644,7 +648,7 @@ var init_state = __esm({
644
648
  label: "Development",
645
649
  emoji: "\u{1F6E0}\uFE0F",
646
650
  serverCategories: ["development"],
647
- commandIds: ["code", "develop", "auto-dev", "git", "gh", "doctor", "assert", "rollback", "deps"]
651
+ commandIds: ["code", "develop", "auto-dev", "git", "gh", "doctor", "assert", "rollback", "deps", "dev-adviser"]
648
652
  },
649
653
  {
650
654
  id: "integrations",
@@ -700,7 +704,7 @@ var init_state = __esm({
700
704
  label: "Google",
701
705
  emoji: "\u{1F310}",
702
706
  serverCategories: ["google"],
703
- commandIds: ["gmail", "gcal", "gdrive", "gmeet"],
707
+ commandIds: ["gmail", "gcal", "gdrive", "gmeet", "resource-manager"],
704
708
  alwaysVisible: true
705
709
  },
706
710
  {
@@ -8071,6 +8075,10 @@ function openGoogleCalendar() {
8071
8075
  calCalendars = [];
8072
8076
  calEnabledCalendarIds = /* @__PURE__ */ new Set();
8073
8077
  calShowCalendarPanel = false;
8078
+ calFindBuildings = [];
8079
+ calFindResources = [];
8080
+ calFindBuildingId = "";
8081
+ calFindResourceId = "";
8074
8082
  renderApp2();
8075
8083
  }
8076
8084
  function renderApp2() {
@@ -8235,6 +8243,7 @@ async function renderList(container) {
8235
8243
  return {
8236
8244
  events: (data.events || []).map((ev) => ({
8237
8245
  ...ev,
8246
+ isAllDay: ev.isAllDay ?? Boolean(ev.start && !ev.start.includes("T")),
8238
8247
  calendarId: calId,
8239
8248
  calendarColor: calInfo?.backgroundColor || "#4285f4",
8240
8249
  calendarName: calInfo?.summary || (calId === "primary" ? "My Calendar" : calId)
@@ -8318,8 +8327,9 @@ async function renderDetail(container) {
8318
8327
  const ev = data.event;
8319
8328
  calCurrentEvent = ev;
8320
8329
  const useLucide = state.iconStyle === "modern" || state.iconStyle === "minimal";
8330
+ const isAllDay = ev.isAllDay || Boolean(ev.start && !ev.start.includes("T"));
8321
8331
  let html = `<div class="gc-detail">`;
8322
- html += `<div class="gc-detail-title">${esc(ev.summary)}</div>`;
8332
+ html += `<div class="gc-detail-title">${esc(ev.summary)}${isAllDay ? `<span style="display:inline-block;font-size:.6em;padding:2px 8px;border-radius:var(--radius-sm);background:rgba(66,133,244,.12);color:var(--accent);margin-left:8px;vertical-align:middle;font-weight:600;">All day</span>` : ""}</div>`;
8323
8333
  html += detailRow("When", `${formatDateTime(ev.start)} \u2192 ${formatDateTime(ev.end)}`);
8324
8334
  if (ev.location) html += detailRow("Location", ev.location);
8325
8335
  if (ev.description) html += detailRow("Description", ev.description);
@@ -8404,8 +8414,9 @@ function renderCreate(container) {
8404
8414
  }
8405
8415
  let html = `<div class="gc-create">`;
8406
8416
  html += `<label>Title<input class="gc-f-title" type="text" placeholder="Meeting title" /></label>`;
8407
- html += `<label>Start<input class="gc-f-start" type="datetime-local" value="${prefill ? toLocal(prefill.start) : defaultStart}" /></label>`;
8408
- html += `<label>End<input class="gc-f-end" type="datetime-local" value="${prefill ? toLocal(prefill.end) : defaultEnd}" /></label>`;
8417
+ html += `<label style="display:flex;align-items:center;gap:6px;margin-bottom:8px;cursor:pointer;flex-direction:row;"><input class="gc-f-allday" type="checkbox" id="create-allday" /><span style="font-size:.82em;font-weight:600;">All day</span></label>`;
8418
+ html += `<label>Start<input class="gc-f-start" id="create-start" type="datetime-local" value="${prefill ? toLocal(prefill.start) : defaultStart}" /></label>`;
8419
+ html += `<label>End<input class="gc-f-end" id="create-end" type="datetime-local" value="${prefill ? toLocal(prefill.end) : defaultEnd}" /></label>`;
8409
8420
  html += `<label>Location (optional)<input class="gc-f-location" type="text" placeholder="Room or address" /></label>`;
8410
8421
  html += `<label>Description (optional)<textarea class="gc-f-desc" placeholder="Notes..."></textarea></label>`;
8411
8422
  html += `<label>Attendees${prefill ? "" : " (optional)"}<input class="gc-f-attendees" type="text" value="${prefill ? esc(prefill.attendees) : ""}" placeholder="email1@example.com, email2@example.com" /></label>`;
@@ -8414,6 +8425,29 @@ function renderCreate(container) {
8414
8425
  html += `<div class="gc-create-status"></div>`;
8415
8426
  html += `</div>`;
8416
8427
  container.innerHTML = html;
8428
+ const allDayCheck = container.querySelector("#create-allday");
8429
+ if (allDayCheck) {
8430
+ allDayCheck.addEventListener("change", () => {
8431
+ const startInput = container.querySelector("#create-start");
8432
+ const endInput = container.querySelector("#create-end");
8433
+ if (!startInput || !endInput) return;
8434
+ if (allDayCheck.checked) {
8435
+ const startDate = startInput.value.split("T")[0] || "";
8436
+ const endDate = endInput.value.split("T")[0] || "";
8437
+ startInput.type = "date";
8438
+ endInput.type = "date";
8439
+ startInput.value = startDate;
8440
+ endInput.value = endDate;
8441
+ } else {
8442
+ const startDate = startInput.value || "";
8443
+ const endDate = endInput.value || "";
8444
+ startInput.type = "datetime-local";
8445
+ endInput.type = "datetime-local";
8446
+ startInput.value = startDate ? `${startDate}T09:00` : "";
8447
+ endInput.value = endDate ? `${endDate}T10:00` : "";
8448
+ }
8449
+ });
8450
+ }
8417
8451
  container.querySelector(".gc-create-btn").addEventListener("click", () => void handleCreate2(container));
8418
8452
  }
8419
8453
  async function handleCreate2(container) {
@@ -8424,6 +8458,8 @@ async function handleCreate2(container) {
8424
8458
  const desc = container.querySelector(".gc-f-desc").value.trim();
8425
8459
  const attendees = container.querySelector(".gc-f-attendees").value.trim();
8426
8460
  const addMeet = container.querySelector(".gc-f-meet").checked;
8461
+ const allDayCheck = container.querySelector(".gc-f-allday");
8462
+ const isAllDay = allDayCheck?.checked || false;
8427
8463
  const statusEl = container.querySelector(".gc-create-status");
8428
8464
  const btn = container.querySelector(".gc-create-btn");
8429
8465
  if (!title) {
@@ -8438,8 +8474,15 @@ async function handleCreate2(container) {
8438
8474
  statusEl.innerHTML = `<span class="gc-status-err">End time is required</span>`;
8439
8475
  return;
8440
8476
  }
8441
- const start = new Date(startRaw).toISOString();
8442
- const end = new Date(endRaw).toISOString();
8477
+ let start;
8478
+ let end;
8479
+ if (isAllDay) {
8480
+ start = startRaw;
8481
+ end = endRaw;
8482
+ } else {
8483
+ start = new Date(startRaw).toISOString();
8484
+ end = new Date(endRaw).toISOString();
8485
+ }
8443
8486
  btn.disabled = true;
8444
8487
  btn.textContent = "Creating...";
8445
8488
  statusEl.textContent = "";
@@ -8451,6 +8494,7 @@ async function handleCreate2(container) {
8451
8494
  title,
8452
8495
  start,
8453
8496
  end,
8497
+ isAllDay,
8454
8498
  description: desc || void 0,
8455
8499
  attendees: attendees || void 0,
8456
8500
  location: location || void 0,
@@ -8482,20 +8526,27 @@ function renderEdit(container) {
8482
8526
  renderApp2();
8483
8527
  return;
8484
8528
  }
8529
+ const eventIsAllDay = ev.isAllDay || Boolean(ev.start && !ev.start.includes("T"));
8485
8530
  const toLocal = (iso) => {
8486
- if (!iso || !iso.includes("T")) return "";
8531
+ if (!iso) return { value: "", isAllDay: false };
8532
+ if (!iso.includes("T")) return { value: iso, isAllDay: true };
8487
8533
  try {
8488
8534
  const d = new Date(iso);
8489
8535
  const pad = (n) => String(n).padStart(2, "0");
8490
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
8536
+ const formatted = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
8537
+ return { value: formatted, isAllDay: false };
8491
8538
  } catch {
8492
- return "";
8539
+ return { value: "", isAllDay: false };
8493
8540
  }
8494
8541
  };
8542
+ const startParsed = toLocal(ev.start);
8543
+ const endParsed = toLocal(ev.end);
8544
+ const inputType = eventIsAllDay ? "date" : "datetime-local";
8495
8545
  let html = `<div class="gc-create">`;
8496
8546
  html += `<label>Title<input class="gc-f-title" type="text" value="${esc(ev.summary)}" /></label>`;
8497
- html += `<label>Start<input class="gc-f-start" type="datetime-local" value="${toLocal(ev.start)}" /></label>`;
8498
- html += `<label>End<input class="gc-f-end" type="datetime-local" value="${toLocal(ev.end)}" /></label>`;
8547
+ html += `<label style="display:flex;align-items:center;gap:6px;margin-bottom:8px;cursor:pointer;flex-direction:row;"><input class="gc-f-allday" type="checkbox" id="edit-allday"${eventIsAllDay ? " checked" : ""} /><span style="font-size:.82em;font-weight:600;">All day</span></label>`;
8548
+ html += `<label>Start<input class="gc-f-start" id="edit-start" type="${inputType}" value="${esc(startParsed.value)}" /></label>`;
8549
+ html += `<label>End<input class="gc-f-end" id="edit-end" type="${inputType}" value="${esc(endParsed.value)}" /></label>`;
8499
8550
  html += `<label>Location<input class="gc-f-location" type="text" value="${esc(ev.location)}" placeholder="Room or address" /></label>`;
8500
8551
  html += `<label>Description<textarea class="gc-f-desc">${esc(ev.description)}</textarea></label>`;
8501
8552
  html += `<label>Attendees<input class="gc-f-attendees" type="text" value="${esc(ev.attendees.join(", "))}" placeholder="email1@example.com, email2@example.com" /></label>`;
@@ -8503,6 +8554,29 @@ function renderEdit(container) {
8503
8554
  html += `<div class="gc-create-status"></div>`;
8504
8555
  html += `</div>`;
8505
8556
  container.innerHTML = html;
8557
+ const allDayCheck = container.querySelector("#edit-allday");
8558
+ if (allDayCheck) {
8559
+ allDayCheck.addEventListener("change", () => {
8560
+ const startInput = container.querySelector("#edit-start");
8561
+ const endInput = container.querySelector("#edit-end");
8562
+ if (!startInput || !endInput) return;
8563
+ if (allDayCheck.checked) {
8564
+ const startDate = startInput.value.split("T")[0] || "";
8565
+ const endDate = endInput.value.split("T")[0] || "";
8566
+ startInput.type = "date";
8567
+ endInput.type = "date";
8568
+ startInput.value = startDate;
8569
+ endInput.value = endDate;
8570
+ } else {
8571
+ const startDate = startInput.value || "";
8572
+ const endDate = endInput.value || "";
8573
+ startInput.type = "datetime-local";
8574
+ endInput.type = "datetime-local";
8575
+ startInput.value = startDate ? `${startDate}T09:00` : "";
8576
+ endInput.value = endDate ? `${endDate}T10:00` : "";
8577
+ }
8578
+ });
8579
+ }
8506
8580
  container.querySelector(".gc-create-btn").addEventListener("click", () => void handleEdit(container, ev));
8507
8581
  }
8508
8582
  async function handleEdit(container, ev) {
@@ -8512,6 +8586,8 @@ async function handleEdit(container, ev) {
8512
8586
  const location = container.querySelector(".gc-f-location").value.trim();
8513
8587
  const desc = container.querySelector(".gc-f-desc").value.trim();
8514
8588
  const attendees = container.querySelector(".gc-f-attendees").value.trim();
8589
+ const allDayCheck = container.querySelector(".gc-f-allday");
8590
+ const isAllDay = allDayCheck?.checked || false;
8515
8591
  const statusEl = container.querySelector(".gc-create-status");
8516
8592
  const btn = container.querySelector(".gc-create-btn");
8517
8593
  if (!title) {
@@ -8526,8 +8602,15 @@ async function handleEdit(container, ev) {
8526
8602
  statusEl.innerHTML = `<span class="gc-status-err">End time is required</span>`;
8527
8603
  return;
8528
8604
  }
8529
- const start = new Date(startRaw).toISOString();
8530
- const end = new Date(endRaw).toISOString();
8605
+ let start;
8606
+ let end;
8607
+ if (isAllDay) {
8608
+ start = startRaw;
8609
+ end = endRaw;
8610
+ } else {
8611
+ start = new Date(startRaw).toISOString();
8612
+ end = new Date(endRaw).toISOString();
8613
+ }
8531
8614
  btn.disabled = true;
8532
8615
  btn.textContent = "Saving...";
8533
8616
  statusEl.textContent = "";
@@ -8535,7 +8618,7 @@ async function handleEdit(container, ev) {
8535
8618
  const res = await apiJson(`/api/google/calendar/events/${encodeURIComponent(ev.id)}`, {
8536
8619
  method: "PUT",
8537
8620
  headers: { "Content-Type": "application/json" },
8538
- body: JSON.stringify({ title, start, end, description: desc, attendees, location, calendarId: calCurrentCalendarId || "primary" })
8621
+ body: JSON.stringify({ title, start, end, isAllDay, description: desc, attendees, location, calendarId: calCurrentCalendarId || "primary" })
8539
8622
  });
8540
8623
  if (res.ok || res.updated) {
8541
8624
  statusEl.innerHTML = `<span class="gc-status-ok">Event updated!</span>`;
@@ -8555,6 +8638,18 @@ async function handleEdit(container, ev) {
8555
8638
  btn.textContent = "Save Changes";
8556
8639
  }
8557
8640
  }
8641
+ function updateResourceDropdown(container) {
8642
+ const rSelect = container.querySelector(".gc-ft-resource");
8643
+ if (!rSelect) return;
8644
+ const filtered = calFindBuildingId ? calFindResources.filter((r) => r.buildingId === calFindBuildingId) : calFindResources;
8645
+ rSelect.innerHTML = '<option value="">All</option>';
8646
+ for (const r of filtered) {
8647
+ const opt = document.createElement("option");
8648
+ opt.value = r.resourceId;
8649
+ opt.textContent = `${r.resourceName}${r.capacity ? ` (${r.capacity})` : ""}`;
8650
+ rSelect.appendChild(opt);
8651
+ }
8652
+ }
8558
8653
  function renderFindTime(container) {
8559
8654
  const tomorrow = /* @__PURE__ */ new Date();
8560
8655
  tomorrow.setDate(tomorrow.getDate() + 1);
@@ -8568,6 +8663,11 @@ function renderFindTime(container) {
8568
8663
  html += `<label>Required Attendees<input class="gc-ft-required" type="text" placeholder="email1@example.com, email2@example.com" /><span class="gc-ft-hint">All must be available. Comma-separated emails.</span></label>`;
8569
8664
  html += `<label>Optional Attendees<input class="gc-ft-optional" type="text" placeholder="email3@example.com, email4@example.com" /><span class="gc-ft-hint">Preferred but not required. Priority by input order.</span></label>`;
8570
8665
  html += `<div class="gc-ft-row">`;
8666
+ html += `<label>Building<select class="gc-ft-building"><option value="">All</option></select></label>`;
8667
+ html += `<label>Resource<select class="gc-ft-resource"><option value="">All</option></select></label>`;
8668
+ html += `</div>`;
8669
+ html += `<span class="gc-ft-hint">Filter by building/resource to check room availability.</span>`;
8670
+ html += `<div class="gc-ft-row">`;
8571
8671
  html += `<label>Duration<select class="gc-ft-duration">`;
8572
8672
  for (const m of [15, 30, 45, 60, 90, 120, 180, 240]) {
8573
8673
  const label = m < 60 ? `${m} min` : m === 60 ? "1 hour" : `${m / 60} hours`;
@@ -8601,6 +8701,42 @@ function renderFindTime(container) {
8601
8701
  html += `<div class="gc-ft-results"></div>`;
8602
8702
  html += `</div>`;
8603
8703
  container.innerHTML = html;
8704
+ const bSelectEl = container.querySelector(".gc-ft-building");
8705
+ const rSelectEl = container.querySelector(".gc-ft-resource");
8706
+ void (async () => {
8707
+ try {
8708
+ const [bData, rData] = await Promise.all([
8709
+ apiJson("/api/calendar/buildings"),
8710
+ apiJson("/api/calendar/resources")
8711
+ ]);
8712
+ calFindBuildings = bData.buildings || [];
8713
+ calFindResources = rData.resources || [];
8714
+ if (bSelectEl) {
8715
+ for (const b of calFindBuildings) {
8716
+ const opt = document.createElement("option");
8717
+ opt.value = b.buildingId;
8718
+ opt.textContent = b.buildingName;
8719
+ bSelectEl.appendChild(opt);
8720
+ }
8721
+ if (calFindBuildingId) bSelectEl.value = calFindBuildingId;
8722
+ }
8723
+ updateResourceDropdown(container);
8724
+ if (calFindResourceId && rSelectEl) rSelectEl.value = calFindResourceId;
8725
+ } catch {
8726
+ }
8727
+ })();
8728
+ if (bSelectEl) {
8729
+ bSelectEl.addEventListener("change", () => {
8730
+ calFindBuildingId = bSelectEl.value;
8731
+ calFindResourceId = "";
8732
+ updateResourceDropdown(container);
8733
+ });
8734
+ }
8735
+ if (rSelectEl) {
8736
+ rSelectEl.addEventListener("change", () => {
8737
+ calFindResourceId = rSelectEl.value;
8738
+ });
8739
+ }
8604
8740
  container.querySelector(".gc-ft-search-btn").addEventListener("click", () => void handleFindSlots(container, 0));
8605
8741
  }
8606
8742
  async function handleFindSlots(container, offset = 0) {
@@ -8617,7 +8753,8 @@ async function handleFindSlots(container, offset = 0) {
8617
8753
  const btn = container.querySelector(".gc-ft-search-btn");
8618
8754
  const required = reqEmails ? reqEmails.split(",").map((e) => e.trim()).filter(Boolean) : [];
8619
8755
  const optional = optEmails ? optEmails.split(",").map((e) => e.trim()).filter(Boolean) : [];
8620
- if (!required.length) {
8756
+ const useResourceApi = !!(calFindBuildingId || calFindResourceId);
8757
+ if (!useResourceApi && !required.length) {
8621
8758
  statusEl.innerHTML = `<span class="gc-status-err">At least one required attendee is needed</span>`;
8622
8759
  return;
8623
8760
  }
@@ -8627,10 +8764,27 @@ async function handleFindSlots(container, offset = 0) {
8627
8764
  }
8628
8765
  btn.disabled = true;
8629
8766
  btn.textContent = "Searching...";
8630
- statusEl.innerHTML = `<span style="color:var(--muted);">Querying free/busy data for ${required.length + optional.length} participant(s)...</span>`;
8767
+ const participantCount = required.length + optional.length;
8768
+ statusEl.innerHTML = useResourceApi ? `<span style="color:var(--muted);">Checking resource availability${participantCount > 0 ? ` for ${participantCount} participant(s)` : ""}...</span>` : `<span style="color:var(--muted);">Querying free/busy data for ${participantCount} participant(s)...</span>`;
8631
8769
  resultsEl.innerHTML = "";
8632
8770
  try {
8633
- const res = await apiJson("/api/google/calendar/find-slots", {
8771
+ const res = useResourceApi ? await apiJson("/api/calendar/slots", {
8772
+ method: "POST",
8773
+ headers: { "Content-Type": "application/json" },
8774
+ body: JSON.stringify({
8775
+ dateFrom,
8776
+ dateTo,
8777
+ durationMinutes: duration,
8778
+ workStartHour: workStart,
8779
+ workEndHour: workEnd,
8780
+ buildingId: calFindBuildingId || void 0,
8781
+ resourceId: calFindResourceId || void 0,
8782
+ offset,
8783
+ bufferMinutes,
8784
+ required: required.length > 0 ? required : void 0,
8785
+ optional: optional.length > 0 ? optional : void 0
8786
+ })
8787
+ }) : await apiJson("/api/google/calendar/find-slots", {
8634
8788
  method: "POST",
8635
8789
  headers: { "Content-Type": "application/json" },
8636
8790
  body: JSON.stringify({
@@ -8685,6 +8839,13 @@ async function handleFindSlots(container, offset = 0) {
8685
8839
  }
8686
8840
  }
8687
8841
  rhtml += `<div class="gc-ft-slot-buffer">Buffer: ${slot.bufferBeforeMinutes} min before, ${slot.bufferAfterMinutes} min after</div>`;
8842
+ if (slot.resourceEmails?.length) {
8843
+ const names = slot.resourceEmails.map((e) => {
8844
+ const r = calFindResources.find((res2) => res2.resourceEmail === e);
8845
+ return r ? r.resourceName : e;
8846
+ });
8847
+ rhtml += `<div class="gc-ft-slot-meta" style="color:var(--accent);font-size:.82em;margin-top:2px;">\u{1F3E2} Available: ${esc(names.join(", "))}</div>`;
8848
+ }
8688
8849
  rhtml += `<button class="gc-ft-slot-create">Create Event</button>`;
8689
8850
  rhtml += `</div>`;
8690
8851
  }
@@ -8794,7 +8955,7 @@ function formatDateTime(dateStr) {
8794
8955
  return dateStr;
8795
8956
  }
8796
8957
  }
8797
- var calView, calCurrentEventId, calCurrentCalendarId, calCurrentEvent, calDays, calWinId, calCalendars, calEnabledCalendarIds, calShowCalendarPanel, calPrefill, STYLES2;
8958
+ var calView, calCurrentEventId, calCurrentCalendarId, calCurrentEvent, calDays, calWinId, calCalendars, calEnabledCalendarIds, calShowCalendarPanel, calPrefill, calFindBuildings, calFindResources, calFindBuildingId, calFindResourceId, STYLES2;
8798
8959
  var init_google_calendar = __esm({
8799
8960
  "services/desktop/client/modules/google-calendar.ts"() {
8800
8961
  init_helpers();
@@ -8810,6 +8971,10 @@ var init_google_calendar = __esm({
8810
8971
  calEnabledCalendarIds = /* @__PURE__ */ new Set();
8811
8972
  calShowCalendarPanel = false;
8812
8973
  calPrefill = null;
8974
+ calFindBuildings = [];
8975
+ calFindResources = [];
8976
+ calFindBuildingId = "";
8977
+ calFindResourceId = "";
8813
8978
  STYLES2 = `
8814
8979
  .gc-app{font-family:var(--sans);color:var(--fg);display:flex;flex-direction:column;height:100%;overflow:hidden}
8815
8980
  .gc-toolbar{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--input-border);background:var(--ctx-hover);flex-shrink:0;align-items:center;flex-wrap:wrap}
@@ -8860,9 +9025,10 @@ var init_google_calendar = __esm({
8860
9025
  .gc-create{padding:16px;display:flex;flex-direction:column;gap:10px}
8861
9026
  .gc-create label{font-size:.82em;font-weight:600;display:flex;flex-direction:column;gap:4px}
8862
9027
  .gc-create input,.gc-create textarea,.gc-create select{padding:8px 10px;border:1px solid var(--input-border);border-radius:var(--radius-sm);font-size:.88em;background:var(--input-bg);color:var(--fg);font-family:var(--sans)}
8863
- .gc-create input[type="datetime-local"]{color:var(--fg);font-family:inherit}
9028
+ .gc-create input[type="datetime-local"],.gc-create input[type="date"]{color:var(--fg);font-family:inherit}
8864
9029
  .gc-create input[type="datetime-local"]::-webkit-datetime-edit,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-fields-wrapper,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-text,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-month-field,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-day-field,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-year-field,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-hour-field,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-minute-field,.gc-create input[type="datetime-local"]::-webkit-datetime-edit-ampm-field{color:inherit;font-family:inherit}
8865
- .gc-create input[type="datetime-local"]::-webkit-calendar-picker-indicator{filter:var(--calendar-picker-filter,none)}
9030
+ .gc-create input[type="date"]::-webkit-datetime-edit,.gc-create input[type="date"]::-webkit-datetime-edit-fields-wrapper,.gc-create input[type="date"]::-webkit-datetime-edit-text,.gc-create input[type="date"]::-webkit-datetime-edit-month-field,.gc-create input[type="date"]::-webkit-datetime-edit-day-field,.gc-create input[type="date"]::-webkit-datetime-edit-year-field{color:inherit;font-family:inherit}
9031
+ .gc-create input[type="datetime-local"]::-webkit-calendar-picker-indicator,.gc-create input[type="date"]::-webkit-calendar-picker-indicator{filter:var(--calendar-picker-filter,none)}
8866
9032
  .gc-create textarea{min-height:80px;resize:vertical}
8867
9033
  .gc-create-btn{padding:10px 24px;border:none;color:#fff;background:linear-gradient(135deg,#4285f4,#34a853);border-radius:var(--radius);cursor:pointer;font-weight:700;font-size:.92em;align-self:flex-start}
8868
9034
  .gc-create-btn:disabled{opacity:.5;cursor:not-allowed}
@@ -10245,6 +10411,716 @@ var init_google_meet = __esm({
10245
10411
  }
10246
10412
  });
10247
10413
 
10414
+ // services/desktop/client/modules/resource-manager.ts
10415
+ function injectStyles2() {
10416
+ if (stylesInjected) return;
10417
+ const style = document.createElement("style");
10418
+ style.textContent = STYLES5;
10419
+ document.head.appendChild(style);
10420
+ stylesInjected = true;
10421
+ }
10422
+ function openResourceManager() {
10423
+ injectStyles2();
10424
+ const useLucide = state.iconStyle === "modern" || state.iconStyle === "minimal";
10425
+ const winIcon = useLucide ? lucideIcon("building", "di-lucide") : "\u{1F3E2}";
10426
+ const win = createWindow({
10427
+ appId: "__resource_manager",
10428
+ title: "Resource Manager",
10429
+ icon: winIcon,
10430
+ content: "app",
10431
+ width: 720,
10432
+ height: 520
10433
+ });
10434
+ rmWinId = win.id;
10435
+ rmView = "buildings";
10436
+ rmBuildings = [];
10437
+ rmResources = [];
10438
+ rmFilterBuildingId = "";
10439
+ rmStatus = "";
10440
+ rmStatusType = "";
10441
+ renderRmApp();
10442
+ }
10443
+ function renderRmApp() {
10444
+ const body = getWinBody(rmWinId);
10445
+ let html = '<div class="rm-app">';
10446
+ if (rmStatus) {
10447
+ const cls = rmStatusType === "ok" ? "rm-status-ok" : rmStatusType === "err" ? "rm-status-err" : "";
10448
+ html += `<div class="rm-status ${cls}">${esc(rmStatus)}</div>`;
10449
+ }
10450
+ html += '<div class="rm-toolbar">';
10451
+ if (rmView === "create-building" || rmView === "create-resource" || rmView === "edit-building" || rmView === "edit-resource") {
10452
+ html += `<button class="rm-back-btn" data-rm-action="back">${lucideIcon("arrow-left", "")} Back</button>`;
10453
+ } else {
10454
+ html += `<button class="${rmView === "buildings" ? "active" : ""}" data-rm-action="tab-buildings">Buildings</button>`;
10455
+ html += `<button class="${rmView === "resources" ? "active" : ""}" data-rm-action="tab-resources">Resources</button>`;
10456
+ if (rmView === "buildings") {
10457
+ html += `<button class="rm-add-btn" data-rm-action="new-building">+ New Building</button>`;
10458
+ }
10459
+ if (rmView === "resources") {
10460
+ html += `<select data-rm-action="filter-building"><option value="">All buildings</option>`;
10461
+ for (const b of rmBuildings) {
10462
+ const sel = rmFilterBuildingId === b.buildingId ? " selected" : "";
10463
+ html += `<option value="${esc(b.buildingId)}"${sel}>${esc(b.buildingName)}</option>`;
10464
+ }
10465
+ html += `</select>`;
10466
+ html += `<button class="rm-add-btn" data-rm-action="new-resource">+ New Resource</button>`;
10467
+ }
10468
+ }
10469
+ html += "</div>";
10470
+ html += '<div class="rm-content" id="rmContent"></div>';
10471
+ html += "</div>";
10472
+ body.innerHTML = html;
10473
+ attachToolbarListeners(body);
10474
+ if (rmView === "buildings") {
10475
+ void renderBuildingsList();
10476
+ } else if (rmView === "resources") {
10477
+ void renderResourcesList();
10478
+ } else if (rmView === "create-building") {
10479
+ renderCreateBuildingForm();
10480
+ } else if (rmView === "create-resource") {
10481
+ renderCreateResourceForm();
10482
+ } else if (rmView === "edit-building") {
10483
+ renderEditBuildingForm();
10484
+ } else if (rmView === "edit-resource") {
10485
+ renderEditResourceForm();
10486
+ }
10487
+ }
10488
+ function attachToolbarListeners(container) {
10489
+ container.querySelectorAll("[data-rm-action]").forEach((el) => {
10490
+ const action = el.dataset.rmAction || "";
10491
+ if (el.tagName === "SELECT") {
10492
+ el.addEventListener("change", () => {
10493
+ if (action === "filter-building") {
10494
+ rmFilterBuildingId = el.value;
10495
+ void renderResourcesList();
10496
+ }
10497
+ });
10498
+ } else {
10499
+ el.addEventListener("click", () => {
10500
+ rmStatus = "";
10501
+ rmStatusType = "";
10502
+ if (action === "tab-buildings") {
10503
+ rmView = "buildings";
10504
+ renderRmApp();
10505
+ } else if (action === "tab-resources") {
10506
+ rmView = "resources";
10507
+ renderRmApp();
10508
+ } else if (action === "new-building") {
10509
+ rmView = "create-building";
10510
+ renderRmApp();
10511
+ } else if (action === "new-resource") {
10512
+ rmView = "create-resource";
10513
+ renderRmApp();
10514
+ } else if (action === "back") {
10515
+ rmView = rmView === "create-building" || rmView === "edit-building" ? "buildings" : "resources";
10516
+ rmEditingBuilding = null;
10517
+ rmEditingResource = null;
10518
+ renderRmApp();
10519
+ }
10520
+ });
10521
+ }
10522
+ });
10523
+ }
10524
+ function getContentEl() {
10525
+ return document.getElementById("rmContent");
10526
+ }
10527
+ async function renderBuildingsList() {
10528
+ const el = getContentEl();
10529
+ if (!el) return;
10530
+ el.innerHTML = '<div class="rm-loading">Loading buildings...</div>';
10531
+ try {
10532
+ const data = await apiJson("/api/calendar/buildings");
10533
+ if (!data.ok) {
10534
+ el.innerHTML = `<div class="rm-empty">${esc(data.error || "Could not load buildings")}</div>`;
10535
+ return;
10536
+ }
10537
+ rmBuildings = data.buildings || [];
10538
+ } catch (e) {
10539
+ el.innerHTML = `<div class="rm-empty">Error: ${esc(e instanceof Error ? e.message : String(e))}</div>`;
10540
+ return;
10541
+ }
10542
+ if (rmBuildings.length === 0) {
10543
+ el.innerHTML = '<div class="rm-empty">No buildings found. Click "+ New Building" to create one.</div>';
10544
+ return;
10545
+ }
10546
+ let html = '<div class="rm-list">';
10547
+ html += '<div class="rm-list-header">';
10548
+ html += '<div class="rm-list-col" style="flex:2">Name</div>';
10549
+ html += '<div class="rm-list-col" style="flex:2">Description</div>';
10550
+ html += '<div class="rm-list-col" style="flex:1">Floors</div>';
10551
+ html += '<div class="rm-list-col" style="flex:0.5"></div>';
10552
+ html += "</div>";
10553
+ for (const b of rmBuildings) {
10554
+ const allFloors = b.floorNames && b.floorNames.length > 0 ? b.floorNames.join(", ") : "-";
10555
+ const floors = b.floorNames && b.floorNames.length > 5 ? `${b.floorNames.slice(0, 5).join(", ")} +${b.floorNames.length - 5}` : allFloors;
10556
+ html += '<div class="rm-list-row">';
10557
+ html += `<div class="rm-list-col" style="flex:2;font-weight:600" title="${esc(b.buildingName)}">${esc(b.buildingName)}</div>`;
10558
+ html += `<div class="rm-list-col" style="flex:2" title="${esc(b.description || "-")}">${esc(b.description || "-")}</div>`;
10559
+ html += `<div class="rm-list-col" style="flex:1" title="${esc(allFloors)}">${esc(floors)}</div>`;
10560
+ html += `<div class="rm-list-col" style="flex:0.5"><button class="rm-edit-btn" data-rm-edit-building="${esc(b.buildingId)}">Edit</button></div>`;
10561
+ html += "</div>";
10562
+ }
10563
+ html += "</div>";
10564
+ el.innerHTML = html;
10565
+ el.querySelectorAll("[data-rm-edit-building]").forEach((btn) => {
10566
+ btn.addEventListener("click", () => {
10567
+ const id = btn.dataset.rmEditBuilding || "";
10568
+ rmEditingBuilding = rmBuildings.find((b) => b.buildingId === id) || null;
10569
+ if (rmEditingBuilding) {
10570
+ rmView = "edit-building";
10571
+ renderRmApp();
10572
+ }
10573
+ });
10574
+ });
10575
+ }
10576
+ async function renderResourcesList() {
10577
+ const el = getContentEl();
10578
+ if (!el) return;
10579
+ el.innerHTML = '<div class="rm-loading">Loading resources...</div>';
10580
+ if (rmBuildings.length === 0) {
10581
+ try {
10582
+ const bData = await apiJson("/api/calendar/buildings");
10583
+ if (bData.ok) rmBuildings = bData.buildings || [];
10584
+ } catch {
10585
+ }
10586
+ }
10587
+ try {
10588
+ const data = await apiJson("/api/calendar/resources");
10589
+ if (!data.ok) {
10590
+ el.innerHTML = `<div class="rm-empty">${esc(data.error || "Could not load resources")}</div>`;
10591
+ return;
10592
+ }
10593
+ rmResources = data.resources || [];
10594
+ } catch (e) {
10595
+ el.innerHTML = `<div class="rm-empty">Error: ${esc(e instanceof Error ? e.message : String(e))}</div>`;
10596
+ return;
10597
+ }
10598
+ const filtered = rmFilterBuildingId ? rmResources.filter((r) => r.buildingId === rmFilterBuildingId) : rmResources;
10599
+ if (filtered.length === 0) {
10600
+ const msg = rmFilterBuildingId ? "No resources found for this building." : 'No resources found. Click "+ New Resource" to create one.';
10601
+ el.innerHTML = `<div class="rm-empty">${esc(msg)}</div>`;
10602
+ return;
10603
+ }
10604
+ let html = '<div class="rm-list">';
10605
+ html += '<div class="rm-list-header">';
10606
+ html += '<div class="rm-list-col" style="flex:2">Name</div>';
10607
+ html += '<div class="rm-list-col" style="flex:1.5">Building</div>';
10608
+ html += '<div class="rm-list-col" style="flex:1">Type</div>';
10609
+ html += '<div class="rm-list-col" style="flex:0.5">Capacity</div>';
10610
+ html += '<div class="rm-list-col" style="flex:2">Email</div>';
10611
+ html += '<div class="rm-list-col" style="flex:0.5"></div>';
10612
+ html += "</div>";
10613
+ const buildingMap = /* @__PURE__ */ new Map();
10614
+ for (const b of rmBuildings) {
10615
+ buildingMap.set(b.buildingId, b.buildingName);
10616
+ }
10617
+ for (const r of filtered) {
10618
+ const buildingName = r.buildingId ? buildingMap.get(r.buildingId) || r.buildingId : "-";
10619
+ const resourceType = r.resourceType || "-";
10620
+ const capacity = r.capacity != null ? String(r.capacity) : "-";
10621
+ const displayName = r.generatedResourceName || r.resourceName;
10622
+ html += '<div class="rm-list-row">';
10623
+ html += `<div class="rm-list-col" style="flex:2;font-weight:600" title="${esc(displayName)}">${esc(displayName)}</div>`;
10624
+ html += `<div class="rm-list-col" style="flex:1.5" title="${esc(buildingName)}">${esc(buildingName)}</div>`;
10625
+ html += `<div class="rm-list-col" style="flex:1">${esc(resourceType)}</div>`;
10626
+ html += `<div class="rm-list-col" style="flex:0.5">${esc(capacity)}</div>`;
10627
+ html += `<div class="rm-list-col" style="flex:2" title="${esc(r.resourceEmail)}">${esc(r.resourceEmail)}</div>`;
10628
+ html += `<div class="rm-list-col" style="flex:0.5"><button class="rm-edit-btn" data-rm-edit-resource="${esc(r.resourceId)}">Edit</button></div>`;
10629
+ html += "</div>";
10630
+ }
10631
+ html += "</div>";
10632
+ el.innerHTML = html;
10633
+ el.querySelectorAll("[data-rm-edit-resource]").forEach((btn) => {
10634
+ btn.addEventListener("click", () => {
10635
+ const id = btn.dataset.rmEditResource || "";
10636
+ rmEditingResource = rmResources.find((r) => r.resourceId === id) || null;
10637
+ if (rmEditingResource) {
10638
+ rmView = "edit-resource";
10639
+ renderRmApp();
10640
+ }
10641
+ });
10642
+ });
10643
+ }
10644
+ function renderCreateBuildingForm() {
10645
+ const el = getContentEl();
10646
+ if (!el) return;
10647
+ let html = '<div class="rm-form">';
10648
+ html += '<label>Building Name <span style="color:var(--danger)">*</span>';
10649
+ html += '<input type="text" id="rmBuildingName" placeholder="e.g. Main Office" /></label>';
10650
+ html += "<label>Description";
10651
+ html += '<textarea id="rmBuildingDesc" placeholder="Optional description"></textarea></label>';
10652
+ html += "<label>Floor Names";
10653
+ html += '<input type="text" id="rmBuildingFloors" placeholder="Comma-separated, e.g. 1F, 2F, 3F" />';
10654
+ html += '<span style="font-size:.75em;color:var(--muted)">Comma-separated floor names or a number of floors</span></label>';
10655
+ html += '<button class="rm-form-btn" id="rmCreateBuildingBtn">Create Building</button>';
10656
+ html += '<div id="rmFormStatus" class="rm-status"></div>';
10657
+ html += "</div>";
10658
+ el.innerHTML = html;
10659
+ const btn = document.getElementById("rmCreateBuildingBtn");
10660
+ if (btn) {
10661
+ btn.addEventListener("click", () => void handleCreateBuilding());
10662
+ }
10663
+ }
10664
+ async function handleCreateBuilding() {
10665
+ const nameInput = document.getElementById("rmBuildingName");
10666
+ const descInput = document.getElementById("rmBuildingDesc");
10667
+ const floorsInput = document.getElementById("rmBuildingFloors");
10668
+ const statusEl = document.getElementById("rmFormStatus");
10669
+ const btn = document.getElementById("rmCreateBuildingBtn");
10670
+ const buildingName = nameInput?.value.trim() || "";
10671
+ if (!buildingName) {
10672
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Building name is required</span>';
10673
+ return;
10674
+ }
10675
+ if (btn) {
10676
+ btn.disabled = true;
10677
+ btn.textContent = "Creating...";
10678
+ }
10679
+ if (statusEl) statusEl.textContent = "";
10680
+ const body = { buildingName };
10681
+ const desc = descInput?.value.trim();
10682
+ if (desc) body.description = desc;
10683
+ const floorsRaw = floorsInput?.value.trim();
10684
+ if (floorsRaw) {
10685
+ const numFloors = parseInt(floorsRaw, 10);
10686
+ if (!isNaN(numFloors) && String(numFloors) === floorsRaw && numFloors > 0) {
10687
+ const names = [];
10688
+ for (let i = 1; i <= numFloors; i++) names.push(String(i));
10689
+ body.floorNames = names;
10690
+ } else {
10691
+ body.floorNames = floorsRaw.split(",").map((s) => s.trim()).filter(Boolean);
10692
+ }
10693
+ }
10694
+ try {
10695
+ const res = await api("/api/calendar/buildings", {
10696
+ method: "POST",
10697
+ headers: { "Content-Type": "application/json" },
10698
+ body: JSON.stringify(body)
10699
+ });
10700
+ if (res.status === 403) {
10701
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">This requires Google Workspace admin privileges. Please ask an administrator to manage resources.</span>';
10702
+ if (btn) {
10703
+ btn.disabled = false;
10704
+ btn.textContent = "Create Building";
10705
+ }
10706
+ return;
10707
+ }
10708
+ const data = await res.json();
10709
+ if (!res.ok || !data.ok) {
10710
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">${esc(data.error || "Failed to create building")}</span>`;
10711
+ if (btn) {
10712
+ btn.disabled = false;
10713
+ btn.textContent = "Create Building";
10714
+ }
10715
+ return;
10716
+ }
10717
+ rmStatus = "Building created successfully!";
10718
+ rmStatusType = "ok";
10719
+ rmView = "buildings";
10720
+ renderRmApp();
10721
+ } catch (e) {
10722
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">Error: ${esc(e instanceof Error ? e.message : String(e))}</span>`;
10723
+ if (btn) {
10724
+ btn.disabled = false;
10725
+ btn.textContent = "Create Building";
10726
+ }
10727
+ }
10728
+ }
10729
+ function renderCreateResourceForm() {
10730
+ const el = getContentEl();
10731
+ if (!el) return;
10732
+ if (rmBuildings.length === 0) {
10733
+ el.innerHTML = '<div class="rm-loading">Loading buildings...</div>';
10734
+ void apiJson("/api/calendar/buildings").then((data) => {
10735
+ if (data.ok) rmBuildings = data.buildings || [];
10736
+ renderCreateResourceFormInner(el);
10737
+ }).catch(() => {
10738
+ renderCreateResourceFormInner(el);
10739
+ });
10740
+ return;
10741
+ }
10742
+ renderCreateResourceFormInner(el);
10743
+ }
10744
+ function renderCreateResourceFormInner(el) {
10745
+ let html = '<div class="rm-form">';
10746
+ html += '<label>Resource Name <span style="color:var(--danger)">*</span>';
10747
+ html += '<input type="text" id="rmResName" placeholder="e.g. Conference Room A" /></label>';
10748
+ html += "<label>Building";
10749
+ html += '<select id="rmResBuilding"><option value="">-- None --</option>';
10750
+ for (const b of rmBuildings) {
10751
+ html += `<option value="${esc(b.buildingId)}">${esc(b.buildingName)}</option>`;
10752
+ }
10753
+ html += "</select></label>";
10754
+ html += "<label>Capacity";
10755
+ html += '<input type="number" id="rmResCapacity" placeholder="e.g. 10" min="0" /></label>';
10756
+ html += "<label>Resource Type";
10757
+ html += '<select id="rmResType">';
10758
+ html += '<option value="CONFERENCE_ROOM">Conference Room</option>';
10759
+ html += '<option value="OTHER">Other</option>';
10760
+ html += "</select></label>";
10761
+ html += "<label>Floor Name";
10762
+ html += '<input type="text" id="rmResFloor" placeholder="e.g. 2F" /></label>';
10763
+ html += "<label>Description";
10764
+ html += '<textarea id="rmResDesc" placeholder="Optional description"></textarea></label>';
10765
+ html += '<button class="rm-form-btn" id="rmCreateResourceBtn">Create Resource</button>';
10766
+ html += '<div id="rmFormStatus" class="rm-status"></div>';
10767
+ html += "</div>";
10768
+ el.innerHTML = html;
10769
+ const btn = document.getElementById("rmCreateResourceBtn");
10770
+ if (btn) {
10771
+ btn.addEventListener("click", () => void handleCreateResource());
10772
+ }
10773
+ }
10774
+ async function handleCreateResource() {
10775
+ const nameInput = document.getElementById("rmResName");
10776
+ const buildingSelect = document.getElementById("rmResBuilding");
10777
+ const capacityInput = document.getElementById("rmResCapacity");
10778
+ const typeSelect = document.getElementById("rmResType");
10779
+ const floorInput = document.getElementById("rmResFloor");
10780
+ const descInput = document.getElementById("rmResDesc");
10781
+ const statusEl = document.getElementById("rmFormStatus");
10782
+ const btn = document.getElementById("rmCreateResourceBtn");
10783
+ const resourceName = nameInput?.value.trim() || "";
10784
+ if (!resourceName) {
10785
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Resource name is required</span>';
10786
+ return;
10787
+ }
10788
+ if (btn) {
10789
+ btn.disabled = true;
10790
+ btn.textContent = "Creating...";
10791
+ }
10792
+ if (statusEl) statusEl.textContent = "";
10793
+ const reqBody = { resourceName };
10794
+ const buildingId = buildingSelect?.value;
10795
+ if (buildingId) reqBody.buildingId = buildingId;
10796
+ const capacityRaw = capacityInput?.value.trim();
10797
+ if (capacityRaw) {
10798
+ const cap = parseInt(capacityRaw, 10);
10799
+ if (!isNaN(cap) && cap >= 0) reqBody.capacity = cap;
10800
+ }
10801
+ const resourceType = typeSelect?.value;
10802
+ if (resourceType) reqBody.resourceType = resourceType;
10803
+ const floorName = floorInput?.value.trim();
10804
+ if (floorName) reqBody.floorName = floorName;
10805
+ const description = descInput?.value.trim();
10806
+ if (description) reqBody.resourceDescription = description;
10807
+ try {
10808
+ const res = await api("/api/calendar/resources", {
10809
+ method: "POST",
10810
+ headers: { "Content-Type": "application/json" },
10811
+ body: JSON.stringify(reqBody)
10812
+ });
10813
+ if (res.status === 403) {
10814
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">This requires Google Workspace admin privileges. Please ask an administrator to manage resources.</span>';
10815
+ if (btn) {
10816
+ btn.disabled = false;
10817
+ btn.textContent = "Create Resource";
10818
+ }
10819
+ return;
10820
+ }
10821
+ const data = await res.json();
10822
+ if (!res.ok || !data.ok) {
10823
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">${esc(data.error || "Failed to create resource")}</span>`;
10824
+ if (btn) {
10825
+ btn.disabled = false;
10826
+ btn.textContent = "Create Resource";
10827
+ }
10828
+ return;
10829
+ }
10830
+ rmStatus = "Resource created successfully!";
10831
+ rmStatusType = "ok";
10832
+ rmView = "resources";
10833
+ renderRmApp();
10834
+ } catch (e) {
10835
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">Error: ${esc(e instanceof Error ? e.message : String(e))}</span>`;
10836
+ if (btn) {
10837
+ btn.disabled = false;
10838
+ btn.textContent = "Create Resource";
10839
+ }
10840
+ }
10841
+ }
10842
+ function renderEditBuildingForm() {
10843
+ const el = getContentEl();
10844
+ if (!el || !rmEditingBuilding) return;
10845
+ const b = rmEditingBuilding;
10846
+ let html = '<div class="rm-form">';
10847
+ html += "<label>Building ID";
10848
+ html += `<input type="text" value="${esc(b.buildingId)}" disabled style="opacity:.6;cursor:not-allowed" /></label>`;
10849
+ html += '<label>Building Name <span style="color:var(--danger)">*</span>';
10850
+ html += `<input type="text" id="rmEditBuildingName" value="${esc(b.buildingName)}" /></label>`;
10851
+ html += "<label>Description";
10852
+ html += `<textarea id="rmEditBuildingDesc">${esc(b.description || "")}</textarea></label>`;
10853
+ html += "<label>Floor Names";
10854
+ html += `<input type="text" id="rmEditBuildingFloors" value="${esc(b.floorNames ? b.floorNames.join(", ") : "")}" />`;
10855
+ html += '<span style="font-size:.75em;color:var(--muted)">Comma-separated floor names or a number of floors</span></label>';
10856
+ html += '<button class="rm-form-btn" id="rmUpdateBuildingBtn">Update Building</button>';
10857
+ html += '<div id="rmFormStatus" class="rm-status"></div>';
10858
+ html += "</div>";
10859
+ el.innerHTML = html;
10860
+ const btn = document.getElementById("rmUpdateBuildingBtn");
10861
+ if (btn) {
10862
+ btn.addEventListener("click", () => void handleUpdateBuilding());
10863
+ }
10864
+ }
10865
+ async function handleUpdateBuilding() {
10866
+ if (!rmEditingBuilding) return;
10867
+ const nameInput = document.getElementById("rmEditBuildingName");
10868
+ const descInput = document.getElementById("rmEditBuildingDesc");
10869
+ const floorsInput = document.getElementById("rmEditBuildingFloors");
10870
+ const statusEl = document.getElementById("rmFormStatus");
10871
+ const btn = document.getElementById("rmUpdateBuildingBtn");
10872
+ const body = {};
10873
+ const newName = nameInput?.value.trim() || "";
10874
+ if (newName && newName !== rmEditingBuilding.buildingName) body.buildingName = newName;
10875
+ const newDesc = descInput?.value.trim() || "";
10876
+ if (newDesc !== (rmEditingBuilding.description || "")) body.description = newDesc;
10877
+ const floorsRaw = floorsInput?.value.trim() || "";
10878
+ const existingFloors = rmEditingBuilding.floorNames ? rmEditingBuilding.floorNames.join(", ") : "";
10879
+ if (floorsRaw !== existingFloors) {
10880
+ if (!floorsRaw) {
10881
+ body.floorNames = [];
10882
+ } else {
10883
+ const numFloors = parseInt(floorsRaw, 10);
10884
+ if (!isNaN(numFloors) && String(numFloors) === floorsRaw && numFloors > 0) {
10885
+ const names = [];
10886
+ for (let i = 1; i <= numFloors; i++) names.push(String(i));
10887
+ body.floorNames = names;
10888
+ } else {
10889
+ body.floorNames = floorsRaw.split(",").map((s) => s.trim()).filter(Boolean);
10890
+ }
10891
+ }
10892
+ }
10893
+ if (Object.keys(body).length === 0) {
10894
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">No changes detected</span>';
10895
+ return;
10896
+ }
10897
+ if (btn) {
10898
+ btn.disabled = true;
10899
+ btn.textContent = "Updating...";
10900
+ }
10901
+ if (statusEl) statusEl.textContent = "";
10902
+ try {
10903
+ const res = await api(`/api/calendar/buildings/${encodeURIComponent(rmEditingBuilding.buildingId)}`, {
10904
+ method: "PATCH",
10905
+ headers: { "Content-Type": "application/json" },
10906
+ body: JSON.stringify(body)
10907
+ });
10908
+ if (res.status === 403) {
10909
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Permission denied: Google Workspace admin privileges required.</span>';
10910
+ if (btn) {
10911
+ btn.disabled = false;
10912
+ btn.textContent = "Update Building";
10913
+ }
10914
+ return;
10915
+ }
10916
+ if (res.status === 404) {
10917
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Building not found.</span>';
10918
+ if (btn) {
10919
+ btn.disabled = false;
10920
+ btn.textContent = "Update Building";
10921
+ }
10922
+ return;
10923
+ }
10924
+ const data = await res.json();
10925
+ if (!res.ok || !data.ok) {
10926
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">${esc(data.error || "Failed to update building")}</span>`;
10927
+ if (btn) {
10928
+ btn.disabled = false;
10929
+ btn.textContent = "Update Building";
10930
+ }
10931
+ return;
10932
+ }
10933
+ rmStatus = "Building updated successfully!";
10934
+ rmStatusType = "ok";
10935
+ rmEditingBuilding = null;
10936
+ rmView = "buildings";
10937
+ renderRmApp();
10938
+ } catch (e) {
10939
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">Error: ${esc(e instanceof Error ? e.message : String(e))}</span>`;
10940
+ if (btn) {
10941
+ btn.disabled = false;
10942
+ btn.textContent = "Update Building";
10943
+ }
10944
+ }
10945
+ }
10946
+ function renderEditResourceForm() {
10947
+ const el = getContentEl();
10948
+ if (!el || !rmEditingResource) return;
10949
+ if (rmBuildings.length === 0) {
10950
+ el.innerHTML = '<div class="rm-loading">Loading buildings...</div>';
10951
+ void apiJson("/api/calendar/buildings").then((data) => {
10952
+ if (data.ok) rmBuildings = data.buildings || [];
10953
+ renderEditResourceFormInner(el);
10954
+ }).catch(() => {
10955
+ renderEditResourceFormInner(el);
10956
+ });
10957
+ return;
10958
+ }
10959
+ renderEditResourceFormInner(el);
10960
+ }
10961
+ function renderEditResourceFormInner(el) {
10962
+ if (!rmEditingResource) return;
10963
+ const r = rmEditingResource;
10964
+ let html = '<div class="rm-form">';
10965
+ html += "<label>Resource ID";
10966
+ html += `<input type="text" value="${esc(r.resourceId)}" disabled style="opacity:.6;cursor:not-allowed" /></label>`;
10967
+ html += '<label>Resource Name <span style="color:var(--danger)">*</span>';
10968
+ html += `<input type="text" id="rmEditResName" value="${esc(r.resourceName)}" /></label>`;
10969
+ html += "<label>Building";
10970
+ html += '<select id="rmEditResBuilding"><option value="">-- None --</option>';
10971
+ for (const b of rmBuildings) {
10972
+ const sel = r.buildingId === b.buildingId ? " selected" : "";
10973
+ html += `<option value="${esc(b.buildingId)}"${sel}>${esc(b.buildingName)}</option>`;
10974
+ }
10975
+ html += "</select></label>";
10976
+ html += "<label>Capacity";
10977
+ html += `<input type="number" id="rmEditResCapacity" value="${r.capacity != null ? r.capacity : ""}" min="0" /></label>`;
10978
+ html += "<label>Resource Type";
10979
+ html += '<select id="rmEditResType">';
10980
+ html += `<option value="CONFERENCE_ROOM"${r.resourceType === "CONFERENCE_ROOM" ? " selected" : ""}>Conference Room</option>`;
10981
+ html += `<option value="OTHER"${r.resourceType === "OTHER" ? " selected" : ""}>Other</option>`;
10982
+ html += "</select></label>";
10983
+ html += "<label>Floor Name";
10984
+ html += `<input type="text" id="rmEditResFloor" value="${esc(r.floorName || "")}" /></label>`;
10985
+ html += "<label>Description";
10986
+ html += `<textarea id="rmEditResDesc">${esc(r.resourceDescription || "")}</textarea></label>`;
10987
+ html += "<label>Email";
10988
+ html += `<input type="text" value="${esc(r.resourceEmail)}" disabled style="opacity:.6;cursor:not-allowed" /></label>`;
10989
+ html += '<button class="rm-form-btn" id="rmUpdateResourceBtn">Update Resource</button>';
10990
+ html += '<div id="rmFormStatus" class="rm-status"></div>';
10991
+ html += "</div>";
10992
+ el.innerHTML = html;
10993
+ const btn = document.getElementById("rmUpdateResourceBtn");
10994
+ if (btn) {
10995
+ btn.addEventListener("click", () => void handleUpdateResource());
10996
+ }
10997
+ }
10998
+ async function handleUpdateResource() {
10999
+ if (!rmEditingResource) return;
11000
+ const nameInput = document.getElementById("rmEditResName");
11001
+ const buildingSelect = document.getElementById("rmEditResBuilding");
11002
+ const capacityInput = document.getElementById("rmEditResCapacity");
11003
+ const typeSelect = document.getElementById("rmEditResType");
11004
+ const floorInput = document.getElementById("rmEditResFloor");
11005
+ const descInput = document.getElementById("rmEditResDesc");
11006
+ const statusEl = document.getElementById("rmFormStatus");
11007
+ const btn = document.getElementById("rmUpdateResourceBtn");
11008
+ const body = {};
11009
+ const newName = nameInput?.value.trim() || "";
11010
+ if (newName && newName !== rmEditingResource.resourceName) body.resourceName = newName;
11011
+ const newBuilding = buildingSelect?.value || "";
11012
+ if (newBuilding !== (rmEditingResource.buildingId || "")) body.buildingId = newBuilding;
11013
+ const capRaw = capacityInput?.value.trim() || "";
11014
+ const newCap = capRaw ? parseInt(capRaw, 10) : 0;
11015
+ if (newCap !== (rmEditingResource.capacity || 0)) body.capacity = newCap;
11016
+ const newType = typeSelect?.value || "";
11017
+ if (newType && newType !== (rmEditingResource.resourceType || "")) body.resourceType = newType;
11018
+ const newFloor = floorInput?.value.trim() || "";
11019
+ if (newFloor !== (rmEditingResource.floorName || "")) body.floorName = newFloor;
11020
+ const newDesc = descInput?.value.trim() || "";
11021
+ if (newDesc !== (rmEditingResource.resourceDescription || "")) body.resourceDescription = newDesc;
11022
+ if (Object.keys(body).length === 0) {
11023
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">No changes detected</span>';
11024
+ return;
11025
+ }
11026
+ if (btn) {
11027
+ btn.disabled = true;
11028
+ btn.textContent = "Updating...";
11029
+ }
11030
+ if (statusEl) statusEl.textContent = "";
11031
+ try {
11032
+ const res = await api(`/api/calendar/resources/${encodeURIComponent(rmEditingResource.resourceId)}`, {
11033
+ method: "PATCH",
11034
+ headers: { "Content-Type": "application/json" },
11035
+ body: JSON.stringify(body)
11036
+ });
11037
+ if (res.status === 403) {
11038
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Permission denied: Google Workspace admin privileges required.</span>';
11039
+ if (btn) {
11040
+ btn.disabled = false;
11041
+ btn.textContent = "Update Resource";
11042
+ }
11043
+ return;
11044
+ }
11045
+ if (res.status === 404) {
11046
+ if (statusEl) statusEl.innerHTML = '<span class="rm-status-err">Resource not found.</span>';
11047
+ if (btn) {
11048
+ btn.disabled = false;
11049
+ btn.textContent = "Update Resource";
11050
+ }
11051
+ return;
11052
+ }
11053
+ const data = await res.json();
11054
+ if (!res.ok || !data.ok) {
11055
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">${esc(data.error || "Failed to update resource")}</span>`;
11056
+ if (btn) {
11057
+ btn.disabled = false;
11058
+ btn.textContent = "Update Resource";
11059
+ }
11060
+ return;
11061
+ }
11062
+ rmStatus = "Resource updated successfully!";
11063
+ rmStatusType = "ok";
11064
+ rmEditingResource = null;
11065
+ rmView = "resources";
11066
+ renderRmApp();
11067
+ } catch (e) {
11068
+ if (statusEl) statusEl.innerHTML = `<span class="rm-status-err">Error: ${esc(e instanceof Error ? e.message : String(e))}</span>`;
11069
+ if (btn) {
11070
+ btn.disabled = false;
11071
+ btn.textContent = "Update Resource";
11072
+ }
11073
+ }
11074
+ }
11075
+ var rmView, rmWinId, rmBuildings, rmResources, rmFilterBuildingId, rmStatus, rmStatusType, rmEditingBuilding, rmEditingResource, STYLES5, stylesInjected;
11076
+ var init_resource_manager = __esm({
11077
+ "services/desktop/client/modules/resource-manager.ts"() {
11078
+ init_helpers();
11079
+ init_state();
11080
+ init_window_manager();
11081
+ rmView = "buildings";
11082
+ rmWinId = -1;
11083
+ rmBuildings = [];
11084
+ rmResources = [];
11085
+ rmFilterBuildingId = "";
11086
+ rmStatus = "";
11087
+ rmStatusType = "";
11088
+ rmEditingBuilding = null;
11089
+ rmEditingResource = null;
11090
+ STYLES5 = `
11091
+ .rm-app{font-family:var(--sans);color:var(--fg);display:flex;flex-direction:column;height:100%;overflow:hidden}
11092
+ .rm-toolbar{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--input-border);background:var(--ctx-hover);flex-shrink:0;align-items:center;flex-wrap:wrap}
11093
+ .rm-toolbar button{padding:6px 14px;border:1px solid var(--input-border);background:var(--panel);color:var(--fg);border-radius:var(--radius-sm);cursor:pointer;font-size:.82em;font-weight:600;transition:all .15s;min-width:90px;text-align:center}
11094
+ .rm-toolbar button:hover{background:var(--ctx-hover)}
11095
+ .rm-toolbar button.active{background:linear-gradient(135deg,#4285f4,#34a853);color:#fff;border-color:transparent}
11096
+ .rm-content{flex:1;overflow-y:auto;padding:0}
11097
+ .rm-loading{text-align:center;padding:40px;color:var(--muted);font-size:.9em}
11098
+ .rm-empty{text-align:center;padding:40px;color:var(--muted);font-size:.9em}
11099
+ .rm-list{width:100%}
11100
+ .rm-list-header{display:flex;gap:8px;padding:8px 14px;font-size:.78em;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);border-bottom:1px solid var(--input-border);background:var(--ctx-hover)}
11101
+ .rm-list-row{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid var(--input-border);font-size:.85em;align-items:center;transition:background .1s}
11102
+ .rm-list-row:hover{background:var(--ctx-hover)}
11103
+ .rm-list-col{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
11104
+ .rm-form{padding:16px;display:flex;flex-direction:column;gap:10px}
11105
+ .rm-form label{font-size:.82em;font-weight:600;display:flex;flex-direction:column;gap:4px}
11106
+ .rm-form input,.rm-form textarea,.rm-form select{padding:8px 10px;border:1px solid var(--input-border);border-radius:var(--radius-sm);font-size:.88em;background:var(--input-bg);color:var(--fg);font-family:var(--sans)}
11107
+ .rm-form textarea{min-height:60px;resize:vertical}
11108
+ .rm-form-btn{padding:10px 24px;border:none;color:#fff;background:linear-gradient(135deg,#4285f4,#34a853);border-radius:var(--radius);cursor:pointer;font-weight:700;font-size:.92em;align-self:flex-start}
11109
+ .rm-form-btn:disabled{opacity:.5;cursor:not-allowed}
11110
+ .rm-status{padding:8px 14px;font-size:.82em;text-align:center}
11111
+ .rm-status-ok{color:var(--success)}
11112
+ .rm-status-err{color:var(--danger)}
11113
+ .rm-add-btn{padding:6px 14px;border:1px solid var(--input-border);background:var(--panel);color:var(--accent);border-radius:var(--radius-sm);cursor:pointer;font-size:.82em;font-weight:600;margin-left:auto}
11114
+ .rm-add-btn:hover{background:var(--ctx-hover)}
11115
+ .rm-toolbar select{padding:5px 8px;border:1px solid var(--input-border);border-radius:var(--radius-sm);font-size:.82em;background:var(--input-bg);color:var(--fg)}
11116
+ .rm-back-btn{padding:6px 14px;border:1px solid var(--input-border);background:var(--panel);color:var(--fg);border-radius:var(--radius-sm);cursor:pointer;font-size:.82em;font-weight:600}
11117
+ .rm-edit-btn{padding:3px 8px;border:1px solid var(--input-border);background:var(--panel);color:var(--accent);border-radius:var(--radius-sm);cursor:pointer;font-size:.75em;white-space:nowrap}
11118
+ .rm-edit-btn:hover{background:var(--ctx-hover)}
11119
+ `;
11120
+ stylesInjected = false;
11121
+ }
11122
+ });
11123
+
10248
11124
  // services/desktop/client/modules/start-menu.ts
10249
11125
  function injectStartMenuDeps(deps) {
10250
11126
  _openApp = deps.openApp;
@@ -10326,6 +11202,13 @@ function renderStartMenu() {
10326
11202
  html += `<div class="app-name">Google Connect</div>`;
10327
11203
  html += `<div class="app-desc">Manage Google account connection</div>`;
10328
11204
  html += `</div></div>`;
11205
+ const rmIcon = useLucide ? lucideIcon(LUCIDE_SYSTEM_ICONS["resource-manager"] || "building", "di-lucide") : "\u{1F3E2}";
11206
+ html += `<div class="start-app-item" data-app-id="__resource_manager" data-cat="google">`;
11207
+ html += `<span class="app-icon">${rmIcon}</span>`;
11208
+ html += `<div class="app-info">`;
11209
+ html += `<div class="app-name">Resource Manager</div>`;
11210
+ html += `<div class="app-desc">Manage buildings and resources</div>`;
11211
+ html += `</div></div>`;
10329
11212
  }
10330
11213
  if (folder.id === "system") {
10331
11214
  const settingsIcon = useLucide ? lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide") : "\u{1F3A8}";
@@ -10406,6 +11289,8 @@ function attachStartMenuListeners() {
10406
11289
  openGoogleDrive();
10407
11290
  } else if (appId === "__google_meet") {
10408
11291
  openGoogleMeet();
11292
+ } else if (appId === "__resource_manager") {
11293
+ openResourceManager();
10409
11294
  } else if (appId === "__uni_editor_picker") {
10410
11295
  void openUniEditorPicker();
10411
11296
  } else if (appId === "__persona_viewer") {
@@ -10430,7 +11315,7 @@ function attachStartMenuListeners() {
10430
11315
  e.preventDefault();
10431
11316
  e.stopPropagation();
10432
11317
  const appId = item.dataset.appId || "";
10433
- const pinnableInternals = ["__terminal", "__file_manager", "__ui-design", "__task_manager", "__log_viewer", "__persona_viewer", "__google_connect", "__google_gmail", "__google_calendar", "__google_drive", "__google_meet"];
11318
+ const pinnableInternals = ["__terminal", "__file_manager", "__ui-design", "__task_manager", "__log_viewer", "__persona_viewer", "__google_connect", "__google_gmail", "__google_calendar", "__google_drive", "__google_meet", "__resource_manager"];
10434
11319
  if (appId.startsWith("__") && !pinnableInternals.includes(appId)) return;
10435
11320
  showAppContextMenu(e.clientX, e.clientY, appId);
10436
11321
  });
@@ -10524,6 +11409,7 @@ var init_start_menu = __esm({
10524
11409
  init_google_calendar();
10525
11410
  init_google_drive();
10526
11411
  init_google_meet();
11412
+ init_resource_manager();
10527
11413
  _openApp = null;
10528
11414
  _openTaskManager = null;
10529
11415
  _openLogViewer = null;
@@ -11200,6 +12086,7 @@ function renderDesktopIcons() {
11200
12086
  if (action === "google-calendar") openGoogleCalendar();
11201
12087
  if (action === "google-drive") openGoogleDrive();
11202
12088
  if (action === "google-meet") openGoogleMeet();
12089
+ if (action === "resource-manager") openResourceManager();
11203
12090
  });
11204
12091
  });
11205
12092
  container.querySelectorAll(".desktop-folder-icon").forEach((folderEl) => {
@@ -11262,7 +12149,8 @@ function openCategoryFolder(folderId) {
11262
12149
  { appId: "__google_gmail", label: "Gmail", lucide: "mail", emoji: "\u{1F4E7}", desc: "Read and send email" },
11263
12150
  { appId: "__google_calendar", label: "Calendar", lucide: "calendar", emoji: "\u{1F4C5}", desc: "View and manage events" },
11264
12151
  { appId: "__google_drive", label: "Google Drive", lucide: "hard-drive", emoji: "\u{1F4BE}", desc: "Browse and search files" },
11265
- { appId: "__google_meet", label: "Google Meet", lucide: "video", emoji: "\u{1F4F9}", desc: "Create and join meetings" }
12152
+ { appId: "__google_meet", label: "Google Meet", lucide: "video", emoji: "\u{1F4F9}", desc: "Create and join meetings" },
12153
+ { appId: "__resource_manager", label: "Resource Manager", lucide: "building", emoji: "\u{1F3E2}", desc: "Manage buildings and resources" }
11266
12154
  ];
11267
12155
  for (const ga of gApps) {
11268
12156
  let gaIcon;
@@ -11357,6 +12245,10 @@ function openCategoryFolder(folderId) {
11357
12245
  openGoogleMeet();
11358
12246
  return;
11359
12247
  }
12248
+ if (appId === "__resource_manager") {
12249
+ openResourceManager();
12250
+ return;
12251
+ }
11360
12252
  const app = findApp(appId);
11361
12253
  if (app) openApp(app);
11362
12254
  });
@@ -11480,6 +12372,7 @@ var init_desktop_icons = __esm({
11480
12372
  init_google_calendar();
11481
12373
  init_google_drive();
11482
12374
  init_google_meet();
12375
+ init_resource_manager();
11483
12376
  }
11484
12377
  });
11485
12378
 
@@ -11552,11 +12445,35 @@ function refreshOpenFolderWindows() {
11552
12445
  html += `<div class="folder-app-label">${esc(app.label)}</div>`;
11553
12446
  html += `</div>`;
11554
12447
  }
12448
+ if (folderId === "google") {
12449
+ const gBg = CATEGORY_COLORS["google"] || "";
12450
+ const gApps = [
12451
+ { appId: "__google_gmail", label: "Gmail", lucide: "mail", emoji: "\u{1F4E7}", desc: "Read and send email" },
12452
+ { appId: "__google_calendar", label: "Calendar", lucide: "calendar", emoji: "\u{1F4C5}", desc: "View and manage events" },
12453
+ { appId: "__google_drive", label: "Google Drive", lucide: "hard-drive", emoji: "\u{1F4BE}", desc: "Browse and search files" },
12454
+ { appId: "__google_meet", label: "Google Meet", lucide: "video", emoji: "\u{1F4F9}", desc: "Create and join meetings" },
12455
+ { appId: "__resource_manager", label: "Resource Manager", lucide: "building", emoji: "\u{1F3E2}", desc: "Manage buildings and resources" }
12456
+ ];
12457
+ for (const ga of gApps) {
12458
+ let gaIcon;
12459
+ if (state.iconStyle === "modern") {
12460
+ gaIcon = `<div class="di-tile di-tile-sm" style="background:${gBg}">${lucideIcon(ga.lucide, "di-lucide")}</div>`;
12461
+ } else if (state.iconStyle === "minimal") {
12462
+ gaIcon = lucideIcon(ga.lucide, "di-lucide");
12463
+ } else {
12464
+ gaIcon = ga.emoji;
12465
+ }
12466
+ html += `<div class="folder-app-item" data-app-id="${ga.appId}" title="${esc(ga.desc)}">`;
12467
+ html += `<div class="folder-app-icon">${gaIcon}</div>`;
12468
+ html += `<div class="folder-app-label">${esc(ga.label)}</div>`;
12469
+ html += `</div>`;
12470
+ }
12471
+ }
11555
12472
  if (folderId === "system") {
12473
+ const sysBg = CATEGORY_COLORS["system"] || "";
11556
12474
  let settingsIcon;
11557
12475
  if (state.iconStyle === "modern") {
11558
- const bg = CATEGORY_COLORS["system"] || "";
11559
- settingsIcon = `<div class="di-tile di-tile-sm" style="background:${bg}">${lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide")}</div>`;
12476
+ settingsIcon = `<div class="di-tile di-tile-sm" style="background:${sysBg}">${lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide")}</div>`;
11560
12477
  } else if (state.iconStyle === "minimal") {
11561
12478
  settingsIcon = lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide");
11562
12479
  } else {
@@ -11566,6 +12483,30 @@ function refreshOpenFolderWindows() {
11566
12483
  html += `<div class="folder-app-icon">${settingsIcon}</div>`;
11567
12484
  html += `<div class="folder-app-label">Settings</div>`;
11568
12485
  html += `</div>`;
12486
+ let tmIcon;
12487
+ if (state.iconStyle === "modern") {
12488
+ tmIcon = `<div class="di-tile di-tile-sm" style="background:${sysBg}">${lucideIcon("activity", "di-lucide")}</div>`;
12489
+ } else if (state.iconStyle === "minimal") {
12490
+ tmIcon = lucideIcon("activity", "di-lucide");
12491
+ } else {
12492
+ tmIcon = "\u{1F4CA}";
12493
+ }
12494
+ html += `<div class="folder-app-item" data-app-id="__task_manager" title="View running jobs &amp; concurrency">`;
12495
+ html += `<div class="folder-app-icon">${tmIcon}</div>`;
12496
+ html += `<div class="folder-app-label">Concurrency Manager</div>`;
12497
+ html += `</div>`;
12498
+ let logIcon;
12499
+ if (state.iconStyle === "modern") {
12500
+ logIcon = `<div class="di-tile di-tile-sm" style="background:${sysBg}">${lucideIcon("scroll-text", "di-lucide")}</div>`;
12501
+ } else if (state.iconStyle === "minimal") {
12502
+ logIcon = lucideIcon("scroll-text", "di-lucide");
12503
+ } else {
12504
+ logIcon = "\u{1F4DC}";
12505
+ }
12506
+ html += `<div class="folder-app-item" data-app-id="__log_viewer" title="View command history &amp; events">`;
12507
+ html += `<div class="folder-app-icon">${logIcon}</div>`;
12508
+ html += `<div class="folder-app-label">Event Log</div>`;
12509
+ html += `</div>`;
11569
12510
  }
11570
12511
  html += "</div></div>";
11571
12512
  body.innerHTML = html;
@@ -11578,6 +12519,34 @@ function refreshOpenFolderWindows() {
11578
12519
  openUiDesign();
11579
12520
  return;
11580
12521
  }
12522
+ if (appId === "__task_manager") {
12523
+ _openTaskManager2?.();
12524
+ return;
12525
+ }
12526
+ if (appId === "__log_viewer") {
12527
+ _openLogViewer2?.();
12528
+ return;
12529
+ }
12530
+ if (appId === "__google_gmail") {
12531
+ openGoogleGmail();
12532
+ return;
12533
+ }
12534
+ if (appId === "__google_calendar") {
12535
+ openGoogleCalendar();
12536
+ return;
12537
+ }
12538
+ if (appId === "__google_drive") {
12539
+ openGoogleDrive();
12540
+ return;
12541
+ }
12542
+ if (appId === "__google_meet") {
12543
+ openGoogleMeet();
12544
+ return;
12545
+ }
12546
+ if (appId === "__resource_manager") {
12547
+ openResourceManager();
12548
+ return;
12549
+ }
11581
12550
  const a = findApp(appId);
11582
12551
  if (a && _openApp2) _openApp2(a);
11583
12552
  });
@@ -11695,6 +12664,20 @@ function showAppContextMenu(x, y, appId) {
11695
12664
  if (_openTaskManager2) _openTaskManager2();
11696
12665
  } else if (aid === "__log_viewer") {
11697
12666
  if (_openLogViewer2) _openLogViewer2();
12667
+ } else if (aid === "__google_gmail") {
12668
+ openGoogleGmail();
12669
+ } else if (aid === "__google_calendar") {
12670
+ openGoogleCalendar();
12671
+ } else if (aid === "__google_drive") {
12672
+ openGoogleDrive();
12673
+ } else if (aid === "__google_meet") {
12674
+ openGoogleMeet();
12675
+ } else if (aid === "__resource_manager") {
12676
+ openResourceManager();
12677
+ } else if (aid === "__google_connect") {
12678
+ openGoogleConnect();
12679
+ } else if (aid === "__persona_viewer") {
12680
+ void openPersonaViewer();
11698
12681
  } else {
11699
12682
  const a = findApp(aid);
11700
12683
  if (a && _openApp2) _openApp2(a);
@@ -12030,6 +13013,13 @@ var init_context_menu = __esm({
12030
13013
  init_taskbar();
12031
13014
  init_desktop_icons();
12032
13015
  init_ui_design();
13016
+ init_google_gmail();
13017
+ init_google_calendar();
13018
+ init_google_drive();
13019
+ init_google_meet();
13020
+ init_resource_manager();
13021
+ init_google_connect();
13022
+ init_persona_viewer();
12033
13023
  init_state();
12034
13024
  _openApp2 = null;
12035
13025
  _toggleTheme = null;