@bonginkan/maria-lite 6.3.0 → 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
@@ -457,6 +458,7 @@ var init_helpers = __esm({
457
458
  gcal: "calendar",
458
459
  gdrive: "hard-drive",
459
460
  gmeet: "video",
461
+ "resource-manager": "building",
460
462
  // System
461
463
  help: "life-buoy",
462
464
  version: "info",
@@ -473,7 +475,8 @@ var init_helpers = __esm({
473
475
  __ui_design: "paintbrush",
474
476
  __file_manager: "folder-open",
475
477
  __task_manager: "activity",
476
- __log_viewer: "scroll-text"
478
+ __log_viewer: "scroll-text",
479
+ __resource_manager: "building"
477
480
  };
478
481
  LUCIDE_FILE_EXT_ICONS = {
479
482
  ts: "file-code",
@@ -701,7 +704,7 @@ var init_state = __esm({
701
704
  label: "Google",
702
705
  emoji: "\u{1F310}",
703
706
  serverCategories: ["google"],
704
- commandIds: ["gmail", "gcal", "gdrive", "gmeet"],
707
+ commandIds: ["gmail", "gcal", "gdrive", "gmeet", "resource-manager"],
705
708
  alwaysVisible: true
706
709
  },
707
710
  {
@@ -8072,6 +8075,10 @@ function openGoogleCalendar() {
8072
8075
  calCalendars = [];
8073
8076
  calEnabledCalendarIds = /* @__PURE__ */ new Set();
8074
8077
  calShowCalendarPanel = false;
8078
+ calFindBuildings = [];
8079
+ calFindResources = [];
8080
+ calFindBuildingId = "";
8081
+ calFindResourceId = "";
8075
8082
  renderApp2();
8076
8083
  }
8077
8084
  function renderApp2() {
@@ -8631,6 +8638,18 @@ async function handleEdit(container, ev) {
8631
8638
  btn.textContent = "Save Changes";
8632
8639
  }
8633
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
+ }
8634
8653
  function renderFindTime(container) {
8635
8654
  const tomorrow = /* @__PURE__ */ new Date();
8636
8655
  tomorrow.setDate(tomorrow.getDate() + 1);
@@ -8644,6 +8663,11 @@ function renderFindTime(container) {
8644
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>`;
8645
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>`;
8646
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">`;
8647
8671
  html += `<label>Duration<select class="gc-ft-duration">`;
8648
8672
  for (const m of [15, 30, 45, 60, 90, 120, 180, 240]) {
8649
8673
  const label = m < 60 ? `${m} min` : m === 60 ? "1 hour" : `${m / 60} hours`;
@@ -8677,6 +8701,42 @@ function renderFindTime(container) {
8677
8701
  html += `<div class="gc-ft-results"></div>`;
8678
8702
  html += `</div>`;
8679
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
+ }
8680
8740
  container.querySelector(".gc-ft-search-btn").addEventListener("click", () => void handleFindSlots(container, 0));
8681
8741
  }
8682
8742
  async function handleFindSlots(container, offset = 0) {
@@ -8693,7 +8753,8 @@ async function handleFindSlots(container, offset = 0) {
8693
8753
  const btn = container.querySelector(".gc-ft-search-btn");
8694
8754
  const required = reqEmails ? reqEmails.split(",").map((e) => e.trim()).filter(Boolean) : [];
8695
8755
  const optional = optEmails ? optEmails.split(",").map((e) => e.trim()).filter(Boolean) : [];
8696
- if (!required.length) {
8756
+ const useResourceApi = !!(calFindBuildingId || calFindResourceId);
8757
+ if (!useResourceApi && !required.length) {
8697
8758
  statusEl.innerHTML = `<span class="gc-status-err">At least one required attendee is needed</span>`;
8698
8759
  return;
8699
8760
  }
@@ -8703,10 +8764,27 @@ async function handleFindSlots(container, offset = 0) {
8703
8764
  }
8704
8765
  btn.disabled = true;
8705
8766
  btn.textContent = "Searching...";
8706
- 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>`;
8707
8769
  resultsEl.innerHTML = "";
8708
8770
  try {
8709
- 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", {
8710
8788
  method: "POST",
8711
8789
  headers: { "Content-Type": "application/json" },
8712
8790
  body: JSON.stringify({
@@ -8761,6 +8839,13 @@ async function handleFindSlots(container, offset = 0) {
8761
8839
  }
8762
8840
  }
8763
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
+ }
8764
8849
  rhtml += `<button class="gc-ft-slot-create">Create Event</button>`;
8765
8850
  rhtml += `</div>`;
8766
8851
  }
@@ -8870,7 +8955,7 @@ function formatDateTime(dateStr) {
8870
8955
  return dateStr;
8871
8956
  }
8872
8957
  }
8873
- 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;
8874
8959
  var init_google_calendar = __esm({
8875
8960
  "services/desktop/client/modules/google-calendar.ts"() {
8876
8961
  init_helpers();
@@ -8886,6 +8971,10 @@ var init_google_calendar = __esm({
8886
8971
  calEnabledCalendarIds = /* @__PURE__ */ new Set();
8887
8972
  calShowCalendarPanel = false;
8888
8973
  calPrefill = null;
8974
+ calFindBuildings = [];
8975
+ calFindResources = [];
8976
+ calFindBuildingId = "";
8977
+ calFindResourceId = "";
8889
8978
  STYLES2 = `
8890
8979
  .gc-app{font-family:var(--sans);color:var(--fg);display:flex;flex-direction:column;height:100%;overflow:hidden}
8891
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}
@@ -10322,6 +10411,716 @@ var init_google_meet = __esm({
10322
10411
  }
10323
10412
  });
10324
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
+
10325
11124
  // services/desktop/client/modules/start-menu.ts
10326
11125
  function injectStartMenuDeps(deps) {
10327
11126
  _openApp = deps.openApp;
@@ -10403,6 +11202,13 @@ function renderStartMenu() {
10403
11202
  html += `<div class="app-name">Google Connect</div>`;
10404
11203
  html += `<div class="app-desc">Manage Google account connection</div>`;
10405
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>`;
10406
11212
  }
10407
11213
  if (folder.id === "system") {
10408
11214
  const settingsIcon = useLucide ? lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide") : "\u{1F3A8}";
@@ -10483,6 +11289,8 @@ function attachStartMenuListeners() {
10483
11289
  openGoogleDrive();
10484
11290
  } else if (appId === "__google_meet") {
10485
11291
  openGoogleMeet();
11292
+ } else if (appId === "__resource_manager") {
11293
+ openResourceManager();
10486
11294
  } else if (appId === "__uni_editor_picker") {
10487
11295
  void openUniEditorPicker();
10488
11296
  } else if (appId === "__persona_viewer") {
@@ -10507,7 +11315,7 @@ function attachStartMenuListeners() {
10507
11315
  e.preventDefault();
10508
11316
  e.stopPropagation();
10509
11317
  const appId = item.dataset.appId || "";
10510
- 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"];
10511
11319
  if (appId.startsWith("__") && !pinnableInternals.includes(appId)) return;
10512
11320
  showAppContextMenu(e.clientX, e.clientY, appId);
10513
11321
  });
@@ -10601,6 +11409,7 @@ var init_start_menu = __esm({
10601
11409
  init_google_calendar();
10602
11410
  init_google_drive();
10603
11411
  init_google_meet();
11412
+ init_resource_manager();
10604
11413
  _openApp = null;
10605
11414
  _openTaskManager = null;
10606
11415
  _openLogViewer = null;
@@ -11277,6 +12086,7 @@ function renderDesktopIcons() {
11277
12086
  if (action === "google-calendar") openGoogleCalendar();
11278
12087
  if (action === "google-drive") openGoogleDrive();
11279
12088
  if (action === "google-meet") openGoogleMeet();
12089
+ if (action === "resource-manager") openResourceManager();
11280
12090
  });
11281
12091
  });
11282
12092
  container.querySelectorAll(".desktop-folder-icon").forEach((folderEl) => {
@@ -11339,7 +12149,8 @@ function openCategoryFolder(folderId) {
11339
12149
  { appId: "__google_gmail", label: "Gmail", lucide: "mail", emoji: "\u{1F4E7}", desc: "Read and send email" },
11340
12150
  { appId: "__google_calendar", label: "Calendar", lucide: "calendar", emoji: "\u{1F4C5}", desc: "View and manage events" },
11341
12151
  { appId: "__google_drive", label: "Google Drive", lucide: "hard-drive", emoji: "\u{1F4BE}", desc: "Browse and search files" },
11342
- { 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" }
11343
12154
  ];
11344
12155
  for (const ga of gApps) {
11345
12156
  let gaIcon;
@@ -11434,6 +12245,10 @@ function openCategoryFolder(folderId) {
11434
12245
  openGoogleMeet();
11435
12246
  return;
11436
12247
  }
12248
+ if (appId === "__resource_manager") {
12249
+ openResourceManager();
12250
+ return;
12251
+ }
11437
12252
  const app = findApp(appId);
11438
12253
  if (app) openApp(app);
11439
12254
  });
@@ -11557,6 +12372,7 @@ var init_desktop_icons = __esm({
11557
12372
  init_google_calendar();
11558
12373
  init_google_drive();
11559
12374
  init_google_meet();
12375
+ init_resource_manager();
11560
12376
  }
11561
12377
  });
11562
12378
 
@@ -11629,11 +12445,35 @@ function refreshOpenFolderWindows() {
11629
12445
  html += `<div class="folder-app-label">${esc(app.label)}</div>`;
11630
12446
  html += `</div>`;
11631
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
+ }
11632
12472
  if (folderId === "system") {
12473
+ const sysBg = CATEGORY_COLORS["system"] || "";
11633
12474
  let settingsIcon;
11634
12475
  if (state.iconStyle === "modern") {
11635
- const bg = CATEGORY_COLORS["system"] || "";
11636
- 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>`;
11637
12477
  } else if (state.iconStyle === "minimal") {
11638
12478
  settingsIcon = lucideIcon(LUCIDE_SYSTEM_ICONS["ui-design"] || "paintbrush", "di-lucide");
11639
12479
  } else {
@@ -11643,6 +12483,30 @@ function refreshOpenFolderWindows() {
11643
12483
  html += `<div class="folder-app-icon">${settingsIcon}</div>`;
11644
12484
  html += `<div class="folder-app-label">Settings</div>`;
11645
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>`;
11646
12510
  }
11647
12511
  html += "</div></div>";
11648
12512
  body.innerHTML = html;
@@ -11655,6 +12519,34 @@ function refreshOpenFolderWindows() {
11655
12519
  openUiDesign();
11656
12520
  return;
11657
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
+ }
11658
12550
  const a = findApp(appId);
11659
12551
  if (a && _openApp2) _openApp2(a);
11660
12552
  });
@@ -11772,6 +12664,20 @@ function showAppContextMenu(x, y, appId) {
11772
12664
  if (_openTaskManager2) _openTaskManager2();
11773
12665
  } else if (aid === "__log_viewer") {
11774
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();
11775
12681
  } else {
11776
12682
  const a = findApp(aid);
11777
12683
  if (a && _openApp2) _openApp2(a);
@@ -12107,6 +13013,13 @@ var init_context_menu = __esm({
12107
13013
  init_taskbar();
12108
13014
  init_desktop_icons();
12109
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();
12110
13023
  init_state();
12111
13024
  _openApp2 = null;
12112
13025
  _toggleTheme = null;