@svadmin/ui 0.29.1 → 0.29.2
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/package.json +20 -9
- package/src/README.md +0 -18
- package/src/actions.ts +0 -110
- package/src/app.css +0 -673
- package/src/component-registry.svelte.ts +0 -60
- package/src/components/AICommandBar.svelte +0 -180
- package/src/components/AboutSettings.svelte +0 -106
- package/src/components/AdminApp.svelte +0 -199
- package/src/components/AnimatedCounter.svelte +0 -46
- package/src/components/AnomalyBadge.svelte +0 -65
- package/src/components/AppearanceSettings.svelte +0 -183
- package/src/components/AuditLogDrawer.svelte +0 -93
- package/src/components/AuditLogViewer.svelte +0 -226
- package/src/components/Authenticated.svelte +0 -51
- package/src/components/AutoForm.svelte +0 -242
- package/src/components/AutoSaveIndicator.svelte +0 -35
- package/src/components/AutoTable.svelte +0 -839
- package/src/components/Breadcrumbs.svelte +0 -58
- package/src/components/CanAccess.svelte +0 -23
- package/src/components/CatchAllNavigate.svelte +0 -21
- package/src/components/ChatDialog.svelte +0 -535
- package/src/components/ComboboxField.svelte +0 -129
- package/src/components/CommandPalette.svelte +0 -135
- package/src/components/ConfigErrorScreen.svelte +0 -126
- package/src/components/ConfirmDialog.svelte +0 -53
- package/src/components/CopilotPanel.svelte +0 -163
- package/src/components/CreatePage.svelte +0 -39
- package/src/components/DevTools.svelte +0 -149
- package/src/components/DraggableGrid.svelte +0 -86
- package/src/components/DraggableHeader.svelte +0 -112
- package/src/components/DrawerForm.svelte +0 -35
- package/src/components/EditPage.svelte +0 -52
- package/src/components/EmptyState.svelte +0 -31
- package/src/components/ErrorBoundary.svelte +0 -49
- package/src/components/ErrorComponent.svelte +0 -36
- package/src/components/FieldRenderer.svelte +0 -357
- package/src/components/ForgotPasswordPage.svelte +0 -112
- package/src/components/Header.svelte +0 -94
- package/src/components/InferencerPanel.svelte +0 -192
- package/src/components/InfiniteList.svelte +0 -93
- package/src/components/InlineEdit.svelte +0 -88
- package/src/components/InsightCard.svelte +0 -98
- package/src/components/KeyboardShortcuts.svelte +0 -61
- package/src/components/Layout.svelte +0 -167
- package/src/components/ListPage.svelte +0 -71
- package/src/components/LiveIndicator.svelte +0 -58
- package/src/components/LoginPage.svelte +0 -163
- package/src/components/MarkdownRenderer.svelte +0 -127
- package/src/components/ModalForm.svelte +0 -38
- package/src/components/NavigateToResource.svelte +0 -20
- package/src/components/PageHeader.svelte +0 -39
- package/src/components/PageSkeleton.svelte +0 -71
- package/src/components/PasswordInput.svelte +0 -96
- package/src/components/PermissionMatrix.svelte +0 -185
- package/src/components/ProfilePage.svelte +0 -284
- package/src/components/RegisterPage.svelte +0 -112
- package/src/components/RolesSettings.svelte +0 -146
- package/src/components/SettingsPage.svelte +0 -113
- package/src/components/ShowPage.svelte +0 -73
- package/src/components/Sidebar.svelte +0 -417
- package/src/components/SidebarItem.svelte +0 -129
- package/src/components/SmartSuggest.svelte +0 -158
- package/src/components/StatsCard.svelte +0 -79
- package/src/components/StepsForm.svelte +0 -192
- package/src/components/TaskQueueDrawer.svelte +0 -158
- package/src/components/TenantSwitcher.svelte +0 -84
- package/src/components/ThemedTitle.svelte +0 -32
- package/src/components/Toast.svelte +0 -46
- package/src/components/TooltipButton.svelte +0 -42
- package/src/components/UndoableNotification.svelte +0 -69
- package/src/components/UpdatePasswordPage.svelte +0 -89
- package/src/components/VirtualTable.svelte +0 -76
- package/src/components/VoiceInput.svelte +0 -120
- package/src/components/buttons/CloneButton.svelte +0 -30
- package/src/components/buttons/CreateButton.svelte +0 -29
- package/src/components/buttons/DeleteButton.svelte +0 -59
- package/src/components/buttons/EditButton.svelte +0 -30
- package/src/components/buttons/ExportButton.svelte +0 -30
- package/src/components/buttons/ImportButton.svelte +0 -56
- package/src/components/buttons/ListButton.svelte +0 -23
- package/src/components/buttons/RefreshButton.svelte +0 -36
- package/src/components/buttons/SaveButton.svelte +0 -27
- package/src/components/buttons/ShowButton.svelte +0 -31
- package/src/components/buttons/index.ts +0 -10
- package/src/components/charts/BarChart.svelte +0 -69
- package/src/components/charts/LineChart.svelte +0 -115
- package/src/components/charts/PieChart.svelte +0 -95
- package/src/components/charts/index.ts +0 -3
- package/src/components/fieldComponentMap.ts +0 -79
- package/src/components/fields/ArrayField.svelte +0 -86
- package/src/components/fields/BooleanField.svelte +0 -13
- package/src/components/fields/DateField.svelte +0 -6
- package/src/components/fields/EmailField.svelte +0 -9
- package/src/components/fields/FileField.svelte +0 -14
- package/src/components/fields/ImageField.svelte +0 -9
- package/src/components/fields/JsonField.svelte +0 -72
- package/src/components/fields/MarkdownField.svelte +0 -9
- package/src/components/fields/MultiSelectField.svelte +0 -21
- package/src/components/fields/NumberField.svelte +0 -6
- package/src/components/fields/RelationField.svelte +0 -24
- package/src/components/fields/RichTextField.svelte +0 -35
- package/src/components/fields/SelectField.svelte +0 -14
- package/src/components/fields/TagField.svelte +0 -12
- package/src/components/fields/TextField.svelte +0 -11
- package/src/components/fields/UrlField.svelte +0 -14
- package/src/components/ui/alert/alert-description.svelte +0 -17
- package/src/components/ui/alert/alert-title.svelte +0 -17
- package/src/components/ui/alert/alert.svelte +0 -44
- package/src/components/ui/alert/index.ts +0 -15
- package/src/components/ui/alert-dialog/alert-dialog-content.svelte +0 -29
- package/src/components/ui/alert-dialog/alert-dialog-description.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-header.svelte +0 -11
- package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +0 -16
- package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +0 -6
- package/src/components/ui/alert-dialog/alert-dialog-title.svelte +0 -16
- package/src/components/ui/alert-dialog/index.ts +0 -20
- package/src/components/ui/avatar/avatar.svelte +0 -63
- package/src/components/ui/avatar/index.ts +0 -2
- package/src/components/ui/badge/badge.svelte +0 -49
- package/src/components/ui/badge/index.ts +0 -3
- package/src/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-item.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-link.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-list.svelte +0 -15
- package/src/components/ui/breadcrumb/breadcrumb-page.svelte +0 -18
- package/src/components/ui/breadcrumb/breadcrumb-separator.svelte +0 -22
- package/src/components/ui/breadcrumb/breadcrumb.svelte +0 -11
- package/src/components/ui/breadcrumb/index.ts +0 -7
- package/src/components/ui/button/button.svelte +0 -90
- package/src/components/ui/button/index.ts +0 -18
- package/src/components/ui/card/card-action.svelte +0 -23
- package/src/components/ui/card/card-content.svelte +0 -20
- package/src/components/ui/card/card-description.svelte +0 -20
- package/src/components/ui/card/card-footer.svelte +0 -20
- package/src/components/ui/card/card-header.svelte +0 -23
- package/src/components/ui/card/card-title.svelte +0 -15
- package/src/components/ui/card/card.svelte +0 -22
- package/src/components/ui/card/index.ts +0 -25
- package/src/components/ui/checkbox/checkbox.svelte +0 -39
- package/src/components/ui/checkbox/index.ts +0 -6
- package/src/components/ui/collapsible/index.ts +0 -7
- package/src/components/ui/command/command-dialog.svelte +0 -31
- package/src/components/ui/command/index.ts +0 -2
- package/src/components/ui/context-menu/index.ts +0 -11
- package/src/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/components/ui/dialog/dialog-content.svelte +0 -48
- package/src/components/ui/dialog/dialog-description.svelte +0 -17
- package/src/components/ui/dialog/dialog-footer.svelte +0 -32
- package/src/components/ui/dialog/dialog-header.svelte +0 -20
- package/src/components/ui/dialog/dialog-overlay.svelte +0 -17
- package/src/components/ui/dialog/dialog-portal.svelte +0 -7
- package/src/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/components/ui/dialog/dialog.svelte +0 -7
- package/src/components/ui/dialog/index.ts +0 -34
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -43
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -35
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -36
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -10
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -27
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +0 -45
- package/src/components/ui/dropdown-menu/index.ts +0 -21
- package/src/components/ui/input/index.ts +0 -7
- package/src/components/ui/input/input.svelte +0 -48
- package/src/components/ui/label/index.ts +0 -1
- package/src/components/ui/label/label.svelte +0 -18
- package/src/components/ui/pagination/index.ts +0 -7
- package/src/components/ui/pagination/pagination-content.svelte +0 -11
- package/src/components/ui/pagination/pagination-ellipsis.svelte +0 -12
- package/src/components/ui/pagination/pagination-item.svelte +0 -11
- package/src/components/ui/pagination/pagination-link.svelte +0 -26
- package/src/components/ui/pagination/pagination-next.svelte +0 -22
- package/src/components/ui/pagination/pagination-previous.svelte +0 -22
- package/src/components/ui/pagination/pagination.svelte +0 -11
- package/src/components/ui/popover/index.ts +0 -12
- package/src/components/ui/popover/popover-content.svelte +0 -25
- package/src/components/ui/progress/index.ts +0 -1
- package/src/components/ui/progress/progress.svelte +0 -22
- package/src/components/ui/scroll-area/index.ts +0 -1
- package/src/components/ui/scroll-area/scroll-area.svelte +0 -20
- package/src/components/ui/select/index.ts +0 -2
- package/src/components/ui/select/select.svelte +0 -36
- package/src/components/ui/separator/index.ts +0 -7
- package/src/components/ui/separator/separator.svelte +0 -23
- package/src/components/ui/sheet/index.ts +0 -16
- package/src/components/ui/sheet/sheet-content.svelte +0 -28
- package/src/components/ui/sheet/sheet-description.svelte +0 -22
- package/src/components/ui/sheet/sheet-footer.svelte +0 -22
- package/src/components/ui/sheet/sheet-header.svelte +0 -22
- package/src/components/ui/sheet/sheet-title.svelte +0 -22
- package/src/components/ui/sheet/sheet.svelte +0 -78
- package/src/components/ui/skeleton/index.ts +0 -2
- package/src/components/ui/skeleton/skeleton.svelte +0 -21
- package/src/components/ui/switch/index.ts +0 -2
- package/src/components/ui/switch/switch.svelte +0 -49
- package/src/components/ui/table/index.ts +0 -28
- package/src/components/ui/table/table-body.svelte +0 -15
- package/src/components/ui/table/table-caption.svelte +0 -20
- package/src/components/ui/table/table-cell.svelte +0 -15
- package/src/components/ui/table/table-footer.svelte +0 -20
- package/src/components/ui/table/table-head.svelte +0 -15
- package/src/components/ui/table/table-header.svelte +0 -20
- package/src/components/ui/table/table-row.svelte +0 -15
- package/src/components/ui/table/table.svelte +0 -17
- package/src/components/ui/tabs/index.ts +0 -15
- package/src/components/ui/tabs/tabs-content.svelte +0 -30
- package/src/components/ui/tabs/tabs-list.svelte +0 -26
- package/src/components/ui/tabs/tabs-trigger.svelte +0 -37
- package/src/components/ui/tabs/tabs.svelte +0 -27
- package/src/components/ui/textarea/index.ts +0 -2
- package/src/components/ui/textarea/textarea.svelte +0 -24
- package/src/components/ui/tooltip/index.ts +0 -19
- package/src/components/ui/tooltip/tooltip-content.svelte +0 -52
- package/src/components/ui/tooltip/tooltip-portal.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
- package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
- package/src/components/ui/tooltip/tooltip.svelte +0 -10
- package/src/editor-config.svelte.ts +0 -23
- package/src/index.ts +0 -155
- package/src/markdown-deps.d.ts +0 -21
- package/src/router-state.svelte.ts +0 -65
- package/src/types.ts +0 -33
- package/src/utils.ts +0 -28
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
import { getAuthProvider, getResources } from '@svadmin/core';
|
|
4
|
-
import type { Role } from '@svadmin/core';
|
|
5
|
-
import PermissionMatrix from './PermissionMatrix.svelte';
|
|
6
|
-
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
7
|
-
import { toast } from '@svadmin/core/toast';
|
|
8
|
-
import { AlertCircle } from '@lucide/svelte';
|
|
9
|
-
|
|
10
|
-
const authProvider = getAuthProvider();
|
|
11
|
-
const rawResources = getResources();
|
|
12
|
-
|
|
13
|
-
let roles = $state<RoleInfo[]>([]);
|
|
14
|
-
let selectedRoleCode = $state<string>('');
|
|
15
|
-
|
|
16
|
-
// Matrix format: { "roleId": { "resourceName": ["action1", "action2"] } }
|
|
17
|
-
let permissionsCache = $state<Record<string, Record<string, string[]>>>({});
|
|
18
|
-
|
|
19
|
-
let loadingRoles = $state(true);
|
|
20
|
-
let loadingPermissions = $state(false);
|
|
21
|
-
let saving = $state(false);
|
|
22
|
-
let error = $state<string | null>(null);
|
|
23
|
-
|
|
24
|
-
// Convert system resources to MatrixResource format
|
|
25
|
-
let matrixResources = $derived<ResourceInfo[]>(
|
|
26
|
-
rawResources.map(r => ({
|
|
27
|
-
code: r.name,
|
|
28
|
-
name: r.label || r.name,
|
|
29
|
-
}))
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
let matrixActions = $state<ActionInfo[]>([
|
|
33
|
-
{ code: 'create', name: t('common.create') ?? 'Create' },
|
|
34
|
-
{ code: 'read', name: t('common.detail') ?? 'Read' },
|
|
35
|
-
{ code: 'update', name: t('common.edit') ?? 'Update' },
|
|
36
|
-
{ code: 'delete', name: t('common.delete') ?? 'Delete' },
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
async function loadRoles() {
|
|
40
|
-
if (!authProvider?.getRoles) {
|
|
41
|
-
error = t('settings.rbacNotSupported') ?? 'RBAC not supported by AuthProvider';
|
|
42
|
-
loadingRoles = false;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const fetchedRoles = await authProvider.getRoles();
|
|
47
|
-
roles = fetchedRoles.map(r => ({ code: r.id, ...r }));
|
|
48
|
-
if (roles.length > 0 && !selectedRoleCode) {
|
|
49
|
-
selectedRoleCode = roles[0].code;
|
|
50
|
-
}
|
|
51
|
-
} catch (e) {
|
|
52
|
-
error = (e as Error).message;
|
|
53
|
-
} finally {
|
|
54
|
-
loadingRoles = false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function loadPermissionsForRole(roleId: string) {
|
|
59
|
-
if (!authProvider?.getRolePermissions || permissionsCache[roleId]) return;
|
|
60
|
-
|
|
61
|
-
loadingPermissions = true;
|
|
62
|
-
try {
|
|
63
|
-
const perms = await authProvider.getRolePermissions(roleId);
|
|
64
|
-
permissionsCache[roleId] = perms;
|
|
65
|
-
} catch (e) {
|
|
66
|
-
toast.error((e as Error).message);
|
|
67
|
-
} finally {
|
|
68
|
-
loadingPermissions = false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Load permissions whenever the selected role changes
|
|
73
|
-
$effect(() => {
|
|
74
|
-
if (selectedRoleCode && !permissionsCache[selectedRoleCode] && !loadingPermissions) {
|
|
75
|
-
loadPermissionsForRole(selectedRoleCode);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
$effect(() => {
|
|
80
|
-
loadRoles();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// isGranted logic
|
|
84
|
-
function checkGrant(roleCode: string, resourceCode: string, actionCode: string) {
|
|
85
|
-
const rolePerms = permissionsCache[roleCode];
|
|
86
|
-
if (!rolePerms) return false;
|
|
87
|
-
return rolePerms[resourceCode]?.includes(actionCode) ?? false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function handleToggle(roleCode: string, resourceCode: string, actionCode: string, grant: boolean) {
|
|
91
|
-
if (!authProvider?.updateRolePermissions) return;
|
|
92
|
-
|
|
93
|
-
// Copy state
|
|
94
|
-
const currentRolePerms = { ...(permissionsCache[roleCode] || {}) };
|
|
95
|
-
const currentResPerms = [...(currentRolePerms[resourceCode] || [])];
|
|
96
|
-
|
|
97
|
-
// Optimistic update
|
|
98
|
-
if (grant) {
|
|
99
|
-
if (!currentResPerms.includes(actionCode)) currentResPerms.push(actionCode);
|
|
100
|
-
} else {
|
|
101
|
-
const idx = currentResPerms.indexOf(actionCode);
|
|
102
|
-
if (idx > -1) currentResPerms.splice(idx, 1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
currentRolePerms[resourceCode] = currentResPerms;
|
|
106
|
-
permissionsCache[roleCode] = currentRolePerms;
|
|
107
|
-
|
|
108
|
-
saving = true;
|
|
109
|
-
try {
|
|
110
|
-
// Send the entire role's permissions back (as defined by our AuthProvider interface)
|
|
111
|
-
const result = await authProvider.updateRolePermissions(roleCode, currentRolePerms);
|
|
112
|
-
if (result.success) {
|
|
113
|
-
// Success silently
|
|
114
|
-
} else {
|
|
115
|
-
throw new Error(result.error?.message ?? t('common.operationFailed'));
|
|
116
|
-
}
|
|
117
|
-
} catch (e) {
|
|
118
|
-
toast.error((e as Error).message);
|
|
119
|
-
// Rollback on fail
|
|
120
|
-
delete permissionsCache[roleCode];
|
|
121
|
-
loadPermissionsForRole(roleCode);
|
|
122
|
-
} finally {
|
|
123
|
-
saving = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
</script>
|
|
127
|
-
|
|
128
|
-
{#if error}
|
|
129
|
-
<div class="h-full flex items-center justify-center">
|
|
130
|
-
<div class="max-w-md p-6 border border-destructive/20 bg-destructive/10 text-destructive rounded-lg flex flex-col items-center gap-3 text-center">
|
|
131
|
-
<AlertCircle class="h-8 w-8" />
|
|
132
|
-
<h3 class="font-bold text-lg">{t('common.error') ?? 'Error'}</h3>
|
|
133
|
-
<p>{error}</p>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
{:else}
|
|
137
|
-
<PermissionMatrix
|
|
138
|
-
{roles}
|
|
139
|
-
resources={matrixResources}
|
|
140
|
-
actions={matrixActions}
|
|
141
|
-
isGranted={checkGrant}
|
|
142
|
-
bind:selectedRole={selectedRoleCode}
|
|
143
|
-
loading={loadingRoles || loadingPermissions || saving}
|
|
144
|
-
onToggle={handleToggle}
|
|
145
|
-
/>
|
|
146
|
-
{/if}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { t } from '@svadmin/core/i18n';
|
|
3
|
-
import { navigate } from '@svadmin/core/router';
|
|
4
|
-
import { currentPath } from '@svadmin/core/router';
|
|
5
|
-
import { User, Palette, Info, Shield, FileSearch } from '@lucide/svelte';
|
|
6
|
-
import ProfilePage from './ProfilePage.svelte';
|
|
7
|
-
import AppearanceSettings from './AppearanceSettings.svelte';
|
|
8
|
-
import AboutSettings from './AboutSettings.svelte';
|
|
9
|
-
import RolesSettings from './RolesSettings.svelte';
|
|
10
|
-
import AuditLogViewer from './AuditLogViewer.svelte';
|
|
11
|
-
|
|
12
|
-
const sections = [
|
|
13
|
-
{
|
|
14
|
-
group: 'settings.general',
|
|
15
|
-
items: [
|
|
16
|
-
{ key: 'profile', path: '/settings/profile', icon: User, label: 'settings.profile' },
|
|
17
|
-
{ key: 'appearance', path: '/settings/appearance', icon: Palette, label: 'settings.appearance' },
|
|
18
|
-
],
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
group: 'settings.system',
|
|
22
|
-
items: [
|
|
23
|
-
{ key: 'roles', path: '/settings/roles', icon: Shield, label: 'settings.rolesAndPermissions' },
|
|
24
|
-
{ key: 'audit', path: '/settings/audit', icon: FileSearch, label: 'settings.auditLogs' },
|
|
25
|
-
{ key: 'about', path: '/settings/about', icon: Info, label: 'settings.about' },
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
let activeKey = $derived.by(() => {
|
|
31
|
-
const path = currentPath();
|
|
32
|
-
if (path.includes('/settings/appearance')) return 'appearance';
|
|
33
|
-
if (path.includes('/settings/roles')) return 'roles';
|
|
34
|
-
if (path.includes('/settings/audit')) return 'audit';
|
|
35
|
-
if (path.includes('/settings/about')) return 'about';
|
|
36
|
-
return 'profile';
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Default redirect to profile
|
|
40
|
-
$effect(() => {
|
|
41
|
-
const path = currentPath();
|
|
42
|
-
if (path === '/settings') {
|
|
43
|
-
navigate('/settings/profile');
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<div class="flex flex-col lg:flex-row gap-0 min-h-full">
|
|
49
|
-
<!-- Left sidebar navigation -->
|
|
50
|
-
<nav class="w-full lg:w-56 shrink-0 bg-muted/10 shadow-[inset_0_-1px_0_rgba(0,0,0,0.05)] lg:shadow-[inset_-1px_0_0_rgba(0,0,0,0.05)] dark:shadow-[inset_-1px_0_0_rgba(255,255,255,0.05)]">
|
|
51
|
-
<!-- Mobile: horizontal scroll tabs -->
|
|
52
|
-
<div class="flex lg:hidden overflow-x-auto px-4 py-2 gap-1">
|
|
53
|
-
{#each sections as section}
|
|
54
|
-
{#each section.items as item}
|
|
55
|
-
{@const active = activeKey === item.key}
|
|
56
|
-
<button
|
|
57
|
-
class="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md whitespace-nowrap transition-colors
|
|
58
|
-
{active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground hover:bg-background/50'}"
|
|
59
|
-
onclick={() => navigate(item.path)}
|
|
60
|
-
>
|
|
61
|
-
<item.icon class="h-4 w-4" />
|
|
62
|
-
{t(item.label)}
|
|
63
|
-
</button>
|
|
64
|
-
{/each}
|
|
65
|
-
{/each}
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<!-- Desktop: vertical nav with groups -->
|
|
69
|
-
<div class="hidden lg:block py-6 px-3 space-y-6">
|
|
70
|
-
<div class="px-3">
|
|
71
|
-
<h2 class="text-lg font-semibold text-foreground">{t('settings.title')}</h2>
|
|
72
|
-
<p class="text-xs text-muted-foreground mt-1">{t('settings.settingsDescription')}</p>
|
|
73
|
-
</div>
|
|
74
|
-
{#each sections as section}
|
|
75
|
-
<div>
|
|
76
|
-
<h3 class="px-3 mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
|
77
|
-
{t(section.group)}
|
|
78
|
-
</h3>
|
|
79
|
-
<div class="space-y-0.5">
|
|
80
|
-
{#each section.items as item}
|
|
81
|
-
{@const active = activeKey === item.key}
|
|
82
|
-
<button
|
|
83
|
-
class="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-sm transition-all
|
|
84
|
-
{active
|
|
85
|
-
? 'bg-primary/10 text-primary font-medium'
|
|
86
|
-
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'}"
|
|
87
|
-
onclick={() => navigate(item.path)}
|
|
88
|
-
>
|
|
89
|
-
<item.icon class="h-4 w-4 shrink-0" />
|
|
90
|
-
{t(item.label)}
|
|
91
|
-
</button>
|
|
92
|
-
{/each}
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
{/each}
|
|
96
|
-
</div>
|
|
97
|
-
</nav>
|
|
98
|
-
|
|
99
|
-
<!-- Right content area -->
|
|
100
|
-
<div class="flex-1 p-6 lg:p-8 {activeKey === 'roles' || activeKey === 'audit' ? 'max-w-6xl' : 'max-w-3xl'}">
|
|
101
|
-
{#if activeKey === 'profile'}
|
|
102
|
-
<ProfilePage />
|
|
103
|
-
{:else if activeKey === 'appearance'}
|
|
104
|
-
<AppearanceSettings />
|
|
105
|
-
{:else if activeKey === 'roles'}
|
|
106
|
-
<RolesSettings />
|
|
107
|
-
{:else if activeKey === 'audit'}
|
|
108
|
-
<AuditLogViewer />
|
|
109
|
-
{:else if activeKey === 'about'}
|
|
110
|
-
<AboutSettings />
|
|
111
|
-
{/if}
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useShow, getResource } from '@svadmin/core';
|
|
3
|
-
import type { FieldDefinition } from '@svadmin/core';
|
|
4
|
-
import { navigate } from '@svadmin/core/router';
|
|
5
|
-
import { t, getLocale } from '@svadmin/core/i18n';
|
|
6
|
-
import { Button } from './ui/button/index.js';
|
|
7
|
-
import * as Card from './ui/card/index.js';
|
|
8
|
-
import { Skeleton } from './ui/skeleton/index.js';
|
|
9
|
-
import PageHeader from './PageHeader.svelte';
|
|
10
|
-
import { getDisplayComponent } from './fieldComponentMap';
|
|
11
|
-
import ListButton from './buttons/ListButton.svelte';
|
|
12
|
-
import EditButton from './buttons/EditButton.svelte';
|
|
13
|
-
import DeleteButton from './buttons/DeleteButton.svelte';
|
|
14
|
-
import RefreshButton from './buttons/RefreshButton.svelte';
|
|
15
|
-
|
|
16
|
-
let { resourceName, id } = $props<{ resourceName: string; id: string | number }>();
|
|
17
|
-
|
|
18
|
-
const resource = $derived(getResource(resourceName));
|
|
19
|
-
const showFields = $derived(resource.fields.filter(f => f.showInShow !== false));
|
|
20
|
-
|
|
21
|
-
const query = useShow({ get resource() { return resourceName; }, get id() { return id; } });
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<div class="space-y-6">
|
|
25
|
-
<PageHeader title="{resource.label} {t('common.detail')}">
|
|
26
|
-
{#snippet actions()}
|
|
27
|
-
<ListButton resource={resourceName} hideText />
|
|
28
|
-
{#if resource.canEdit !== false}
|
|
29
|
-
<EditButton resource={resourceName} recordItemId={id} hideText />
|
|
30
|
-
{/if}
|
|
31
|
-
{#if resource.canDelete !== false}
|
|
32
|
-
<DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => navigate(`/${resourceName}`)} />
|
|
33
|
-
{/if}
|
|
34
|
-
<RefreshButton resource={resourceName} hideText />
|
|
35
|
-
{/snippet}
|
|
36
|
-
</PageHeader>
|
|
37
|
-
|
|
38
|
-
{#if query.query.isLoading}
|
|
39
|
-
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
40
|
-
<Card.Content class="p-0">
|
|
41
|
-
{#each showFields.slice(0, 6) as _, i}
|
|
42
|
-
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
43
|
-
<Skeleton class="h-4 w-1/2 sm:w-1/4" />
|
|
44
|
-
<Skeleton class="h-4 w-3/4 sm:w-2/5 mt-1 sm:mt-0 sm:ml-auto" />
|
|
45
|
-
</div>
|
|
46
|
-
{/each}
|
|
47
|
-
</Card.Content>
|
|
48
|
-
</Card.Root>
|
|
49
|
-
{:else if query.query.data?.data}
|
|
50
|
-
<Card.Root class="overflow-hidden border-border/40 shadow-sm">
|
|
51
|
-
<Card.Content class="p-0">
|
|
52
|
-
{#each showFields as field, i}
|
|
53
|
-
{@const value = (query.query.data!.data as Record<string, unknown>)[field.key]}
|
|
54
|
-
{@const DisplayComponent = getDisplayComponent(field.type)}
|
|
55
|
-
<div class="flex flex-col sm:flex-row px-4 sm:px-6 py-3 sm:py-4 {i % 2 === 1 ? 'bg-muted/20' : ''}">
|
|
56
|
-
<div class="sm:w-1/3 text-xs sm:text-sm font-medium text-muted-foreground mb-1 sm:mb-0">{field.label}</div>
|
|
57
|
-
<div class="sm:w-2/3 text-sm text-foreground">
|
|
58
|
-
{#if DisplayComponent && value != null}
|
|
59
|
-
<DisplayComponent
|
|
60
|
-
{value}
|
|
61
|
-
options={field.options}
|
|
62
|
-
resourceName={field.resource}
|
|
63
|
-
/>
|
|
64
|
-
{:else}
|
|
65
|
-
{value != null ? String(value) : '—'}
|
|
66
|
-
{/if}
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
{/each}
|
|
70
|
-
</Card.Content>
|
|
71
|
-
</Card.Root>
|
|
72
|
-
{/if}
|
|
73
|
-
</div>
|