@open-mercato/ui 0.5.1-develop.2975.ccbadc8198 → 0.5.1-develop.2996.ce62fd491c
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/.turbo/turbo-build.log +1 -1
- package/dist/backend/AppShell.js +274 -697
- package/dist/backend/AppShell.js.map +3 -3
- package/dist/backend/CrudForm.js +1 -1
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/crud/CollapsibleZoneLayout.js +23 -3
- package/dist/backend/crud/CollapsibleZoneLayout.js.map +2 -2
- package/dist/backend/section-page/SectionNav.js +10 -8
- package/dist/backend/section-page/SectionNav.js.map +2 -2
- package/dist/backend/section-page/SectionPage.js +2 -2
- package/dist/backend/section-page/SectionPage.js.map +2 -2
- package/dist/backend/sidebar/SidebarCustomizationEditor.js +1303 -0
- package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +7 -0
- package/dist/backend/sidebar/customization-helpers.js +150 -0
- package/dist/backend/sidebar/customization-helpers.js.map +7 -0
- package/dist/primitives/switch.js +1 -2
- package/dist/primitives/switch.js.map +2 -2
- package/jest.setup.ts +13 -0
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +245 -732
- package/src/backend/CrudForm.tsx +1 -1
- package/src/backend/__tests__/AppShell.test.tsx +1 -1
- package/src/backend/__tests__/CollapsibleZoneLayout.test.tsx +101 -0
- package/src/backend/__tests__/CrudForm.navigation.test.tsx +42 -0
- package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +200 -0
- package/src/backend/crud/CollapsibleZoneLayout.tsx +28 -3
- package/src/backend/section-page/SectionNav.tsx +14 -10
- package/src/backend/section-page/SectionPage.tsx +15 -10
- package/src/backend/sidebar/SidebarCustomizationEditor.tsx +1562 -0
- package/src/backend/sidebar/customization-helpers.ts +203 -0
- package/src/primitives/switch.tsx +1 -2
package/dist/backend/AppShell.js
CHANGED
|
@@ -4,20 +4,20 @@ import * as React from "react";
|
|
|
4
4
|
import { createContext, useContext } from "react";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import Image from "next/image";
|
|
7
|
-
import {
|
|
7
|
+
import { ChevronDown, Search, X } from "lucide-react";
|
|
8
8
|
import { Button } from "../primitives/button.js";
|
|
9
9
|
import { IconButton } from "../primitives/icon-button.js";
|
|
10
|
-
import {
|
|
10
|
+
import { Input } from "../primitives/input.js";
|
|
11
11
|
import { FlashMessages } from "./FlashMessages.js";
|
|
12
12
|
import { QueryProvider } from "../theme/QueryProvider.js";
|
|
13
13
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
14
|
-
import { apiCall } from "./utils/apiCall.js";
|
|
15
14
|
import { LastOperationBanner } from "./operations/LastOperationBanner.js";
|
|
16
15
|
import { ProgressTopBar } from "./progress/ProgressTopBar.js";
|
|
17
16
|
import { UpgradeActionBanner } from "./upgrades/UpgradeActionBanner.js";
|
|
18
17
|
import { PartialIndexBanner } from "./indexes/PartialIndexBanner.js";
|
|
19
18
|
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
20
19
|
import { slugifySidebarId } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
|
|
20
|
+
import { cloneSidebarGroups } from "./sidebar/customization-helpers.js";
|
|
21
21
|
import { InjectionSpot } from "./injection/InjectionSpot.js";
|
|
22
22
|
import { LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID } from "./injection/mutationEvents.js";
|
|
23
23
|
import { mergeMenuItems } from "./injection/mergeMenuItems.js";
|
|
@@ -266,7 +266,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
266
266
|
const locale = useLocale();
|
|
267
267
|
const { payload: chromePayload, isReady: isChromeReady, isLoading: isChromeLoading } = useBackendChrome();
|
|
268
268
|
const resolvedGroups = React.useMemo(
|
|
269
|
-
() =>
|
|
269
|
+
() => cloneSidebarGroups(chromePayload?.groups ?? groups),
|
|
270
270
|
[chromePayload?.groups, groups]
|
|
271
271
|
);
|
|
272
272
|
const resolvedSettingsSections = chromePayload?.settingsSections ?? settingsSections;
|
|
@@ -285,19 +285,44 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
285
285
|
const [openGroups, setOpenGroups] = React.useState(
|
|
286
286
|
() => Object.fromEntries(resolvedGroups.map((g) => [resolveGroupKey(g), true]))
|
|
287
287
|
);
|
|
288
|
-
const [customizing, setCustomizing] = React.useState(false);
|
|
289
|
-
const [customDraft, setCustomDraft] = React.useState(null);
|
|
290
|
-
const [loadingPreferences, setLoadingPreferences] = React.useState(false);
|
|
291
|
-
const [savingPreferences, setSavingPreferences] = React.useState(false);
|
|
292
|
-
const [customizationError, setCustomizationError] = React.useState(null);
|
|
293
|
-
const [availableRoleTargets, setAvailableRoleTargets] = React.useState([]);
|
|
294
|
-
const [selectedRoleIds, setSelectedRoleIds] = React.useState([]);
|
|
295
|
-
const [canApplyToRoles, setCanApplyToRoles] = React.useState(false);
|
|
296
|
-
const originalNavRef = React.useRef(null);
|
|
297
288
|
const [headerTitle, setHeaderTitle] = React.useState(currentTitle);
|
|
298
289
|
const [headerBreadcrumb, setHeaderBreadcrumb] = React.useState(breadcrumb);
|
|
299
|
-
const
|
|
300
|
-
const
|
|
290
|
+
const [navQuery, setNavQuery] = React.useState("");
|
|
291
|
+
const navQueryNorm = navQuery.trim().toLowerCase();
|
|
292
|
+
const navQueryActive = navQueryNorm.length > 0;
|
|
293
|
+
const matchesQuery = React.useCallback((label) => {
|
|
294
|
+
if (!navQueryActive) return true;
|
|
295
|
+
if (!label) return false;
|
|
296
|
+
return label.toLowerCase().includes(navQueryNorm);
|
|
297
|
+
}, [navQueryActive, navQueryNorm]);
|
|
298
|
+
const effectiveCollapsed = collapsed;
|
|
299
|
+
const expandedSidebarWidth = "240px";
|
|
300
|
+
const sidebarAsideRef = React.useRef(null);
|
|
301
|
+
const [sidebarScrollState, setSidebarScrollState] = React.useState("down");
|
|
302
|
+
React.useEffect(() => {
|
|
303
|
+
const aside = sidebarAsideRef.current;
|
|
304
|
+
if (!aside) return;
|
|
305
|
+
const target = aside.querySelector('[data-sidebar-scroll="true"]');
|
|
306
|
+
if (!target) return;
|
|
307
|
+
const update = () => {
|
|
308
|
+
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
309
|
+
const canScroll = scrollHeight > clientHeight + 1;
|
|
310
|
+
if (!canScroll) {
|
|
311
|
+
setSidebarScrollState("none");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const atBottom = scrollTop + clientHeight >= scrollHeight - 8;
|
|
315
|
+
setSidebarScrollState(atBottom ? "up" : "down");
|
|
316
|
+
};
|
|
317
|
+
update();
|
|
318
|
+
target.addEventListener("scroll", update, { passive: true });
|
|
319
|
+
const ro = new ResizeObserver(update);
|
|
320
|
+
ro.observe(target);
|
|
321
|
+
return () => {
|
|
322
|
+
target.removeEventListener("scroll", update);
|
|
323
|
+
ro.disconnect();
|
|
324
|
+
};
|
|
325
|
+
}, [pathname, effectiveCollapsed]);
|
|
301
326
|
const injectionContext = React.useMemo(
|
|
302
327
|
() => ({
|
|
303
328
|
path: pathname ?? "",
|
|
@@ -346,226 +371,8 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
346
371
|
}
|
|
347
372
|
}, [resolvedGroups]);
|
|
348
373
|
const toggleGroup = (groupId) => setOpenGroups((prev) => ({ ...prev, [groupId]: prev[groupId] === false }));
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
if (!prev) return prev;
|
|
352
|
-
const next = updater(prev);
|
|
353
|
-
if (originalNavRef.current) {
|
|
354
|
-
setNavGroups(applyCustomizationDraft(originalNavRef.current, next));
|
|
355
|
-
}
|
|
356
|
-
return next;
|
|
357
|
-
});
|
|
358
|
-
}, []);
|
|
359
|
-
const startCustomization = React.useCallback(async () => {
|
|
360
|
-
if (customizing || loadingPreferences) return;
|
|
361
|
-
setCustomizationError(null);
|
|
362
|
-
setLoadingPreferences(true);
|
|
363
|
-
try {
|
|
364
|
-
const baseSnapshot = filterMainSidebarGroups(AppShell.cloneGroups(navGroups));
|
|
365
|
-
const call = await apiCall("/api/auth/sidebar/preferences");
|
|
366
|
-
const data = call.ok ? call.result ?? null : null;
|
|
367
|
-
const rawSettings = data?.settings;
|
|
368
|
-
const responseOrder = Array.isArray(rawSettings?.groupOrder) ? rawSettings.groupOrder.map((id) => typeof id === "string" ? id.trim() : "").filter((id) => id.length > 0) : [];
|
|
369
|
-
const responseGroupLabels = {};
|
|
370
|
-
if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === "object") {
|
|
371
|
-
for (const [key, value] of Object.entries(rawSettings.groupLabels)) {
|
|
372
|
-
if (typeof value !== "string") continue;
|
|
373
|
-
const trimmedKey = key.trim();
|
|
374
|
-
if (!trimmedKey) continue;
|
|
375
|
-
responseGroupLabels[trimmedKey] = value;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
const responseItemLabels = {};
|
|
379
|
-
if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === "object") {
|
|
380
|
-
for (const [key, value] of Object.entries(rawSettings.itemLabels)) {
|
|
381
|
-
if (typeof value !== "string") continue;
|
|
382
|
-
const trimmedKey = key.trim();
|
|
383
|
-
if (!trimmedKey) continue;
|
|
384
|
-
responseItemLabels[trimmedKey] = value;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems) ? rawSettings.hiddenItems.map((itemId) => typeof itemId === "string" ? itemId.trim() : "").filter((itemId) => itemId.length > 0) : [];
|
|
388
|
-
const canManageRoles = data?.canApplyToRoles === true;
|
|
389
|
-
setCanApplyToRoles(canManageRoles);
|
|
390
|
-
if (canManageRoles) {
|
|
391
|
-
const roles = Array.isArray(data?.roles) ? data.roles.filter((role) => typeof role?.id === "string" && typeof role?.name === "string") : [];
|
|
392
|
-
const mappedRoles = roles.map((role) => ({
|
|
393
|
-
id: role.id,
|
|
394
|
-
name: role.name,
|
|
395
|
-
hasPreference: role.hasPreference === true
|
|
396
|
-
}));
|
|
397
|
-
setAvailableRoleTargets(mappedRoles);
|
|
398
|
-
setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id));
|
|
399
|
-
} else {
|
|
400
|
-
setAvailableRoleTargets([]);
|
|
401
|
-
setSelectedRoleIds([]);
|
|
402
|
-
}
|
|
403
|
-
const currentIds = baseSnapshot.map((group) => resolveGroupKey(group));
|
|
404
|
-
const order = mergeGroupOrder(responseOrder, currentIds);
|
|
405
|
-
const { itemDefaults } = collectSidebarDefaults(baseSnapshot);
|
|
406
|
-
const hiddenItemIds = {};
|
|
407
|
-
for (const itemId of responseHiddenItems) {
|
|
408
|
-
if (!itemDefaults.has(itemId)) continue;
|
|
409
|
-
hiddenItemIds[itemId] = true;
|
|
410
|
-
}
|
|
411
|
-
const draft = {
|
|
412
|
-
order,
|
|
413
|
-
groupLabels: { ...responseGroupLabels },
|
|
414
|
-
itemLabels: { ...responseItemLabels },
|
|
415
|
-
hiddenItemIds
|
|
416
|
-
};
|
|
417
|
-
originalNavRef.current = baseSnapshot;
|
|
418
|
-
setCustomDraft(draft);
|
|
419
|
-
setNavGroups(applyCustomizationDraft(baseSnapshot, draft));
|
|
420
|
-
setCustomizing(true);
|
|
421
|
-
} catch (error) {
|
|
422
|
-
console.error("Failed to load sidebar preferences", error);
|
|
423
|
-
setCustomizationError(t("appShell.sidebarCustomizationLoadError"));
|
|
424
|
-
} finally {
|
|
425
|
-
setLoadingPreferences(false);
|
|
426
|
-
}
|
|
427
|
-
}, [customizing, loadingPreferences, navGroups, t]);
|
|
428
|
-
const cancelCustomization = React.useCallback(() => {
|
|
429
|
-
setCustomizing(false);
|
|
430
|
-
setCustomDraft(null);
|
|
431
|
-
setCustomizationError(null);
|
|
432
|
-
setAvailableRoleTargets([]);
|
|
433
|
-
setSelectedRoleIds([]);
|
|
434
|
-
setCanApplyToRoles(false);
|
|
435
|
-
if (originalNavRef.current) {
|
|
436
|
-
setNavGroups(AppShell.cloneGroups(originalNavRef.current));
|
|
437
|
-
}
|
|
438
|
-
originalNavRef.current = null;
|
|
439
|
-
}, []);
|
|
440
|
-
const resetCustomization = React.useCallback(() => {
|
|
441
|
-
if (!originalNavRef.current) return;
|
|
442
|
-
const base = AppShell.cloneGroups(originalNavRef.current);
|
|
443
|
-
const order = base.map((group) => resolveGroupKey(group));
|
|
444
|
-
const draft = { order, groupLabels: {}, itemLabels: {}, hiddenItemIds: {} };
|
|
445
|
-
originalNavRef.current = base;
|
|
446
|
-
setCustomDraft(draft);
|
|
447
|
-
setNavGroups(applyCustomizationDraft(base, draft));
|
|
448
|
-
if (canApplyToRoles) {
|
|
449
|
-
setSelectedRoleIds(availableRoleTargets.filter((role) => role.hasPreference).map((role) => role.id));
|
|
450
|
-
}
|
|
451
|
-
}, [availableRoleTargets, canApplyToRoles]);
|
|
452
|
-
const saveCustomization = React.useCallback(async () => {
|
|
453
|
-
if (!customDraft) return;
|
|
454
|
-
setSavingPreferences(true);
|
|
455
|
-
setCustomizationError(null);
|
|
456
|
-
try {
|
|
457
|
-
const baseGroups = originalNavRef.current ?? filterMainSidebarGroups(AppShell.cloneGroups(navGroups));
|
|
458
|
-
const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups);
|
|
459
|
-
const sanitizedGroupLabels = {};
|
|
460
|
-
for (const [key, value] of Object.entries(customDraft.groupLabels)) {
|
|
461
|
-
const trimmed = value.trim();
|
|
462
|
-
const base = groupDefaults.get(key);
|
|
463
|
-
if (!trimmed || !base) continue;
|
|
464
|
-
if (trimmed !== base) sanitizedGroupLabels[key] = trimmed;
|
|
465
|
-
}
|
|
466
|
-
const sanitizedItemLabels = {};
|
|
467
|
-
for (const [itemId, value] of Object.entries(customDraft.itemLabels)) {
|
|
468
|
-
const trimmed = value.trim();
|
|
469
|
-
const base = itemDefaults.get(itemId);
|
|
470
|
-
if (!trimmed || !base) continue;
|
|
471
|
-
if (trimmed !== base) sanitizedItemLabels[itemId] = trimmed;
|
|
472
|
-
}
|
|
473
|
-
const sanitizedHiddenItems = [];
|
|
474
|
-
for (const [itemId, hidden] of Object.entries(customDraft.hiddenItemIds)) {
|
|
475
|
-
if (!hidden) continue;
|
|
476
|
-
if (!itemDefaults.has(itemId)) continue;
|
|
477
|
-
sanitizedHiddenItems.push(itemId);
|
|
478
|
-
}
|
|
479
|
-
const applyToRolesPayload = canApplyToRoles ? [...selectedRoleIds] : [];
|
|
480
|
-
const clearRoleIdsPayload = canApplyToRoles ? availableRoleTargets.filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id)).map((role) => role.id) : [];
|
|
481
|
-
const payload = {
|
|
482
|
-
groupOrder: customDraft.order,
|
|
483
|
-
groupLabels: sanitizedGroupLabels,
|
|
484
|
-
itemLabels: sanitizedItemLabels,
|
|
485
|
-
hiddenItems: sanitizedHiddenItems
|
|
486
|
-
};
|
|
487
|
-
if (canApplyToRoles) {
|
|
488
|
-
payload.applyToRoles = applyToRolesPayload;
|
|
489
|
-
payload.clearRoleIds = clearRoleIdsPayload;
|
|
490
|
-
}
|
|
491
|
-
const call = await apiCall("/api/auth/sidebar/preferences", {
|
|
492
|
-
method: "PUT",
|
|
493
|
-
headers: { "content-type": "application/json" },
|
|
494
|
-
body: JSON.stringify(payload)
|
|
495
|
-
});
|
|
496
|
-
if (!call.ok) {
|
|
497
|
-
setCustomizationError(t("appShell.sidebarCustomizationSaveError"));
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
const data = call.result ?? null;
|
|
501
|
-
if (data?.canApplyToRoles !== void 0) {
|
|
502
|
-
setCanApplyToRoles(data.canApplyToRoles === true);
|
|
503
|
-
}
|
|
504
|
-
if (Array.isArray(data?.roles)) {
|
|
505
|
-
const mappedRoles = data.roles.filter((role) => typeof role?.id === "string" && typeof role?.name === "string").map((role) => ({
|
|
506
|
-
id: role.id,
|
|
507
|
-
name: role.name,
|
|
508
|
-
hasPreference: role.hasPreference === true
|
|
509
|
-
}));
|
|
510
|
-
setAvailableRoleTargets(mappedRoles);
|
|
511
|
-
setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id));
|
|
512
|
-
}
|
|
513
|
-
originalNavRef.current = applyCustomizationDraft(baseGroups, customDraft);
|
|
514
|
-
setNavGroups(AppShell.cloneGroups(originalNavRef.current));
|
|
515
|
-
setCustomizing(false);
|
|
516
|
-
setCustomDraft(null);
|
|
517
|
-
try {
|
|
518
|
-
window.dispatchEvent(new Event("om:refresh-sidebar"));
|
|
519
|
-
} catch {
|
|
520
|
-
}
|
|
521
|
-
} catch (error) {
|
|
522
|
-
console.error("Failed to save sidebar preferences", error);
|
|
523
|
-
setCustomizationError(t("appShell.sidebarCustomizationSaveError"));
|
|
524
|
-
} finally {
|
|
525
|
-
setSavingPreferences(false);
|
|
526
|
-
}
|
|
527
|
-
}, [customDraft, navGroups, t]);
|
|
528
|
-
const moveGroup = React.useCallback((groupId, offset) => {
|
|
529
|
-
updateDraft((draft) => {
|
|
530
|
-
const order = [...draft.order];
|
|
531
|
-
const index = order.indexOf(groupId);
|
|
532
|
-
if (index === -1) return draft;
|
|
533
|
-
const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset));
|
|
534
|
-
if (nextIndex === index) return draft;
|
|
535
|
-
order.splice(index, 1);
|
|
536
|
-
order.splice(nextIndex, 0, groupId);
|
|
537
|
-
return { ...draft, order };
|
|
538
|
-
});
|
|
539
|
-
}, [updateDraft]);
|
|
540
|
-
const setGroupLabel = React.useCallback((groupId, value) => {
|
|
541
|
-
updateDraft((draft) => {
|
|
542
|
-
const next = { ...draft.groupLabels };
|
|
543
|
-
if (value.trim().length === 0) delete next[groupId];
|
|
544
|
-
else next[groupId] = value;
|
|
545
|
-
return { ...draft, groupLabels: next };
|
|
546
|
-
});
|
|
547
|
-
}, [updateDraft]);
|
|
548
|
-
const setItemLabel = React.useCallback((itemId, value) => {
|
|
549
|
-
updateDraft((draft) => {
|
|
550
|
-
const next = { ...draft.itemLabels };
|
|
551
|
-
if (value.trim().length === 0) delete next[itemId];
|
|
552
|
-
else next[itemId] = value;
|
|
553
|
-
return { ...draft, itemLabels: next };
|
|
554
|
-
});
|
|
555
|
-
}, [updateDraft]);
|
|
556
|
-
const setItemHidden = React.useCallback((itemId, hidden) => {
|
|
557
|
-
updateDraft((draft) => {
|
|
558
|
-
const next = { ...draft.hiddenItemIds };
|
|
559
|
-
if (hidden) next[itemId] = true;
|
|
560
|
-
else delete next[itemId];
|
|
561
|
-
return { ...draft, hiddenItemIds: next };
|
|
562
|
-
});
|
|
563
|
-
}, [updateDraft]);
|
|
564
|
-
const toggleRoleSelection = React.useCallback((roleId) => {
|
|
565
|
-
setSelectedRoleIds((prev) => prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]);
|
|
566
|
-
}, []);
|
|
567
|
-
const asideWidth = effectiveCollapsed ? "72px" : expandedSidebarWidth;
|
|
568
|
-
const asideClassesBase = `border-r bg-background/80 py-4 min-h-svh`;
|
|
374
|
+
const asideWidth = effectiveCollapsed ? "80px" : expandedSidebarWidth;
|
|
375
|
+
const asideClassesBase = `border-r bg-background py-4`;
|
|
569
376
|
React.useEffect(() => {
|
|
570
377
|
try {
|
|
571
378
|
localStorage.setItem("om:sidebarCollapsed", collapsed ? "1" : "0");
|
|
@@ -601,107 +408,138 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
601
408
|
}
|
|
602
409
|
}, [pathname]);
|
|
603
410
|
React.useEffect(() => {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
setNavGroups(applyCustomizationDraft(originalNavRef.current, customDraft));
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
setNavGroups(AppShell.cloneGroups(resolvedGroups));
|
|
610
|
-
}, [resolvedGroups, customizing, customDraft]);
|
|
411
|
+
setNavGroups(cloneSidebarGroups(resolvedGroups));
|
|
412
|
+
}, [resolvedGroups]);
|
|
611
413
|
function renderSectionSidebar(sections, title, compact, hideHeader) {
|
|
612
414
|
const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
613
415
|
const lastVisibleIndex = sortedSections.length - 1;
|
|
614
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col
|
|
615
|
-
!hideHeader && /* @__PURE__ */ jsx("div", { className:
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
416
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-3", children: [
|
|
417
|
+
!hideHeader && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxs(
|
|
418
|
+
Link,
|
|
419
|
+
{
|
|
420
|
+
href: "/backend",
|
|
421
|
+
className: `flex items-center gap-3 rounded-xl transition-colors hover:bg-muted ${compact ? "p-2 justify-center" : "p-3"}`,
|
|
422
|
+
"aria-label": t("appShell.goToDashboard"),
|
|
423
|
+
children: [
|
|
424
|
+
/* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
|
|
425
|
+
!compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
) }),
|
|
429
|
+
!compact && /* @__PURE__ */ jsx(
|
|
430
|
+
Input,
|
|
431
|
+
{
|
|
432
|
+
type: "text",
|
|
433
|
+
value: navQuery,
|
|
434
|
+
onChange: (e) => setNavQuery(e.target.value),
|
|
435
|
+
placeholder: t("appShell.searchNavPlaceholder", "Search..."),
|
|
436
|
+
"aria-label": t("appShell.searchNavAria", "Search navigation"),
|
|
437
|
+
leftIcon: /* @__PURE__ */ jsx(Search, { "aria-hidden": true }),
|
|
438
|
+
rightIcon: navQueryActive ? /* @__PURE__ */ jsx(
|
|
439
|
+
IconButton,
|
|
440
|
+
{
|
|
441
|
+
type: "button",
|
|
442
|
+
variant: "ghost",
|
|
443
|
+
size: "xs",
|
|
444
|
+
onClick: () => setNavQuery(""),
|
|
445
|
+
"aria-label": t("appShell.searchNavClear", "Clear search"),
|
|
446
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5", "aria-hidden": true })
|
|
447
|
+
}
|
|
448
|
+
) : void 0,
|
|
449
|
+
className: "mb-2"
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
/* @__PURE__ */ jsx("div", { "data-sidebar-scroll": "true", className: `flex flex-1 flex-col gap-3 overflow-y-auto scrollbar-hide pr-1 ${compact ? "-ml-2 pl-2" : "-ml-3 pl-3"}`, children: /* @__PURE__ */ jsx("nav", { className: "flex flex-col gap-2", children: sortedSections.map((section, sectionIndex) => {
|
|
453
|
+
const matchesItemQuery = (item) => {
|
|
454
|
+
if (!navQueryActive) return true;
|
|
455
|
+
const label = item.labelKey ? t(item.labelKey, item.label) : item.label;
|
|
456
|
+
if (matchesQuery(label)) return true;
|
|
457
|
+
return Array.isArray(item.children) && item.children.some(matchesItemQuery);
|
|
458
|
+
};
|
|
459
|
+
const visibleItems = navQueryActive ? section.items.filter(matchesItemQuery) : section.items;
|
|
460
|
+
if (visibleItems.length === 0) return null;
|
|
461
|
+
const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
462
|
+
const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label;
|
|
463
|
+
const sectionKey = `settings:${section.id}`;
|
|
464
|
+
const open = openGroups[sectionKey] !== false;
|
|
465
|
+
const sortSectionItems = (items = []) => [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
466
|
+
const filterChildren = (children2) => {
|
|
467
|
+
if (!children2) return [];
|
|
468
|
+
if (!navQueryActive) return [...children2];
|
|
469
|
+
return children2.filter(matchesItemQuery);
|
|
470
|
+
};
|
|
471
|
+
const renderSectionItem = (item, depth = 0) => {
|
|
472
|
+
const label = item.labelKey ? t(item.labelKey, item.label) : item.label;
|
|
473
|
+
const childItems = sortSectionItems(filterChildren(item.children));
|
|
474
|
+
const isOnItemBranch = !!pathname && (pathname === item.href || pathname.startsWith(`${item.href}/`));
|
|
475
|
+
const hasActiveChild = !!(pathname && childItems.some((child) => pathname === child.href || pathname.startsWith(`${child.href}/`)));
|
|
476
|
+
const showChildren = childItems.length > 0 && (isOnItemBranch || navQueryActive);
|
|
477
|
+
const isActive = isOnItemBranch || hasActiveChild;
|
|
478
|
+
const base = compact ? "w-10 h-10 justify-center" : "w-full py-2 gap-2";
|
|
479
|
+
const spacingStyle = !compact ? {
|
|
480
|
+
paddingLeft: `${12 + depth * 16}px`,
|
|
481
|
+
paddingRight: "12px"
|
|
482
|
+
} : void 0;
|
|
483
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
678
484
|
/* @__PURE__ */ jsxs(
|
|
679
|
-
|
|
485
|
+
Link,
|
|
680
486
|
{
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
487
|
+
href: item.href,
|
|
488
|
+
className: `relative text-sm font-medium rounded-lg inline-flex items-center ${base} ${isActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted"}`,
|
|
489
|
+
style: spacingStyle,
|
|
490
|
+
title: compact ? label : void 0,
|
|
491
|
+
"data-menu-item-id": item.id,
|
|
492
|
+
onClick: () => setMobileOpen(false),
|
|
685
493
|
children: [
|
|
686
|
-
|
|
687
|
-
|
|
494
|
+
isActive && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `absolute ${compact ? "left-[-20px]" : "left-[-12px]"} top-2 w-1 h-5 rounded-r bg-foreground` }),
|
|
495
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0", children: renderIcon(
|
|
496
|
+
item.icon,
|
|
497
|
+
item.iconName,
|
|
498
|
+
item.iconMarkup,
|
|
499
|
+
item.href.includes("/backend/entities/user/") && item.href.endsWith("/records") ? DataTableIcon : DefaultIcon
|
|
500
|
+
) }),
|
|
501
|
+
!compact && /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
|
|
688
502
|
]
|
|
689
503
|
}
|
|
690
504
|
),
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
505
|
+
showChildren ? childItems.map((child) => renderSectionItem(child, depth + 1)) : null
|
|
506
|
+
] }, item.id);
|
|
507
|
+
};
|
|
508
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
509
|
+
!compact && /* @__PURE__ */ jsxs(
|
|
510
|
+
Button,
|
|
511
|
+
{
|
|
512
|
+
variant: "muted",
|
|
513
|
+
onClick: () => toggleGroup(sectionKey),
|
|
514
|
+
className: "w-full px-1 justify-between flex text-xs font-medium uppercase tracking-wider text-muted-foreground/70 py-1",
|
|
515
|
+
"aria-expanded": open,
|
|
516
|
+
children: [
|
|
517
|
+
/* @__PURE__ */ jsx("span", { children: sectionLabel }),
|
|
518
|
+
/* @__PURE__ */ jsx(Chevron, { open })
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
(open || compact) && /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1`, children: sortedItems.map((item) => renderSectionItem(item)) }),
|
|
523
|
+
sectionIndex !== lastVisibleIndex && /* @__PURE__ */ jsx("div", { className: `my-2 border-t ${compact ? "-ml-2 -mr-3" : "-ml-3 -mr-4"}` })
|
|
524
|
+
] }, section.id);
|
|
525
|
+
}) }) })
|
|
696
526
|
] });
|
|
697
527
|
}
|
|
698
528
|
function renderSidebar(compact, hideHeader) {
|
|
699
529
|
if (!isChromeReady && isChromeLoading && resolvedGroups.length === 0) {
|
|
700
530
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-full gap-3", "data-testid": "backend-chrome-loading", children: [
|
|
701
|
-
!hideHeader ? /* @__PURE__ */ jsx("div", { className:
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
531
|
+
!hideHeader ? /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxs(
|
|
532
|
+
Link,
|
|
533
|
+
{
|
|
534
|
+
href: "/backend",
|
|
535
|
+
className: `flex items-center gap-3 rounded-xl transition-colors hover:bg-muted ${compact ? "p-2 justify-center" : "p-3"}`,
|
|
536
|
+
"aria-label": t("appShell.goToDashboard"),
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
|
|
539
|
+
!compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
) }) : null,
|
|
705
543
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 pr-1", children: [
|
|
706
544
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
707
545
|
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
@@ -749,180 +587,19 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
749
587
|
}
|
|
750
588
|
const isMobileVariant = !!hideHeader;
|
|
751
589
|
const shouldRenderSidebarInjectionSpots = !isMobileVariant;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const renderEditableItems = (baseItems, currentItems, depth = 0) => {
|
|
766
|
-
if (!customDraft) return null;
|
|
767
|
-
return baseItems.map((baseItem) => {
|
|
768
|
-
const itemKey = resolveItemKey(baseItem);
|
|
769
|
-
const current = currentItems.find((item) => item.href === baseItem.href) ?? baseItem;
|
|
770
|
-
const placeholder = baseItem.defaultTitle ?? baseItem.title;
|
|
771
|
-
const value = customDraft.itemLabels[itemKey] ?? "";
|
|
772
|
-
const hidden = customDraft.hiddenItemIds[itemKey] === true;
|
|
773
|
-
return /* @__PURE__ */ jsxs(
|
|
774
|
-
"div",
|
|
775
|
-
{
|
|
776
|
-
className: `flex flex-col gap-1 ${hidden ? "opacity-60" : ""}`,
|
|
777
|
-
style: depth ? { marginLeft: depth * 16 } : void 0,
|
|
778
|
-
children: [
|
|
779
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: placeholder }),
|
|
780
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
781
|
-
/* @__PURE__ */ jsx(
|
|
782
|
-
Checkbox,
|
|
783
|
-
{
|
|
784
|
-
checked: !hidden,
|
|
785
|
-
onCheckedChange: (next) => setItemHidden(itemKey, next !== true),
|
|
786
|
-
disabled: savingPreferences,
|
|
787
|
-
"aria-label": t("appShell.sidebarCustomizationShowItem"),
|
|
788
|
-
title: t("appShell.sidebarCustomizationShowItem")
|
|
789
|
-
}
|
|
790
|
-
),
|
|
791
|
-
/* @__PURE__ */ jsx(
|
|
792
|
-
"input",
|
|
793
|
-
{
|
|
794
|
-
value,
|
|
795
|
-
onChange: (event) => setItemLabel(itemKey, event.target.value),
|
|
796
|
-
placeholder,
|
|
797
|
-
disabled: savingPreferences,
|
|
798
|
-
className: "h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
799
|
-
}
|
|
800
|
-
)
|
|
801
|
-
] }),
|
|
802
|
-
baseItem.children && baseItem.children.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: renderEditableItems(baseItem.children, current.children ?? [], depth + 1) }) : null
|
|
803
|
-
]
|
|
804
|
-
},
|
|
805
|
-
itemKey
|
|
806
|
-
);
|
|
807
|
-
});
|
|
808
|
-
};
|
|
809
|
-
const customizationEditor = customizing ? customDraft ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded border border-dashed bg-muted/30 p-3", children: [
|
|
810
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
811
|
-
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: t("appShell.sidebarCustomizationHeading") }),
|
|
812
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
813
|
-
/* @__PURE__ */ jsx(
|
|
814
|
-
Button,
|
|
815
|
-
{
|
|
816
|
-
variant: "outline",
|
|
817
|
-
size: "sm",
|
|
818
|
-
onClick: resetCustomization,
|
|
819
|
-
disabled: savingPreferences,
|
|
820
|
-
children: t("appShell.sidebarCustomizationReset")
|
|
821
|
-
}
|
|
822
|
-
),
|
|
823
|
-
/* @__PURE__ */ jsx(
|
|
824
|
-
Button,
|
|
825
|
-
{
|
|
826
|
-
variant: "outline",
|
|
827
|
-
size: "sm",
|
|
828
|
-
onClick: cancelCustomization,
|
|
829
|
-
disabled: savingPreferences,
|
|
830
|
-
children: t("appShell.sidebarCustomizationCancel")
|
|
831
|
-
}
|
|
832
|
-
),
|
|
833
|
-
/* @__PURE__ */ jsx(
|
|
834
|
-
Button,
|
|
835
|
-
{
|
|
836
|
-
size: "sm",
|
|
837
|
-
className: "bg-foreground text-background hover:bg-foreground/90",
|
|
838
|
-
onClick: saveCustomization,
|
|
839
|
-
disabled: savingPreferences,
|
|
840
|
-
children: savingPreferences ? t("appShell.sidebarCustomizationSaving") : t("appShell.sidebarCustomizationSave")
|
|
841
|
-
}
|
|
842
|
-
)
|
|
843
|
-
] })
|
|
844
|
-
] }),
|
|
845
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("appShell.sidebarCustomizationHint", { locale: localeLabel }) }),
|
|
846
|
-
canApplyToRoles ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded border bg-background/80 p-3 shadow-sm", children: [
|
|
847
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
848
|
-
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: t("appShell.sidebarApplyToRolesTitle") }),
|
|
849
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("appShell.sidebarApplyToRolesDescription") })
|
|
850
|
-
] }),
|
|
851
|
-
availableRoleTargets.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: availableRoleTargets.map((role) => {
|
|
852
|
-
const checked = selectedRoleIds.includes(role.id);
|
|
853
|
-
const willClear = role.hasPreference && !checked;
|
|
854
|
-
return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 rounded-md border bg-background px-2 py-1 text-sm shadow-sm cursor-pointer", children: [
|
|
855
|
-
/* @__PURE__ */ jsx(
|
|
856
|
-
Checkbox,
|
|
857
|
-
{
|
|
858
|
-
checked,
|
|
859
|
-
onCheckedChange: () => toggleRoleSelection(role.id),
|
|
860
|
-
disabled: savingPreferences
|
|
861
|
-
}
|
|
862
|
-
),
|
|
863
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: role.name }),
|
|
864
|
-
role.hasPreference ? /* @__PURE__ */ jsx("span", { className: `text-xs ${willClear ? "text-destructive" : "text-muted-foreground"}`, children: willClear ? t("appShell.sidebarRoleWillClear") : t("appShell.sidebarRoleHasPreset") }) : null
|
|
865
|
-
] }, role.id);
|
|
866
|
-
}) }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("appShell.sidebarApplyToRolesEmpty") })
|
|
867
|
-
] }) : null,
|
|
868
|
-
customizationError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: customizationError }) : null,
|
|
869
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: orderedGroupIds.map((groupId, index) => {
|
|
870
|
-
const baseGroup = baseGroupMap.get(groupId);
|
|
871
|
-
if (!baseGroup) return null;
|
|
872
|
-
const currentGroup = navGroups.find((group) => resolveGroupKey(group) === groupId) ?? baseGroup;
|
|
873
|
-
const placeholder = baseGroup.defaultName ?? baseGroup.name;
|
|
874
|
-
const value = customDraft.groupLabels[groupId] ?? "";
|
|
875
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded border bg-background p-3 shadow-sm", children: [
|
|
876
|
-
/* @__PURE__ */ jsxs("div", { className: `flex ${compact ? "flex-col gap-2" : "items-center gap-2"}`, children: [
|
|
877
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
878
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("appShell.sidebarCustomizationGroupLabel") }),
|
|
879
|
-
/* @__PURE__ */ jsx(
|
|
880
|
-
"input",
|
|
881
|
-
{
|
|
882
|
-
value,
|
|
883
|
-
onChange: (event) => setGroupLabel(groupId, event.target.value),
|
|
884
|
-
placeholder,
|
|
885
|
-
disabled: savingPreferences,
|
|
886
|
-
className: "mt-1 h-8 w-full rounded border bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
887
|
-
}
|
|
888
|
-
)
|
|
889
|
-
] }),
|
|
890
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 self-start", children: [
|
|
891
|
-
/* @__PURE__ */ jsx(
|
|
892
|
-
IconButton,
|
|
893
|
-
{
|
|
894
|
-
variant: "outline",
|
|
895
|
-
size: "sm",
|
|
896
|
-
className: "text-muted-foreground hover:text-foreground",
|
|
897
|
-
onClick: () => moveGroup(groupId, -1),
|
|
898
|
-
disabled: index === 0 || savingPreferences,
|
|
899
|
-
"aria-label": t("appShell.sidebarCustomizationMoveUp"),
|
|
900
|
-
children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-4" })
|
|
901
|
-
}
|
|
902
|
-
),
|
|
903
|
-
/* @__PURE__ */ jsx(
|
|
904
|
-
IconButton,
|
|
905
|
-
{
|
|
906
|
-
variant: "outline",
|
|
907
|
-
size: "sm",
|
|
908
|
-
className: "text-muted-foreground hover:text-foreground",
|
|
909
|
-
onClick: () => moveGroup(groupId, 1),
|
|
910
|
-
disabled: index === orderedGroupIds.length - 1 || savingPreferences,
|
|
911
|
-
"aria-label": t("appShell.sidebarCustomizationMoveDown"),
|
|
912
|
-
children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
|
|
913
|
-
}
|
|
914
|
-
)
|
|
915
|
-
] })
|
|
916
|
-
] }),
|
|
917
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: renderEditableItems(baseGroup.items, currentGroup.items) })
|
|
918
|
-
] }, groupId);
|
|
919
|
-
}) })
|
|
920
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "rounded border border-dashed bg-muted/30 p-3 text-sm text-muted-foreground", children: t("appShell.sidebarCustomizationLoading") }) : null;
|
|
921
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-full gap-3", children: [
|
|
922
|
-
!hideHeader && /* @__PURE__ */ jsx("div", { className: `flex items-center ${compact ? "justify-center" : "justify-between"} mb-2`, children: /* @__PURE__ */ jsxs(Link, { href: "/backend", className: "flex items-center gap-2", "aria-label": t("appShell.goToDashboard"), children: [
|
|
923
|
-
/* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 32, height: 32, className: "rounded m-4" }),
|
|
924
|
-
!compact && /* @__PURE__ */ jsx("div", { className: "text-m font-semibold", children: resolvedProductName })
|
|
925
|
-
] }) }),
|
|
590
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-3", children: [
|
|
591
|
+
!hideHeader && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxs(
|
|
592
|
+
Link,
|
|
593
|
+
{
|
|
594
|
+
href: "/backend",
|
|
595
|
+
className: `flex items-center gap-3 rounded-xl transition-colors hover:bg-muted ${compact ? "p-2 justify-center" : "p-3"}`,
|
|
596
|
+
"aria-label": t("appShell.goToDashboard"),
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
|
|
599
|
+
!compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
|
|
600
|
+
]
|
|
601
|
+
}
|
|
602
|
+
) }),
|
|
926
603
|
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
927
604
|
InjectionSpot,
|
|
928
605
|
{
|
|
@@ -930,7 +607,30 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
930
607
|
context: injectionContext
|
|
931
608
|
}
|
|
932
609
|
) : null,
|
|
933
|
-
/* @__PURE__ */ jsx(
|
|
610
|
+
!compact && /* @__PURE__ */ jsx(
|
|
611
|
+
Input,
|
|
612
|
+
{
|
|
613
|
+
type: "text",
|
|
614
|
+
value: navQuery,
|
|
615
|
+
onChange: (e) => setNavQuery(e.target.value),
|
|
616
|
+
placeholder: t("appShell.searchNavPlaceholder", "Search..."),
|
|
617
|
+
"aria-label": t("appShell.searchNavAria", "Search navigation"),
|
|
618
|
+
leftIcon: /* @__PURE__ */ jsx(Search, { "aria-hidden": true }),
|
|
619
|
+
rightIcon: navQueryActive ? /* @__PURE__ */ jsx(
|
|
620
|
+
IconButton,
|
|
621
|
+
{
|
|
622
|
+
type: "button",
|
|
623
|
+
variant: "ghost",
|
|
624
|
+
size: "xs",
|
|
625
|
+
onClick: () => setNavQuery(""),
|
|
626
|
+
"aria-label": t("appShell.searchNavClear", "Clear search"),
|
|
627
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5", "aria-hidden": true })
|
|
628
|
+
}
|
|
629
|
+
) : void 0,
|
|
630
|
+
className: "mb-2"
|
|
631
|
+
}
|
|
632
|
+
),
|
|
633
|
+
/* @__PURE__ */ jsx("div", { "data-sidebar-scroll": "true", className: `flex flex-1 flex-col gap-3 overflow-y-auto scrollbar-hide pr-1 ${compact ? "-ml-2 pl-2" : "-ml-3 pl-3"}`, children: (() => {
|
|
934
634
|
const isSettingsPath = (href) => {
|
|
935
635
|
if (href === "/backend/settings") return true;
|
|
936
636
|
return resolvedSettingsPathPrefixes.some((prefix) => href.startsWith(prefix));
|
|
@@ -960,42 +660,50 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
960
660
|
) : null,
|
|
961
661
|
mainGroups.map((g, gi) => {
|
|
962
662
|
const groupId = resolveGroupKey(g);
|
|
963
|
-
const open = openGroups[groupId] !== false;
|
|
964
|
-
const visibleItems = g.items.filter((item) =>
|
|
663
|
+
const open = navQueryActive ? true : openGroups[groupId] !== false;
|
|
664
|
+
const visibleItems = g.items.filter((item) => {
|
|
665
|
+
if (item.hidden === true) return false;
|
|
666
|
+
if (!navQueryActive) return true;
|
|
667
|
+
if (matchesQuery(item.title)) return true;
|
|
668
|
+
const itemChildren = (item.children ?? []).filter((c) => c.hidden !== true);
|
|
669
|
+
return itemChildren.some((c) => matchesQuery(c.title));
|
|
670
|
+
});
|
|
965
671
|
if (visibleItems.length === 0) return null;
|
|
966
672
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
967
|
-
/* @__PURE__ */ jsxs(
|
|
673
|
+
!compact && /* @__PURE__ */ jsxs(
|
|
968
674
|
Button,
|
|
969
675
|
{
|
|
970
676
|
variant: "muted",
|
|
971
677
|
onClick: () => toggleGroup(groupId),
|
|
972
|
-
className:
|
|
678
|
+
className: "w-full px-1 justify-between flex text-xs font-medium uppercase tracking-wider text-muted-foreground/70 py-1",
|
|
973
679
|
"aria-expanded": open,
|
|
974
680
|
children: [
|
|
975
|
-
|
|
976
|
-
|
|
681
|
+
/* @__PURE__ */ jsx("span", { children: g.name }),
|
|
682
|
+
/* @__PURE__ */ jsx(Chevron, { open })
|
|
977
683
|
]
|
|
978
684
|
}
|
|
979
685
|
),
|
|
980
|
-
open && /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1
|
|
981
|
-
const
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
const
|
|
985
|
-
const
|
|
686
|
+
(open || compact) && /* @__PURE__ */ jsx("div", { className: `flex flex-col ${compact ? "items-center" : ""} gap-1`, children: visibleItems.map((i) => {
|
|
687
|
+
const allChildItems = (i.children ?? []).filter((child) => child.hidden !== true);
|
|
688
|
+
const matchingChildItems = navQueryActive ? allChildItems.filter((c) => matchesQuery(c.title)) : allChildItems;
|
|
689
|
+
const childItems = navQueryActive ? matchingChildItems : allChildItems;
|
|
690
|
+
const showChildren = navQueryActive ? matchingChildItems.length > 0 : !!pathname && allChildItems.length > 0 && pathname.startsWith(i.href);
|
|
691
|
+
const hasActiveChild = !!(pathname && allChildItems.some((c) => pathname.startsWith(c.href)));
|
|
692
|
+
const isParentActive = pathname === i.href || !navQueryActive && showChildren && !hasActiveChild;
|
|
693
|
+
const base = compact ? "w-10 h-10 justify-center" : "w-full px-3 py-2 gap-2";
|
|
986
694
|
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
987
695
|
/* @__PURE__ */ jsxs(
|
|
988
696
|
Link,
|
|
989
697
|
{
|
|
990
698
|
href: i.href,
|
|
991
|
-
className: `relative text-sm rounded inline-flex items-center ${base} ${isParentActive ? "bg-
|
|
699
|
+
className: `relative text-sm font-medium rounded-lg inline-flex items-center ${base} ${isParentActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted"} ${i.enabled === false ? "pointer-events-none opacity-50" : ""}`,
|
|
992
700
|
"aria-disabled": i.enabled === false,
|
|
993
701
|
title: compact ? i.title : void 0,
|
|
994
702
|
"data-menu-item-id": i.id ?? i.href,
|
|
995
703
|
onClick: () => setMobileOpen(false),
|
|
996
704
|
children: [
|
|
997
|
-
isParentActive ? /* @__PURE__ */ jsx("span", { className:
|
|
998
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
705
|
+
isParentActive ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `absolute ${compact ? "left-[-20px]" : "left-[-12px]"} top-2 w-1 h-5 rounded-r bg-foreground` }) : null,
|
|
706
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0", children: renderIcon(
|
|
999
707
|
i.icon,
|
|
1000
708
|
i.iconName,
|
|
1001
709
|
i.iconMarkup,
|
|
@@ -1005,40 +713,43 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1005
713
|
]
|
|
1006
714
|
}
|
|
1007
715
|
),
|
|
1008
|
-
showChildren ? /* @__PURE__ */
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
716
|
+
showChildren ? /* @__PURE__ */ jsxs("div", { className: `relative flex flex-col ${compact ? "items-center" : ""} gap-1`, children: [
|
|
717
|
+
!compact && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute left-1.5 top-1 bottom-1 w-px bg-border" }),
|
|
718
|
+
childItems.map((c) => {
|
|
719
|
+
const childActive = pathname?.startsWith(c.href);
|
|
720
|
+
const childBase = compact ? "w-10 h-8 justify-center" : "w-full pl-5 pr-3 py-2 gap-2";
|
|
721
|
+
return /* @__PURE__ */ jsxs(
|
|
722
|
+
Link,
|
|
723
|
+
{
|
|
724
|
+
href: c.href,
|
|
725
|
+
className: `relative text-sm font-medium rounded-lg inline-flex items-center ${childBase} ${childActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted"} ${c.enabled === false ? "pointer-events-none opacity-50" : ""}`,
|
|
726
|
+
"aria-disabled": c.enabled === false,
|
|
727
|
+
title: compact ? c.title : void 0,
|
|
728
|
+
"data-menu-item-id": c.id ?? c.href,
|
|
729
|
+
onClick: () => setMobileOpen(false),
|
|
730
|
+
children: [
|
|
731
|
+
childActive ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `absolute ${compact ? "left-[-20px]" : "left-[-12px]"} top-2 w-1 h-5 rounded-r bg-foreground` }) : null,
|
|
732
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0", children: renderIcon(
|
|
733
|
+
c.icon,
|
|
734
|
+
c.iconName,
|
|
735
|
+
c.iconMarkup,
|
|
736
|
+
c.href.includes("/backend/entities/user/") && c.href.endsWith("/records") ? DataTableIcon : DefaultIcon
|
|
737
|
+
) }),
|
|
738
|
+
!compact && /* @__PURE__ */ jsx("span", { children: c.title })
|
|
739
|
+
]
|
|
740
|
+
},
|
|
741
|
+
c.href
|
|
742
|
+
);
|
|
743
|
+
})
|
|
744
|
+
] }) : null
|
|
1034
745
|
] }, i.href);
|
|
1035
746
|
}) }),
|
|
1036
|
-
gi !== mainLastVisibleGroupIndex && /* @__PURE__ */ jsx("div", { className:
|
|
747
|
+
gi !== mainLastVisibleGroupIndex && /* @__PURE__ */ jsx("div", { className: `my-2 border-t ${compact ? "-ml-2 -mr-3" : "-ml-3 -mr-4"}` })
|
|
1037
748
|
] }, groupId);
|
|
1038
749
|
})
|
|
1039
750
|
] }) });
|
|
1040
751
|
})() }),
|
|
1041
|
-
/* @__PURE__ */ jsxs("div", { className: "sticky bottom-0
|
|
752
|
+
/* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 bg-background pb-1", children: [
|
|
1042
753
|
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1043
754
|
InjectionSpot,
|
|
1044
755
|
{
|
|
@@ -1046,56 +757,13 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1046
757
|
context: injectionContext
|
|
1047
758
|
}
|
|
1048
759
|
) : null,
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
|
|
760
|
+
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
761
|
+
StatusBadgeInjectionSpot,
|
|
1051
762
|
{
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
title: compact ? t("backend.nav.settings", "Settings") : void 0,
|
|
1055
|
-
onClick: () => setMobileOpen(false),
|
|
1056
|
-
children: [
|
|
1057
|
-
(pathname?.startsWith("/backend/settings") || pathname?.startsWith("/backend/config") || pathname?.startsWith("/backend/users") || pathname?.startsWith("/backend/roles") || pathname?.startsWith("/backend/api-keys") || pathname?.startsWith("/backend/entities") || pathname?.startsWith("/backend/query-indexes") || pathname?.startsWith("/backend/definitions") || pathname?.startsWith("/backend/instances") || pathname?.startsWith("/backend/tasks") || pathname?.startsWith("/backend/events") || pathname?.startsWith("/backend/rules") || pathname?.startsWith("/backend/sets") || pathname?.startsWith("/backend/logs") || pathname?.startsWith("/backend/directory") || pathname?.startsWith("/backend/feature-toggles")) && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground" }),
|
|
1058
|
-
/* @__PURE__ */ jsx("span", { className: `flex items-center justify-center shrink-0 ${compact ? "" : "text-muted-foreground"}`, children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1059
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
1060
|
-
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
1061
|
-
] }) }),
|
|
1062
|
-
!compact && /* @__PURE__ */ jsx("span", { children: t("backend.nav.settings", "Settings") })
|
|
1063
|
-
]
|
|
763
|
+
spotId: GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID,
|
|
764
|
+
context: injectionContext
|
|
1064
765
|
}
|
|
1065
|
-
),
|
|
1066
|
-
!customizing && /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
|
|
1067
|
-
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1068
|
-
StatusBadgeInjectionSpot,
|
|
1069
|
-
{
|
|
1070
|
-
spotId: GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID,
|
|
1071
|
-
context: injectionContext
|
|
1072
|
-
}
|
|
1073
|
-
) : null,
|
|
1074
|
-
compact || isMobileVariant ? /* @__PURE__ */ jsx(
|
|
1075
|
-
IconButton,
|
|
1076
|
-
{
|
|
1077
|
-
variant: "outline",
|
|
1078
|
-
size: "lg",
|
|
1079
|
-
onClick: startCustomization,
|
|
1080
|
-
disabled: loadingPreferences,
|
|
1081
|
-
"aria-label": t("appShell.customizeSidebar"),
|
|
1082
|
-
children: CustomizeIcon
|
|
1083
|
-
}
|
|
1084
|
-
) : /* @__PURE__ */ jsxs(
|
|
1085
|
-
Button,
|
|
1086
|
-
{
|
|
1087
|
-
variant: "outline",
|
|
1088
|
-
size: "default",
|
|
1089
|
-
onClick: startCustomization,
|
|
1090
|
-
disabled: loadingPreferences,
|
|
1091
|
-
"aria-label": t("appShell.customizeSidebar"),
|
|
1092
|
-
children: [
|
|
1093
|
-
CustomizeIcon,
|
|
1094
|
-
loadingPreferences ? t("appShell.sidebarCustomizationLoading") : t("appShell.customizeSidebar")
|
|
1095
|
-
]
|
|
1096
|
-
}
|
|
1097
|
-
)
|
|
1098
|
-
] }),
|
|
766
|
+
) : null,
|
|
1099
767
|
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1100
768
|
InjectionSpot,
|
|
1101
769
|
{
|
|
@@ -1106,7 +774,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1106
774
|
] })
|
|
1107
775
|
] });
|
|
1108
776
|
}
|
|
1109
|
-
const gridColsClass =
|
|
777
|
+
const gridColsClass = effectiveCollapsed ? "lg:grid-cols-[80px_1fr]" : "lg:grid-cols-[240px_1fr]";
|
|
1110
778
|
const headerCtxValue = React.useMemo(() => ({
|
|
1111
779
|
setBreadcrumb: setHeaderBreadcrumb,
|
|
1112
780
|
setTitle: setHeaderTitle
|
|
@@ -1144,7 +812,23 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1144
812
|
);
|
|
1145
813
|
return /* @__PURE__ */ jsxs(HeaderContext.Provider, { value: headerCtxValue, children: [
|
|
1146
814
|
/* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid ${gridColsClass}`, children: [
|
|
1147
|
-
/* @__PURE__ */
|
|
815
|
+
/* @__PURE__ */ jsxs("aside", { ref: sidebarAsideRef, className: `${asideClassesBase} ${effectiveCollapsed ? "px-2" : "px-3"} hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative`, style: { width: asideWidth }, children: [
|
|
816
|
+
renderSidebar(effectiveCollapsed),
|
|
817
|
+
sidebarScrollState !== "none" ? /* @__PURE__ */ jsx(
|
|
818
|
+
"div",
|
|
819
|
+
{
|
|
820
|
+
"aria-hidden": true,
|
|
821
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 flex h-10 items-end justify-center bg-gradient-to-t from-background via-background/80 to-transparent pb-1.5",
|
|
822
|
+
children: /* @__PURE__ */ jsx(
|
|
823
|
+
"span",
|
|
824
|
+
{
|
|
825
|
+
className: `inline-flex transition-transform duration-300 ${sidebarScrollState === "up" ? "rotate-180" : ""}`,
|
|
826
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 animate-bounce text-muted-foreground/70" })
|
|
827
|
+
}
|
|
828
|
+
)
|
|
829
|
+
}
|
|
830
|
+
) : null
|
|
831
|
+
] }),
|
|
1148
832
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
|
|
1149
833
|
/* @__PURE__ */ jsxs("header", { className: "border-b bg-background/80 px-3 lg:px-4 py-2 lg:py-3 flex items-center justify-between gap-2", children: [
|
|
1150
834
|
/* @__PURE__ */ jsx(
|
|
@@ -1165,7 +849,6 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1165
849
|
className: "hidden lg:inline-flex",
|
|
1166
850
|
"aria-label": t("appShell.toggleSidebar"),
|
|
1167
851
|
onClick: () => setCollapsed((c) => !c),
|
|
1168
|
-
disabled: customizing,
|
|
1169
852
|
children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1170
853
|
/* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
1171
854
|
/* @__PURE__ */ jsx("path", { d: "M9 4v16" })
|
|
@@ -1259,112 +942,6 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1259
942
|
/* @__PURE__ */ jsx(UmesDevToolsPanel, {})
|
|
1260
943
|
] });
|
|
1261
944
|
}
|
|
1262
|
-
AppShell.cloneGroups = function cloneGroups(groups) {
|
|
1263
|
-
const cloneItem = (item) => ({
|
|
1264
|
-
id: item.id,
|
|
1265
|
-
href: item.href,
|
|
1266
|
-
title: item.title,
|
|
1267
|
-
defaultTitle: item.defaultTitle,
|
|
1268
|
-
icon: item.icon,
|
|
1269
|
-
iconName: item.iconName,
|
|
1270
|
-
iconMarkup: item.iconMarkup,
|
|
1271
|
-
enabled: item.enabled,
|
|
1272
|
-
hidden: item.hidden,
|
|
1273
|
-
pageContext: item.pageContext,
|
|
1274
|
-
children: item.children ? item.children.map((child) => cloneItem(child)) : void 0
|
|
1275
|
-
});
|
|
1276
|
-
return groups.map((group) => ({
|
|
1277
|
-
id: group.id,
|
|
1278
|
-
name: group.name,
|
|
1279
|
-
defaultName: group.defaultName,
|
|
1280
|
-
items: group.items.map((item) => cloneItem(item))
|
|
1281
|
-
}));
|
|
1282
|
-
};
|
|
1283
|
-
function applyCustomizationDraft(baseGroups, draft) {
|
|
1284
|
-
const clones = AppShell.cloneGroups(baseGroups);
|
|
1285
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1286
|
-
for (const group of clones) {
|
|
1287
|
-
byId.set(resolveGroupKey(group), group);
|
|
1288
|
-
}
|
|
1289
|
-
const orderedIds = mergeGroupOrder(draft.order, Array.from(byId.keys()));
|
|
1290
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1291
|
-
const result = [];
|
|
1292
|
-
for (const id of orderedIds) {
|
|
1293
|
-
if (seen.has(id)) continue;
|
|
1294
|
-
const group = byId.get(id);
|
|
1295
|
-
if (!group) continue;
|
|
1296
|
-
seen.add(id);
|
|
1297
|
-
const baseName = group.defaultName ?? group.name;
|
|
1298
|
-
const override = draft.groupLabels[id]?.trim();
|
|
1299
|
-
result.push({
|
|
1300
|
-
...group,
|
|
1301
|
-
name: override && override.length > 0 ? override : baseName,
|
|
1302
|
-
items: group.items.map((item) => applyItemDraft(item, draft))
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
return result;
|
|
1306
|
-
}
|
|
1307
|
-
function applyItemDraft(item, draft) {
|
|
1308
|
-
const itemKey = resolveItemKey(item);
|
|
1309
|
-
const baseTitle = item.defaultTitle ?? item.title;
|
|
1310
|
-
const override = draft.itemLabels[itemKey]?.trim();
|
|
1311
|
-
const children = item.children ? item.children.map((child) => applyItemDraft(child, draft)) : void 0;
|
|
1312
|
-
const hidden = draft.hiddenItemIds[itemKey] === true;
|
|
1313
|
-
return {
|
|
1314
|
-
...item,
|
|
1315
|
-
title: override && override.length > 0 ? override : baseTitle,
|
|
1316
|
-
hidden,
|
|
1317
|
-
children
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
function mergeGroupOrder(preferred, current) {
|
|
1321
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1322
|
-
const merged = [];
|
|
1323
|
-
for (const id of preferred) {
|
|
1324
|
-
const trimmed = id.trim();
|
|
1325
|
-
if (!trimmed || seen.has(trimmed) || !current.includes(trimmed)) continue;
|
|
1326
|
-
seen.add(trimmed);
|
|
1327
|
-
merged.push(trimmed);
|
|
1328
|
-
}
|
|
1329
|
-
for (const id of current) {
|
|
1330
|
-
if (seen.has(id)) continue;
|
|
1331
|
-
seen.add(id);
|
|
1332
|
-
merged.push(id);
|
|
1333
|
-
}
|
|
1334
|
-
return merged;
|
|
1335
|
-
}
|
|
1336
|
-
function collectSidebarDefaults(groups) {
|
|
1337
|
-
const groupDefaults = /* @__PURE__ */ new Map();
|
|
1338
|
-
const itemDefaults = /* @__PURE__ */ new Map();
|
|
1339
|
-
const visitItems = (items) => {
|
|
1340
|
-
for (const item of items) {
|
|
1341
|
-
const key = resolveItemKey(item);
|
|
1342
|
-
const baseTitle = item.defaultTitle ?? item.title;
|
|
1343
|
-
itemDefaults.set(key, baseTitle);
|
|
1344
|
-
itemDefaults.set(item.href, baseTitle);
|
|
1345
|
-
if (item.children && item.children.length > 0) visitItems(item.children);
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
for (const group of groups) {
|
|
1349
|
-
const key = resolveGroupKey(group);
|
|
1350
|
-
groupDefaults.set(key, group.defaultName ?? group.name);
|
|
1351
|
-
visitItems(group.items);
|
|
1352
|
-
}
|
|
1353
|
-
return { groupDefaults, itemDefaults };
|
|
1354
|
-
}
|
|
1355
|
-
function filterMainSidebarGroups(groups) {
|
|
1356
|
-
const isMainItem = (item) => {
|
|
1357
|
-
if (item.pageContext && item.pageContext !== "main") return false;
|
|
1358
|
-
return true;
|
|
1359
|
-
};
|
|
1360
|
-
return groups.map((group) => ({
|
|
1361
|
-
...group,
|
|
1362
|
-
items: group.items.filter(isMainItem).map((item) => ({
|
|
1363
|
-
...item,
|
|
1364
|
-
children: item.children?.filter(isMainItem)
|
|
1365
|
-
}))
|
|
1366
|
-
})).filter((group) => group.items.length > 0);
|
|
1367
|
-
}
|
|
1368
945
|
export {
|
|
1369
946
|
AppShell,
|
|
1370
947
|
ApplyBreadcrumb
|