@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.
- package/README.md +35 -2
- package/dist/cli.cjs +4739 -1162
- package/dist/desktop-client.js +1017 -27
- package/dist/ext.cjs +1 -1
- package/dist/ext.d.cts +1 -1
- package/origin/index.meta.json +1 -1
- package/package.json +1 -1
package/dist/desktop-client.js
CHANGED
|
@@ -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
|
|
8408
|
-
html += `<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
|
-
|
|
8442
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
8498
|
-
html += `<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
|
-
|
|
8530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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-
|
|
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
|
-
|
|
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 & 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 & 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;
|