@specscreen/backoffice-core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1635 @@
1
+ 'use strict';
2
+
3
+ var React11 = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var lucideReact = require('lucide-react');
6
+ var reactSlot = require('@radix-ui/react-slot');
7
+ var classVarianceAuthority = require('class-variance-authority');
8
+ var clsx = require('clsx');
9
+ var tailwindMerge = require('tailwind-merge');
10
+ var LabelPrimitive = require('@radix-ui/react-label');
11
+ var SelectPrimitive = require('@radix-ui/react-select');
12
+ var reactDom = require('react-dom');
13
+ var core = require('@dnd-kit/core');
14
+ var sortable = require('@dnd-kit/sortable');
15
+ var utilities = require('@dnd-kit/utilities');
16
+ var reactTable = require('@tanstack/react-table');
17
+
18
+ function _interopNamespace(e) {
19
+ if (e && e.__esModule) return e;
20
+ var n = Object.create(null);
21
+ if (e) {
22
+ Object.keys(e).forEach(function (k) {
23
+ if (k !== 'default') {
24
+ var d = Object.getOwnPropertyDescriptor(e, k);
25
+ Object.defineProperty(n, k, d.get ? d : {
26
+ enumerable: true,
27
+ get: function () { return e[k]; }
28
+ });
29
+ }
30
+ });
31
+ }
32
+ n.default = e;
33
+ return Object.freeze(n);
34
+ }
35
+
36
+ var React11__namespace = /*#__PURE__*/_interopNamespace(React11);
37
+ var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
38
+ var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
39
+
40
+ // src/core/auth/AuthContext.tsx
41
+
42
+ // src/core/rbac/evaluator.ts
43
+ function canAccessResource(user, meta) {
44
+ if (!meta) return true;
45
+ const { requiredRoles, requiredPermissions } = meta;
46
+ const hasRoleConstraint = requiredRoles && requiredRoles.length > 0;
47
+ const hasPermissionConstraint = requiredPermissions && requiredPermissions.length > 0;
48
+ if (!hasRoleConstraint && !hasPermissionConstraint) return true;
49
+ if (!user) return false;
50
+ if (hasRoleConstraint) {
51
+ const userRoles = user.roles ?? [];
52
+ const rolesPassed = requiredRoles.some((role) => userRoles.includes(role));
53
+ if (!rolesPassed) return false;
54
+ }
55
+ if (hasPermissionConstraint) {
56
+ const userPermissions = user.permissions ?? [];
57
+ const permissionsPassed = requiredPermissions.every(
58
+ (perm) => userPermissions.includes(perm)
59
+ );
60
+ if (!permissionsPassed) return false;
61
+ }
62
+ return true;
63
+ }
64
+ function evaluateCan(user, permission) {
65
+ if (!user) return false;
66
+ return (user.permissions ?? []).includes(permission);
67
+ }
68
+ function evaluateHasRole(user, role) {
69
+ if (!user) return false;
70
+ return (user.roles ?? []).includes(role);
71
+ }
72
+ function evaluateCanAny(user, permissions) {
73
+ if (!user || permissions.length === 0) return false;
74
+ const userPermissions = user.permissions ?? [];
75
+ return permissions.some((perm) => userPermissions.includes(perm));
76
+ }
77
+ function evaluateCanAll(user, permissions) {
78
+ if (!user || permissions.length === 0) return false;
79
+ const userPermissions = user.permissions ?? [];
80
+ return permissions.every((perm) => userPermissions.includes(perm));
81
+ }
82
+ function authReducer(state, action) {
83
+ switch (action.type) {
84
+ case "LOADING":
85
+ return { ...state, isLoading: true, error: null };
86
+ case "AUTHENTICATED":
87
+ return {
88
+ isLoading: false,
89
+ isAuthenticated: true,
90
+ user: action.user,
91
+ error: null
92
+ };
93
+ case "UNAUTHENTICATED":
94
+ return {
95
+ isLoading: false,
96
+ isAuthenticated: false,
97
+ user: null,
98
+ error: null
99
+ };
100
+ case "ERROR":
101
+ return {
102
+ isLoading: false,
103
+ isAuthenticated: false,
104
+ user: null,
105
+ error: action.error
106
+ };
107
+ case "USER_UPDATED":
108
+ return { ...state, user: action.user };
109
+ default:
110
+ return state;
111
+ }
112
+ }
113
+ var initialState = {
114
+ isLoading: true,
115
+ isAuthenticated: false,
116
+ user: null,
117
+ error: null
118
+ };
119
+ var AuthContext = React11.createContext(null);
120
+ function AuthContextProvider({
121
+ authProvider,
122
+ children
123
+ }) {
124
+ const [state, dispatch] = React11.useReducer(authReducer, initialState);
125
+ React11.useEffect(() => {
126
+ let cancelled = false;
127
+ async function boot() {
128
+ dispatch({ type: "LOADING" });
129
+ try {
130
+ const isAuthenticated = await authProvider.checkAuth();
131
+ if (cancelled) return;
132
+ if (!isAuthenticated) {
133
+ dispatch({ type: "UNAUTHENTICATED" });
134
+ return;
135
+ }
136
+ const user = await authProvider.getUser();
137
+ if (cancelled) return;
138
+ if (user) {
139
+ dispatch({ type: "AUTHENTICATED", user });
140
+ } else {
141
+ dispatch({ type: "UNAUTHENTICATED" });
142
+ }
143
+ } catch {
144
+ if (!cancelled) dispatch({ type: "UNAUTHENTICATED" });
145
+ }
146
+ }
147
+ boot();
148
+ return () => {
149
+ cancelled = true;
150
+ };
151
+ }, [authProvider]);
152
+ const login = React11.useCallback(
153
+ async (params) => {
154
+ dispatch({ type: "LOADING" });
155
+ await authProvider.login(params);
156
+ const user = await authProvider.getUser();
157
+ if (user) {
158
+ dispatch({ type: "AUTHENTICATED", user });
159
+ } else {
160
+ dispatch({ type: "ERROR", error: "Failed to retrieve user after login." });
161
+ }
162
+ },
163
+ [authProvider]
164
+ );
165
+ const logout = React11.useCallback(async () => {
166
+ try {
167
+ await authProvider.logout();
168
+ } catch (err) {
169
+ console.error("[BackofficeApp] Logout error:", err);
170
+ } finally {
171
+ dispatch({ type: "UNAUTHENTICATED" });
172
+ }
173
+ }, [authProvider]);
174
+ const refreshUser = React11.useCallback(async () => {
175
+ const user = await authProvider.getUser();
176
+ if (user) dispatch({ type: "USER_UPDATED", user });
177
+ }, [authProvider]);
178
+ const can = React11.useCallback(
179
+ (permission) => evaluateCan(state.user, permission),
180
+ [state.user]
181
+ );
182
+ const canAny = React11.useCallback(
183
+ (permissions) => evaluateCanAny(state.user, permissions),
184
+ [state.user]
185
+ );
186
+ const canAll = React11.useCallback(
187
+ (permissions) => evaluateCanAll(state.user, permissions),
188
+ [state.user]
189
+ );
190
+ const hasRole = React11.useCallback(
191
+ (role) => evaluateHasRole(state.user, role),
192
+ [state.user]
193
+ );
194
+ const value = React11.useMemo(
195
+ () => ({
196
+ ...state,
197
+ login,
198
+ logout,
199
+ refreshUser,
200
+ can,
201
+ canAny,
202
+ canAll,
203
+ hasRole
204
+ }),
205
+ [state, login, logout, refreshUser, can, canAny, canAll, hasRole]
206
+ );
207
+ return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value, children });
208
+ }
209
+ function useAuthContext() {
210
+ const ctx = React11.useContext(AuthContext);
211
+ if (!ctx) {
212
+ throw new Error("useAuthContext must be used inside <BackofficeApp>.");
213
+ }
214
+ return ctx;
215
+ }
216
+ function LoadingScreen({ message }) {
217
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-screen w-full flex-col items-center justify-center gap-3 bg-background", children: [
218
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-8 animate-spin text-muted-foreground" }),
219
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: message })
220
+ ] });
221
+ }
222
+ function cn(...inputs) {
223
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
224
+ }
225
+ var buttonVariants = classVarianceAuthority.cva(
226
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
227
+ {
228
+ variants: {
229
+ variant: {
230
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
231
+ destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
232
+ outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
233
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
234
+ ghost: "hover:bg-accent hover:text-accent-foreground",
235
+ link: "text-primary underline-offset-4 hover:underline"
236
+ },
237
+ size: {
238
+ default: "h-9 px-4 py-2",
239
+ sm: "h-8 rounded-md px-3 text-xs",
240
+ lg: "h-10 rounded-lg px-6",
241
+ icon: "size-8"
242
+ }
243
+ },
244
+ defaultVariants: {
245
+ variant: "default",
246
+ size: "default"
247
+ }
248
+ }
249
+ );
250
+ var Button = React11__namespace.forwardRef(
251
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
252
+ const Comp = asChild ? reactSlot.Slot : "button";
253
+ return /* @__PURE__ */ jsxRuntime.jsx(
254
+ Comp,
255
+ {
256
+ className: cn(buttonVariants({ variant, size, className })),
257
+ ref,
258
+ ...props
259
+ }
260
+ );
261
+ }
262
+ );
263
+ Button.displayName = "Button";
264
+ var Input = React11__namespace.forwardRef(({ className, type, ...props }, ref) => {
265
+ return /* @__PURE__ */ jsxRuntime.jsx(
266
+ "input",
267
+ {
268
+ type,
269
+ className: cn(
270
+ "flex h-9 w-full rounded-lg border border-input bg-background px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50",
271
+ className
272
+ ),
273
+ ref,
274
+ ...props
275
+ }
276
+ );
277
+ });
278
+ Input.displayName = "Input";
279
+ var Label = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
280
+ LabelPrimitive__namespace.Root,
281
+ {
282
+ ref,
283
+ className: cn(
284
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
285
+ className
286
+ ),
287
+ ...props
288
+ }
289
+ ));
290
+ Label.displayName = LabelPrimitive__namespace.Root.displayName;
291
+ function LoginPage({
292
+ appName = "backoffice",
293
+ logo,
294
+ onLoginSuccess,
295
+ onForgotPassword,
296
+ description
297
+ }) {
298
+ const { login, isLoading, error } = useAuthContext();
299
+ const [email, setEmail] = React11.useState("");
300
+ const [password, setPassword] = React11.useState("");
301
+ const [localError, setLocalError] = React11.useState(null);
302
+ const [attempts, setAttempts] = React11.useState(0);
303
+ const MAX_ATTEMPTS = 10;
304
+ async function handleSubmit(e) {
305
+ e.preventDefault();
306
+ setLocalError(null);
307
+ if (!email.trim() || !password) {
308
+ setLocalError("Please enter your email and password.");
309
+ return;
310
+ }
311
+ setAttempts((n) => n + 1);
312
+ if (attempts >= MAX_ATTEMPTS) {
313
+ setLocalError("Too many failed attempts. Please wait before trying again.");
314
+ return;
315
+ }
316
+ try {
317
+ await login({ email: email.trim(), password });
318
+ setAttempts(0);
319
+ onLoginSuccess?.();
320
+ } catch (err) {
321
+ console.error("[BackofficeApp] Login error:", err);
322
+ setLocalError("Invalid email or password.");
323
+ }
324
+ }
325
+ const displayError = localError ?? (error ? "Authentication error. Please try again." : null);
326
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen w-full bg-background flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-sm rounded-xl border border-border bg-background shadow-xs overflow-hidden p-10", children: [
327
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 pb-8", children: [
328
+ logo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-6", children: logo }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-6 rounded-full bg-foreground flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-background text-xs font-bold", children: "B" }) }),
329
+ /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "text-xl font-semibold text-foreground text-center", children: [
330
+ "Welcome to ",
331
+ appName
332
+ ] }),
333
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground text-center", children: description ?? "Enter your email below to login to your account" })
334
+ ] }),
335
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [
336
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
337
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "email", children: "Email" }),
338
+ /* @__PURE__ */ jsxRuntime.jsx(
339
+ Input,
340
+ {
341
+ id: "email",
342
+ type: "email",
343
+ placeholder: "m@example.com",
344
+ autoComplete: "email",
345
+ required: true,
346
+ value: email,
347
+ onChange: (e) => setEmail(e.target.value),
348
+ disabled: isLoading
349
+ }
350
+ )
351
+ ] }),
352
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
353
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
354
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "password", children: "Password" }),
355
+ onForgotPassword && /* @__PURE__ */ jsxRuntime.jsx(
356
+ "button",
357
+ {
358
+ type: "button",
359
+ onClick: onForgotPassword,
360
+ className: "text-sm text-foreground hover:underline",
361
+ children: "Forgot password?"
362
+ }
363
+ )
364
+ ] }),
365
+ /* @__PURE__ */ jsxRuntime.jsx(
366
+ Input,
367
+ {
368
+ id: "password",
369
+ type: "password",
370
+ autoComplete: "current-password",
371
+ required: true,
372
+ value: password,
373
+ onChange: (e) => setPassword(e.target.value),
374
+ disabled: isLoading
375
+ }
376
+ )
377
+ ] }),
378
+ displayError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive text-center", role: "alert", children: displayError }),
379
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Logging in\u2026" : "Log in" })
380
+ ] }),
381
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 text-sm text-muted-foreground text-center", children: "Don't have an account? Ask an admin for access." })
382
+ ] }) });
383
+ }
384
+ function AuthGuard({
385
+ children,
386
+ loginPageProps,
387
+ renderLogin,
388
+ renderLoading,
389
+ onLoginSuccess
390
+ }) {
391
+ const { isLoading, isAuthenticated, error } = useAuthContext();
392
+ if (isLoading) {
393
+ return renderLoading ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() }) : /* @__PURE__ */ jsxRuntime.jsx(LoadingScreen, {});
394
+ }
395
+ if (!isAuthenticated || error) {
396
+ if (renderLogin) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLogin() });
397
+ return /* @__PURE__ */ jsxRuntime.jsx(
398
+ LoginPage,
399
+ {
400
+ ...loginPageProps,
401
+ onLoginSuccess
402
+ }
403
+ );
404
+ }
405
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
406
+ }
407
+ var DefaultNavLink = ({
408
+ href,
409
+ children,
410
+ className,
411
+ onClick
412
+ }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href, className, onClick, children });
413
+ function SidebarItem({
414
+ label,
415
+ path,
416
+ icon: Icon2,
417
+ isActive,
418
+ NavLink
419
+ }) {
420
+ return /* @__PURE__ */ jsxRuntime.jsxs(
421
+ NavLink,
422
+ {
423
+ href: path,
424
+ isActive,
425
+ className: cn(
426
+ "flex h-8 w-full items-center gap-2 rounded-lg px-2 text-sm text-sidebar-foreground transition-colors",
427
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
428
+ isActive && "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
429
+ ),
430
+ children: [
431
+ Icon2 && /* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "size-4 shrink-0" }),
432
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: label })
433
+ ]
434
+ }
435
+ );
436
+ }
437
+ function Sidebar({
438
+ config,
439
+ currentPath,
440
+ NavLink = DefaultNavLink,
441
+ onLogout
442
+ }) {
443
+ const { user } = useAuthContext();
444
+ const [userMenuOpen, setUserMenuOpen] = React11.useState(false);
445
+ const headerName = config.companyName ?? config.appName;
446
+ const websiteLabel = config.goToWebsite?.label ?? "Go to website";
447
+ const WebsiteIcon = config.goToWebsite?.icon ?? lucideReact.LayoutPanelTop;
448
+ const websiteTarget = config.goToWebsite?.target ?? "_blank";
449
+ const groups = config.sidebarGroups ?? (config.resources ? [{ label: "", resources: config.resources }] : []);
450
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "flex h-full w-[255px] shrink-0 flex-col border-r border-sidebar-border bg-sidebar", children: [
451
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 border-b border-sidebar-border px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-8 w-full items-center gap-2 px-1.5", children: [
452
+ typeof config.logo === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
453
+ "img",
454
+ {
455
+ src: config.logo,
456
+ alt: headerName,
457
+ className: "size-5 shrink-0"
458
+ }
459
+ ) : config.logo ? /* @__PURE__ */ jsxRuntime.jsx(config.logo, { className: "size-5 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-5 shrink-0 rounded-sm bg-foreground flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-background text-[10px] font-bold leading-none", children: headerName.charAt(0).toUpperCase() }) }),
460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-base font-semibold text-foreground", children: headerName })
461
+ ] }) }),
462
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col gap-2 overflow-y-auto", children: groups.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsx(
463
+ FilteredGroup,
464
+ {
465
+ group,
466
+ currentPath,
467
+ NavLink,
468
+ user
469
+ },
470
+ group.label || gi
471
+ )) }),
472
+ config.sidebarFooterLinks && config.sidebarFooterLinks.length > 0 || config.goToWebsite ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 p-2", children: [
473
+ config.sidebarFooterLinks?.map((link) => /* @__PURE__ */ jsxRuntime.jsxs(
474
+ NavLink,
475
+ {
476
+ href: link.path,
477
+ isActive: currentPath === link.path,
478
+ className: cn(
479
+ "flex h-8 w-full items-center gap-2 rounded-lg px-2 text-sm text-sidebar-foreground transition-colors",
480
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
481
+ currentPath === link.path && "bg-sidebar-accent text-sidebar-accent-foreground"
482
+ ),
483
+ children: [
484
+ link.icon && /* @__PURE__ */ jsxRuntime.jsx(link.icon, { className: "size-4 shrink-0" }),
485
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: link.label })
486
+ ]
487
+ },
488
+ link.path
489
+ )),
490
+ config.goToWebsite && /* @__PURE__ */ jsxRuntime.jsxs(
491
+ "a",
492
+ {
493
+ href: config.goToWebsite.url,
494
+ target: websiteTarget,
495
+ rel: websiteTarget === "_blank" ? "noreferrer noopener" : void 0,
496
+ className: cn(
497
+ "flex h-8 w-full items-center gap-2 rounded-lg px-2 text-sm text-sidebar-foreground transition-colors",
498
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
499
+ ),
500
+ children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx(WebsiteIcon, { className: "size-4 shrink-0" }),
502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: websiteLabel })
503
+ ]
504
+ }
505
+ )
506
+ ] }) : null,
507
+ user && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col p-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center gap-2 p-2", children: [
508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-sidebar-foreground", children: user.name ?? user.email.split("@")[0] }),
510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-muted-foreground", children: user.email })
511
+ ] }),
512
+ /* @__PURE__ */ jsxRuntime.jsx(
513
+ "button",
514
+ {
515
+ type: "button",
516
+ className: "size-4 shrink-0 text-muted-foreground hover:text-foreground",
517
+ onClick: () => setUserMenuOpen((v) => !v),
518
+ "aria-label": "User menu",
519
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EllipsisVertical, { className: "size-4" })
520
+ }
521
+ ),
522
+ userMenuOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full right-0 mb-1 w-40 rounded-lg border border-border bg-background shadow-sm py-1 z-50", children: onLogout && /* @__PURE__ */ jsxRuntime.jsx(
523
+ "button",
524
+ {
525
+ type: "button",
526
+ className: "w-full px-3 py-1.5 text-left text-sm text-foreground hover:bg-accent",
527
+ onClick: () => {
528
+ setUserMenuOpen(false);
529
+ onLogout();
530
+ },
531
+ children: "Log out"
532
+ }
533
+ ) })
534
+ ] }) })
535
+ ] });
536
+ }
537
+ function FilteredGroup({
538
+ group,
539
+ currentPath,
540
+ NavLink,
541
+ user
542
+ }) {
543
+ const visibleResources = group.resources.filter((r) => {
544
+ const hideIfUnauthorized = r.meta?.hideIfUnauthorized ?? true;
545
+ if (!hideIfUnauthorized) return true;
546
+ return canAccessResource(user, r.meta);
547
+ });
548
+ if (visibleResources.length === 0) return null;
549
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col p-2", children: [
550
+ group.label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 flex h-8 items-center px-2 opacity-70", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-sidebar-foreground", children: group.label }) }),
551
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: visibleResources.map((resource) => /* @__PURE__ */ jsxRuntime.jsx(
552
+ SidebarItem,
553
+ {
554
+ resource,
555
+ label: resource.label,
556
+ path: resource.path,
557
+ icon: resource.icon,
558
+ isActive: currentPath === resource.path || currentPath?.startsWith(resource.path + "/"),
559
+ NavLink
560
+ },
561
+ resource.name
562
+ )) })
563
+ ] });
564
+ }
565
+ function SidebarToggle({ collapsed, onToggle }) {
566
+ return /* @__PURE__ */ jsxRuntime.jsx(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ onClick: onToggle,
571
+ className: "flex size-7 items-center justify-center rounded-lg hover:bg-accent transition-colors",
572
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
573
+ children: collapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeft, { className: "size-4" })
574
+ }
575
+ );
576
+ }
577
+ function AppShell({
578
+ config,
579
+ currentPath,
580
+ NavLink,
581
+ headerActionLabel,
582
+ onHeaderAction,
583
+ children
584
+ }) {
585
+ const { logout } = useAuthContext();
586
+ const [sidebarCollapsed, setSidebarCollapsed] = React11.useState(false);
587
+ const allResources = config.resources ?? config.sidebarGroups?.flatMap((g) => g.resources) ?? [];
588
+ const activeResource = allResources.find(
589
+ (r) => currentPath === r.path || currentPath?.startsWith(r.path + "/")
590
+ );
591
+ const pageTitle = activeResource?.label ?? config.appName;
592
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-screen w-full overflow-hidden bg-background", children: [
593
+ !sidebarCollapsed && /* @__PURE__ */ jsxRuntime.jsx(
594
+ Sidebar,
595
+ {
596
+ config,
597
+ currentPath,
598
+ NavLink,
599
+ onLogout: logout
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden min-w-0", children: [
603
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex shrink-0 items-center justify-between border-b border-border px-6 py-3", children: [
604
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ "button",
607
+ {
608
+ type: "button",
609
+ onClick: () => setSidebarCollapsed((v) => !v),
610
+ className: "flex size-7 items-center justify-center rounded-lg hover:bg-accent transition-colors",
611
+ "aria-label": "Toggle sidebar",
612
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeft, { className: "size-4 text-foreground" })
613
+ }
614
+ ),
615
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-2 h-[17px] w-px bg-border", "aria-hidden": true }),
616
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-foreground", children: pageTitle })
617
+ ] }),
618
+ headerActionLabel && onHeaderAction && /* @__PURE__ */ jsxRuntime.jsxs(
619
+ "button",
620
+ {
621
+ type: "button",
622
+ onClick: onHeaderAction,
623
+ className: cn(
624
+ "inline-flex items-center gap-1.5 rounded-lg bg-foreground px-2.5 py-1.5",
625
+ "text-sm font-medium text-background shadow-xs transition-opacity hover:opacity-90"
626
+ ),
627
+ children: [
628
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }),
629
+ headerActionLabel
630
+ ]
631
+ }
632
+ )
633
+ ] }),
634
+ /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto p-6", children })
635
+ ] })
636
+ ] });
637
+ }
638
+ function BackofficeApp({
639
+ config,
640
+ authProvider,
641
+ NavLink,
642
+ currentPath,
643
+ loginPageProps,
644
+ renderLogin,
645
+ renderLoading,
646
+ headerActionLabel,
647
+ onHeaderAction,
648
+ onLoginSuccess,
649
+ children
650
+ }) {
651
+ return /* @__PURE__ */ jsxRuntime.jsx(AuthContextProvider, { authProvider, children: /* @__PURE__ */ jsxRuntime.jsx(
652
+ AuthGuard,
653
+ {
654
+ loginPageProps,
655
+ renderLogin,
656
+ renderLoading,
657
+ onLoginSuccess,
658
+ children: /* @__PURE__ */ jsxRuntime.jsx(
659
+ AppShell,
660
+ {
661
+ config,
662
+ currentPath,
663
+ NavLink,
664
+ headerActionLabel,
665
+ onHeaderAction,
666
+ children: children ?? config.children
667
+ }
668
+ )
669
+ }
670
+ ) });
671
+ }
672
+
673
+ // src/hooks/useAuth.ts
674
+ function useAuth() {
675
+ const { isLoading, isAuthenticated, user, error, login, logout, refreshUser } = useAuthContext();
676
+ return { isLoading, isAuthenticated, user, error, login, logout, refreshUser };
677
+ }
678
+
679
+ // src/hooks/usePermissions.ts
680
+ function usePermissions() {
681
+ const { user, can, canAny, canAll, hasRole } = useAuthContext();
682
+ return {
683
+ roles: user?.roles ?? [],
684
+ permissions: user?.permissions ?? [],
685
+ can,
686
+ canAny,
687
+ canAll,
688
+ hasRole
689
+ };
690
+ }
691
+ function AccessDenied({
692
+ message = "You do not have permission to access this page.",
693
+ actionLabel,
694
+ onAction
695
+ }) {
696
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full w-full flex-col items-center justify-center gap-6 p-10 text-center", children: [
697
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex size-14 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldOff, { className: "size-7 text-muted-foreground" }) }),
698
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
699
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Access Denied" }),
700
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: message })
701
+ ] }),
702
+ actionLabel && onAction && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", onClick: onAction, children: actionLabel })
703
+ ] });
704
+ }
705
+ function ResourceGuard({ meta, children, fallback }) {
706
+ const { user, isLoading } = useAuthContext();
707
+ if (isLoading) return null;
708
+ if (!canAccessResource(user, meta)) {
709
+ return fallback !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : /* @__PURE__ */ jsxRuntime.jsx(AccessDenied, {});
710
+ }
711
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
712
+ }
713
+ function Can({ permission, role, permissions, children, fallback = null }) {
714
+ const { can, hasRole, canAll } = useAuthContext();
715
+ let granted = true;
716
+ if (permission) {
717
+ granted = can(permission);
718
+ } else if (role) {
719
+ granted = hasRole(role);
720
+ } else if (permissions && permissions.length > 0) {
721
+ granted = canAll(permissions);
722
+ }
723
+ return granted ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
724
+ }
725
+ var Select = SelectPrimitive__namespace.Root;
726
+ var SelectGroup = SelectPrimitive__namespace.Group;
727
+ var SelectValue = SelectPrimitive__namespace.Value;
728
+ var SelectTrigger = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
729
+ SelectPrimitive__namespace.Trigger,
730
+ {
731
+ ref,
732
+ className: cn(
733
+ "flex h-9 w-full items-center justify-between rounded-lg border border-input bg-background px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
734
+ className
735
+ ),
736
+ ...props,
737
+ children: [
738
+ children,
739
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }) })
740
+ ]
741
+ }
742
+ ));
743
+ SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
744
+ var SelectScrollUpButton = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
745
+ SelectPrimitive__namespace.ScrollUpButton,
746
+ {
747
+ ref,
748
+ className: cn(
749
+ "flex cursor-pointer items-center justify-center py-1",
750
+ className
751
+ ),
752
+ ...props,
753
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
754
+ }
755
+ ));
756
+ SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
757
+ var SelectScrollDownButton = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
758
+ SelectPrimitive__namespace.ScrollDownButton,
759
+ {
760
+ ref,
761
+ className: cn(
762
+ "flex cursor-pointer items-center justify-center py-1",
763
+ className
764
+ ),
765
+ ...props,
766
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
767
+ }
768
+ ));
769
+ SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
770
+ var SelectContent = React11__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
771
+ SelectPrimitive__namespace.Content,
772
+ {
773
+ ref,
774
+ className: cn(
775
+ "relative z-50 max-h-96 min-w-32 overflow-hidden rounded-lg border border-border bg-background shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
776
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
777
+ className
778
+ ),
779
+ position,
780
+ ...props,
781
+ children: /* @__PURE__ */ jsxRuntime.jsx(
782
+ SelectPrimitive__namespace.Viewport,
783
+ {
784
+ className: cn(
785
+ "p-1",
786
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
787
+ ),
788
+ children
789
+ }
790
+ )
791
+ }
792
+ ) }));
793
+ SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
794
+ var SelectItem = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
795
+ SelectPrimitive__namespace.Item,
796
+ {
797
+ ref,
798
+ className: cn(
799
+ "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-base outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
800
+ className
801
+ ),
802
+ ...props,
803
+ children: [
804
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
805
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
806
+ ]
807
+ }
808
+ ));
809
+ SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
810
+ var SelectSeparator = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
811
+ SelectPrimitive__namespace.Separator,
812
+ {
813
+ ref,
814
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
815
+ ...props
816
+ }
817
+ ));
818
+ SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
819
+ var WIDTH_CLASS_MAP = {
820
+ sm: "max-w-md",
821
+ md: "max-w-lg",
822
+ lg: "max-w-2xl",
823
+ xl: "max-w-4xl",
824
+ "2xl": "max-w-6xl",
825
+ full: "max-w-[95vw]"
826
+ };
827
+ function RenderModal({
828
+ open,
829
+ onOpenChange,
830
+ title,
831
+ description,
832
+ children,
833
+ renderContent,
834
+ footer,
835
+ width = "lg",
836
+ closeOnOverlayClick = true,
837
+ showCloseButton = true,
838
+ overlayClassName,
839
+ panelClassName,
840
+ contentClassName
841
+ }) {
842
+ React11__namespace.default.useEffect(() => {
843
+ if (!open) return;
844
+ function handleEscape(event) {
845
+ if (event.key === "Escape") {
846
+ onOpenChange(false);
847
+ }
848
+ }
849
+ window.addEventListener("keydown", handleEscape);
850
+ return () => window.removeEventListener("keydown", handleEscape);
851
+ }, [open, onOpenChange]);
852
+ if (!open) return null;
853
+ const hasHeader = Boolean(title || description || showCloseButton);
854
+ const content = renderContent ? renderContent() : children;
855
+ return reactDom.createPortal(
856
+ /* @__PURE__ */ jsxRuntime.jsx(
857
+ "div",
858
+ {
859
+ className: cn(
860
+ "fixed inset-0 z-[2147483647] flex items-center justify-center bg-black/30 p-4 backdrop-blur-[1px]",
861
+ overlayClassName
862
+ ),
863
+ onMouseDown: (event) => {
864
+ if (!closeOnOverlayClick) return;
865
+ if (event.target === event.currentTarget) {
866
+ onOpenChange(false);
867
+ }
868
+ },
869
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
870
+ "div",
871
+ {
872
+ role: "dialog",
873
+ "aria-modal": "true",
874
+ className: cn(
875
+ "w-full overflow-hidden rounded-xl border border-border bg-background shadow-xl",
876
+ WIDTH_CLASS_MAP[width],
877
+ panelClassName
878
+ ),
879
+ children: [
880
+ hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start justify-between gap-4 px-5 py-4", children: [
881
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
882
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "truncate text-lg font-semibold text-foreground", children: title }),
883
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })
884
+ ] }),
885
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
886
+ "button",
887
+ {
888
+ type: "button",
889
+ "aria-label": "Close modal",
890
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
891
+ onClick: () => onOpenChange(false),
892
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
893
+ }
894
+ )
895
+ ] }),
896
+ /* @__PURE__ */ jsxRuntime.jsx(
897
+ "div",
898
+ {
899
+ className: cn(
900
+ "max-h-[80vh] overflow-auto px-5 py-4",
901
+ contentClassName
902
+ ),
903
+ children: content
904
+ }
905
+ ),
906
+ footer && /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "px-5 py-4", children: footer })
907
+ ]
908
+ }
909
+ )
910
+ }
911
+ ),
912
+ document.body
913
+ );
914
+ }
915
+ var DEFAULT_ACCENT_OPTIONS = [
916
+ { value: "neutral", label: "Neutral", previewClassName: "bg-foreground" },
917
+ { value: "blue", label: "Blue", previewClassName: "bg-blue-600" },
918
+ { value: "green", label: "Green", previewClassName: "bg-emerald-600" },
919
+ { value: "orange", label: "Orange", previewClassName: "bg-orange-500" }
920
+ ];
921
+ function SettingsModal({
922
+ open,
923
+ onOpenChange,
924
+ mode,
925
+ onModeChange,
926
+ accent,
927
+ onAccentChange,
928
+ accentOptions = DEFAULT_ACCENT_OPTIONS,
929
+ personalizationContent,
930
+ usersContent,
931
+ defaultSection = "general",
932
+ width = "xl",
933
+ closeOnOverlayClick = true,
934
+ overlayClassName,
935
+ panelClassName,
936
+ contentClassName,
937
+ sidebarClassName,
938
+ bodyClassName
939
+ }) {
940
+ const [section, setSection] = React11__namespace.default.useState(defaultSection);
941
+ React11__namespace.default.useEffect(() => {
942
+ if (open) {
943
+ setSection(defaultSection);
944
+ }
945
+ }, [open, defaultSection]);
946
+ if (!open) return null;
947
+ return /* @__PURE__ */ jsxRuntime.jsx(
948
+ RenderModal,
949
+ {
950
+ open,
951
+ onOpenChange,
952
+ width,
953
+ closeOnOverlayClick,
954
+ showCloseButton: false,
955
+ overlayClassName,
956
+ contentClassName: cn(
957
+ "relative h-full overflow-hidden p-0",
958
+ contentClassName
959
+ ),
960
+ panelClassName: cn("h-[500px] max-w-[800px]", panelClassName),
961
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full", children: [
962
+ /* @__PURE__ */ jsxRuntime.jsx(
963
+ "button",
964
+ {
965
+ type: "button",
966
+ onClick: () => onOpenChange(false),
967
+ className: "absolute right-4 top-4 z-10 inline-flex size-8 items-center justify-center rounded-md text-foreground/70 transition-colors hover:bg-accent hover:text-foreground",
968
+ "aria-label": "Close settings",
969
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
970
+ }
971
+ ),
972
+ /* @__PURE__ */ jsxRuntime.jsxs(
973
+ "aside",
974
+ {
975
+ className: cn(
976
+ "flex h-full w-64 shrink-0 flex-col gap-1 overflow-y-auto bg-sidebar p-2",
977
+ sidebarClassName
978
+ ),
979
+ children: [
980
+ /* @__PURE__ */ jsxRuntime.jsx(
981
+ SettingsSectionButton,
982
+ {
983
+ icon: lucideReact.Settings,
984
+ label: "General",
985
+ active: section === "general",
986
+ onClick: () => setSection("general")
987
+ }
988
+ ),
989
+ /* @__PURE__ */ jsxRuntime.jsx(
990
+ SettingsSectionButton,
991
+ {
992
+ icon: lucideReact.CircleDot,
993
+ label: "Personalization",
994
+ active: section === "personalization",
995
+ onClick: () => setSection("personalization")
996
+ }
997
+ ),
998
+ /* @__PURE__ */ jsxRuntime.jsx(
999
+ SettingsSectionButton,
1000
+ {
1001
+ icon: lucideReact.UserCog,
1002
+ label: "Users",
1003
+ active: section === "users",
1004
+ onClick: () => setSection("users")
1005
+ }
1006
+ )
1007
+ ]
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-w-0 flex-1 flex-col", bodyClassName), children: [
1011
+ /* @__PURE__ */ jsxRuntime.jsx("header", { className: "flex h-16 items-center pr-12", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-4", children: [
1012
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Settings" }),
1013
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-3.5 text-muted-foreground" }),
1014
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground capitalize", children: section })
1015
+ ] }) }),
1016
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto px-4 pb-4", children: [
1017
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px w-full bg-border" }),
1018
+ section === "general" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
1019
+ /* @__PURE__ */ jsxRuntime.jsx(SettingsRow, { label: "Appearance", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-flex items-center", children: [
1020
+ /* @__PURE__ */ jsxRuntime.jsxs(
1021
+ "select",
1022
+ {
1023
+ value: mode,
1024
+ onChange: (e) => onModeChange(e.target.value),
1025
+ className: "h-8 appearance-none rounded-lg px-2.5 pr-7 text-sm font-medium text-foreground outline-none transition-colors hover:bg-accent",
1026
+ children: [
1027
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "light", children: "Light" }),
1028
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dark", children: "Dark" })
1029
+ ]
1030
+ }
1031
+ ),
1032
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2 size-4 text-foreground" })
1033
+ ] }) }),
1034
+ /* @__PURE__ */ jsxRuntime.jsx(SettingsRow, { label: "Accent color", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-flex items-center", children: [
1035
+ /* @__PURE__ */ jsxRuntime.jsx(
1036
+ "select",
1037
+ {
1038
+ value: accent,
1039
+ onChange: (e) => onAccentChange(e.target.value),
1040
+ className: "h-8 appearance-none rounded-lg pl-8 pr-7 text-sm font-medium text-foreground outline-none transition-colors hover:bg-accent",
1041
+ children: accentOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
1042
+ }
1043
+ ),
1044
+ /* @__PURE__ */ jsxRuntime.jsx(
1045
+ "span",
1046
+ {
1047
+ className: cn(
1048
+ "pointer-events-none absolute left-2.5 inline-flex size-2 rounded-full",
1049
+ accentOptions.find((o) => o.value === accent)?.previewClassName ?? "bg-foreground"
1050
+ )
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2 size-4 text-foreground" })
1054
+ ] }) })
1055
+ ] }),
1056
+ section === "personalization" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3", children: personalizationContent }),
1057
+ section === "users" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3", children: usersContent })
1058
+ ] })
1059
+ ] })
1060
+ ] })
1061
+ }
1062
+ );
1063
+ }
1064
+ function SettingsSectionButton({
1065
+ icon: Icon2,
1066
+ label,
1067
+ active,
1068
+ onClick
1069
+ }) {
1070
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1071
+ "button",
1072
+ {
1073
+ type: "button",
1074
+ onClick,
1075
+ className: cn(
1076
+ "flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-sm text-sidebar-foreground",
1077
+ "transition-colors hover:bg-sidebar-accent",
1078
+ active && "bg-sidebar-accent font-medium"
1079
+ ),
1080
+ children: [
1081
+ /* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "size-4 shrink-0" }),
1082
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label })
1083
+ ]
1084
+ }
1085
+ );
1086
+ }
1087
+ function SettingsRow({ label, children }) {
1088
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1089
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-3", children: [
1090
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: label }),
1091
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children })
1092
+ ] }),
1093
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px w-full bg-border" })
1094
+ ] });
1095
+ }
1096
+ function TableActionMenu({
1097
+ items,
1098
+ ariaLabel = "Open actions"
1099
+ }) {
1100
+ const [open, setOpen] = React11__namespace.default.useState(false);
1101
+ const [confirmingItem, setConfirmingItem] = React11__namespace.default.useState(null);
1102
+ const [menuPosition, setMenuPosition] = React11__namespace.default.useState({ top: 0, left: 0 });
1103
+ const dropdownRef = React11__namespace.default.useRef(null);
1104
+ const triggerRef = React11__namespace.default.useRef(null);
1105
+ const getConfirmOptions = React11__namespace.default.useCallback(
1106
+ (item) => {
1107
+ if (item.confirm === false) return null;
1108
+ if (typeof item.confirm === "object") return item.confirm;
1109
+ if (item.confirm || item.variant === "destructive") {
1110
+ return {};
1111
+ }
1112
+ return null;
1113
+ },
1114
+ []
1115
+ );
1116
+ const confirmOptions = confirmingItem ? getConfirmOptions(confirmingItem) : null;
1117
+ const confirmTitle = confirmOptions?.title ?? (confirmingItem?.variant === "destructive" ? "Confirm deletion" : "Confirm action");
1118
+ const confirmDescription = confirmOptions?.description ?? (confirmingItem?.variant === "destructive" ? "This action cannot be undone." : "Please confirm that you want to continue.");
1119
+ const confirmLabel = confirmOptions?.confirmLabel ?? "OK";
1120
+ const cancelLabel = confirmOptions?.cancelLabel ?? "Cancel";
1121
+ const handleItemSelect = React11__namespace.default.useCallback(
1122
+ (item) => {
1123
+ const nextConfirmOptions = getConfirmOptions(item);
1124
+ if (nextConfirmOptions) {
1125
+ setConfirmingItem(item);
1126
+ setOpen(false);
1127
+ return;
1128
+ }
1129
+ item.onSelect();
1130
+ setOpen(false);
1131
+ },
1132
+ [getConfirmOptions]
1133
+ );
1134
+ const handleConfirm = React11__namespace.default.useCallback(() => {
1135
+ if (!confirmingItem) return;
1136
+ confirmingItem.onSelect();
1137
+ setConfirmingItem(null);
1138
+ }, [confirmingItem]);
1139
+ const updateMenuPosition = React11__namespace.default.useCallback(() => {
1140
+ if (!triggerRef.current) return;
1141
+ const rect = triggerRef.current.getBoundingClientRect();
1142
+ setMenuPosition({
1143
+ top: rect.bottom + 4,
1144
+ left: rect.right
1145
+ });
1146
+ }, []);
1147
+ React11__namespace.default.useEffect(() => {
1148
+ function handleOutsideClick(event) {
1149
+ const target = event.target;
1150
+ if (dropdownRef.current?.contains(target)) return;
1151
+ if (triggerRef.current?.contains(target)) return;
1152
+ setOpen(false);
1153
+ }
1154
+ function handleEscape(event) {
1155
+ if (event.key === "Escape") {
1156
+ if (confirmingItem) {
1157
+ setConfirmingItem(null);
1158
+ return;
1159
+ }
1160
+ setOpen(false);
1161
+ }
1162
+ }
1163
+ if (open || confirmingItem) {
1164
+ updateMenuPosition();
1165
+ document.addEventListener("mousedown", handleOutsideClick);
1166
+ window.addEventListener("resize", updateMenuPosition);
1167
+ window.addEventListener("scroll", updateMenuPosition, true);
1168
+ document.addEventListener("keydown", handleEscape);
1169
+ }
1170
+ return () => {
1171
+ document.removeEventListener("mousedown", handleOutsideClick);
1172
+ window.removeEventListener("resize", updateMenuPosition);
1173
+ window.removeEventListener("scroll", updateMenuPosition, true);
1174
+ document.removeEventListener("keydown", handleEscape);
1175
+ };
1176
+ }, [confirmingItem, open, updateMenuPosition]);
1177
+ const visibleItems = items.filter(Boolean);
1178
+ if (visibleItems.length === 0) return null;
1179
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-flex", children: [
1180
+ /* @__PURE__ */ jsxRuntime.jsx(
1181
+ "button",
1182
+ {
1183
+ ref: triggerRef,
1184
+ type: "button",
1185
+ onClick: () => setOpen((v) => !v),
1186
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
1187
+ "aria-haspopup": "menu",
1188
+ "aria-expanded": open,
1189
+ "aria-label": ariaLabel,
1190
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-4" })
1191
+ }
1192
+ ),
1193
+ open && reactDom.createPortal(
1194
+ /* @__PURE__ */ jsxRuntime.jsx(
1195
+ "div",
1196
+ {
1197
+ ref: dropdownRef,
1198
+ role: "menu",
1199
+ className: "fixed z-[2147483647] min-w-[120px] -translate-x-full rounded-lg border border-border bg-popover p-1 shadow-md",
1200
+ style: { top: menuPosition.top, left: menuPosition.left },
1201
+ children: visibleItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1202
+ "button",
1203
+ {
1204
+ type: "button",
1205
+ role: "menuitem",
1206
+ disabled: item.disabled,
1207
+ onClick: () => handleItemSelect(item),
1208
+ className: cn(
1209
+ "flex w-full items-center rounded-md px-2 py-1.5 text-left text-sm transition-colors",
1210
+ "hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
1211
+ item.variant === "destructive" ? "text-destructive" : "text-popover-foreground"
1212
+ ),
1213
+ children: item.label
1214
+ },
1215
+ item.key
1216
+ ))
1217
+ }
1218
+ ),
1219
+ document.body
1220
+ ),
1221
+ confirmingItem && reactDom.createPortal(
1222
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[2147483647] flex items-center justify-center bg-neutral-950/30 p-4 backdrop-blur-[1.5px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-xl border border-border bg-background p-6 shadow-lg", children: [
1223
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1224
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-foreground", children: confirmTitle }),
1225
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: confirmDescription })
1226
+ ] }),
1227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [
1228
+ /* @__PURE__ */ jsxRuntime.jsx(
1229
+ Button,
1230
+ {
1231
+ variant: "outline",
1232
+ onClick: () => setConfirmingItem(null),
1233
+ children: cancelLabel
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ jsxRuntime.jsx(
1237
+ Button,
1238
+ {
1239
+ variant: confirmingItem.variant === "destructive" ? "destructive" : "default",
1240
+ onClick: handleConfirm,
1241
+ children: confirmLabel
1242
+ }
1243
+ )
1244
+ ] })
1245
+ ] }) }),
1246
+ document.body
1247
+ )
1248
+ ] });
1249
+ }
1250
+ var toneClasses = {
1251
+ neutral: "bg-secondary text-secondary-foreground",
1252
+ success: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300",
1253
+ danger: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300",
1254
+ warning: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300"
1255
+ };
1256
+ function TableBadge({
1257
+ children,
1258
+ tone = "neutral",
1259
+ className
1260
+ }) {
1261
+ return /* @__PURE__ */ jsxRuntime.jsx(
1262
+ "span",
1263
+ {
1264
+ className: cn(
1265
+ "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
1266
+ toneClasses[tone],
1267
+ className
1268
+ ),
1269
+ children
1270
+ }
1271
+ );
1272
+ }
1273
+ var Table = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
1274
+ "table",
1275
+ {
1276
+ ref,
1277
+ className: cn("w-full caption-bottom text-sm bg-background", className),
1278
+ ...props
1279
+ }
1280
+ ) }));
1281
+ Table.displayName = "Table";
1282
+ var TableHeader = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1283
+ "thead",
1284
+ {
1285
+ ref,
1286
+ className: cn("bg-muted/50 [&_tr]:border-b", className),
1287
+ ...props
1288
+ }
1289
+ ));
1290
+ TableHeader.displayName = "TableHeader";
1291
+ var TableBody = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1292
+ "tbody",
1293
+ {
1294
+ ref,
1295
+ className: cn("[&_tr:last-child]:border-0", className),
1296
+ ...props
1297
+ }
1298
+ ));
1299
+ TableBody.displayName = "TableBody";
1300
+ var TableFooter = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1301
+ "tfoot",
1302
+ {
1303
+ ref,
1304
+ className: cn(
1305
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
1306
+ className
1307
+ ),
1308
+ ...props
1309
+ }
1310
+ ));
1311
+ TableFooter.displayName = "TableFooter";
1312
+ var TableRow = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1313
+ "tr",
1314
+ {
1315
+ ref,
1316
+ className: cn("h-[53px] border-b transition-colors hover:bg-muted/30 data-[state=selected]:bg-muted", className),
1317
+ ...props
1318
+ }
1319
+ ));
1320
+ TableRow.displayName = "TableRow";
1321
+ var TableHead = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1322
+ "th",
1323
+ {
1324
+ ref,
1325
+ className: cn(
1326
+ "h-10 px-2 text-left align-middle text-sm font-medium text-foreground",
1327
+ className
1328
+ ),
1329
+ ...props
1330
+ }
1331
+ ));
1332
+ TableHead.displayName = "TableHead";
1333
+ var TableCell = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1334
+ "td",
1335
+ {
1336
+ ref,
1337
+ className: cn("px-2 py-2 align-middle text-sm", className),
1338
+ ...props
1339
+ }
1340
+ ));
1341
+ TableCell.displayName = "TableCell";
1342
+ var TableCaption = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1343
+ "caption",
1344
+ {
1345
+ ref,
1346
+ className: cn("mt-4 text-sm text-muted-foreground", className),
1347
+ ...props
1348
+ }
1349
+ ));
1350
+ TableCaption.displayName = "TableCaption";
1351
+ function SortableDataTableRow({
1352
+ row,
1353
+ rowId
1354
+ }) {
1355
+ const {
1356
+ attributes,
1357
+ listeners,
1358
+ setNodeRef,
1359
+ transform,
1360
+ transition,
1361
+ isDragging
1362
+ } = sortable.useSortable({ id: rowId });
1363
+ const verticalOnlyTransform = transform ? {
1364
+ ...transform,
1365
+ x: 0
1366
+ } : null;
1367
+ return /* @__PURE__ */ jsxRuntime.jsx(
1368
+ TableRow,
1369
+ {
1370
+ ref: setNodeRef,
1371
+ "data-state": row.getIsSelected() && "selected",
1372
+ className: "cursor-grab active:cursor-grabbing",
1373
+ style: {
1374
+ transform: utilities.CSS.Transform.toString(verticalOnlyTransform),
1375
+ transition,
1376
+ opacity: isDragging ? 0.5 : 1,
1377
+ position: "relative",
1378
+ zIndex: isDragging ? 1 : 0,
1379
+ willChange: "transform",
1380
+ touchAction: "none"
1381
+ },
1382
+ ...attributes,
1383
+ ...listeners,
1384
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsxRuntime.jsx(TableCell, { children: reactTable.flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
1385
+ }
1386
+ );
1387
+ }
1388
+ function DataTable({
1389
+ columns,
1390
+ data,
1391
+ className,
1392
+ emptyMessage = "No results.",
1393
+ rowDragOptions
1394
+ }) {
1395
+ const sensors = core.useSensors(
1396
+ core.useSensor(core.PointerSensor, {
1397
+ activationConstraint: {
1398
+ distance: 6
1399
+ }
1400
+ }),
1401
+ core.useSensor(core.KeyboardSensor, {
1402
+ coordinateGetter: sortable.sortableKeyboardCoordinates
1403
+ })
1404
+ );
1405
+ const table = reactTable.useReactTable({
1406
+ data,
1407
+ columns,
1408
+ getCoreRowModel: reactTable.getCoreRowModel()
1409
+ });
1410
+ const rowIds = React11__namespace.useMemo(
1411
+ () => rowDragOptions ? data.map((row) => rowDragOptions.getRowId(row)) : [],
1412
+ [data, rowDragOptions]
1413
+ );
1414
+ const handleDragEnd = React11__namespace.useCallback(
1415
+ (event) => {
1416
+ const { active, over } = event;
1417
+ if (!rowDragOptions || !over || active.id === over.id) {
1418
+ return;
1419
+ }
1420
+ const oldIndex = data.findIndex(
1421
+ (row) => rowDragOptions.getRowId(row) === String(active.id)
1422
+ );
1423
+ const newIndex = data.findIndex(
1424
+ (row) => rowDragOptions.getRowId(row) === String(over.id)
1425
+ );
1426
+ if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) {
1427
+ return;
1428
+ }
1429
+ rowDragOptions.onReorder(sortable.arrayMove(data, oldIndex, newIndex));
1430
+ },
1431
+ [data, rowDragOptions]
1432
+ );
1433
+ return /* @__PURE__ */ jsxRuntime.jsx(
1434
+ core.DndContext,
1435
+ {
1436
+ sensors: rowDragOptions ? sensors : void 0,
1437
+ collisionDetection: core.closestCenter,
1438
+ onDragEnd: rowDragOptions ? handleDragEnd : void 0,
1439
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("rounded-md border", className), children: /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
1440
+ /* @__PURE__ */ jsxRuntime.jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsxRuntime.jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsxRuntime.jsx(TableHead, { children: header.isPlaceholder ? null : reactTable.flexRender(
1441
+ header.column.columnDef.header,
1442
+ header.getContext()
1443
+ ) }, header.id)) }, headerGroup.id)) }),
1444
+ /* @__PURE__ */ jsxRuntime.jsx(TableBody, { children: table.getRowModel().rows.length ? rowDragOptions ? /* @__PURE__ */ jsxRuntime.jsx(
1445
+ sortable.SortableContext,
1446
+ {
1447
+ items: rowIds,
1448
+ strategy: sortable.verticalListSortingStrategy,
1449
+ children: table.getRowModel().rows.map((row) => {
1450
+ const rowId = rowDragOptions.getRowId(row.original);
1451
+ return /* @__PURE__ */ jsxRuntime.jsx(
1452
+ SortableDataTableRow,
1453
+ {
1454
+ row,
1455
+ rowId
1456
+ },
1457
+ rowId
1458
+ );
1459
+ })
1460
+ }
1461
+ ) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsxRuntime.jsx(
1462
+ TableRow,
1463
+ {
1464
+ "data-state": row.getIsSelected() && "selected",
1465
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsxRuntime.jsx(TableCell, { children: reactTable.flexRender(
1466
+ cell.column.columnDef.cell,
1467
+ cell.getContext()
1468
+ ) }, cell.id))
1469
+ },
1470
+ row.id
1471
+ )) : /* @__PURE__ */ jsxRuntime.jsx(TableRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
1472
+ TableCell,
1473
+ {
1474
+ colSpan: columns.length,
1475
+ className: "h-24 text-center",
1476
+ children: emptyMessage
1477
+ }
1478
+ ) }) })
1479
+ ] }) })
1480
+ }
1481
+ );
1482
+ }
1483
+ function TablePagination({
1484
+ totalRows,
1485
+ selectedRows,
1486
+ page,
1487
+ pageSize,
1488
+ pageSizeOptions = [10, 20, 50, 100, "all"],
1489
+ onPageChange,
1490
+ onPageSizeChange,
1491
+ className
1492
+ }) {
1493
+ const safePageSize = Math.max(1, pageSize);
1494
+ const totalPages = Math.max(1, Math.ceil(totalRows / safePageSize));
1495
+ const safePage = Math.min(Math.max(1, page), totalPages);
1496
+ const canGoPrev = safePage > 1;
1497
+ const canGoNext = safePage < totalPages;
1498
+ const hasAllOption = pageSizeOptions.includes("all");
1499
+ const currentPageSizeValue = hasAllOption && totalRows > 0 && safePageSize === totalRows ? "all" : String(safePageSize);
1500
+ const selectedCount = selectedRows ?? 0;
1501
+ const rowsText = `${selectedCount} of ${totalRows} row(s) selected.`;
1502
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex h-9 w-full items-center justify-between gap-2", className), children: [
1503
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: rowsText }),
1504
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-8 text-foreground", children: [
1505
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1506
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pr-2 text-sm font-medium text-foreground", children: "Rows per page" }),
1507
+ /* @__PURE__ */ jsxRuntime.jsx(
1508
+ "select",
1509
+ {
1510
+ value: currentPageSizeValue,
1511
+ onChange: (event) => {
1512
+ const nextValue = event.target.value;
1513
+ if (nextValue === "all") {
1514
+ onPageSizeChange(Math.max(1, totalRows));
1515
+ return;
1516
+ }
1517
+ onPageSizeChange(Number(nextValue));
1518
+ },
1519
+ className: "h-9 w-20 rounded-md border border-input bg-background px-3 text-sm shadow-sm",
1520
+ "aria-label": "Rows per page",
1521
+ children: pageSizeOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: String(option), children: option === "all" ? "All" : option }, String(option)))
1522
+ }
1523
+ )
1524
+ ] }),
1525
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pr-2 text-sm font-medium", children: [
1526
+ "Page ",
1527
+ safePage,
1528
+ " of ",
1529
+ totalPages
1530
+ ] }),
1531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1532
+ /* @__PURE__ */ jsxRuntime.jsx(
1533
+ IconButton,
1534
+ {
1535
+ label: "Previous page",
1536
+ disabled: !canGoPrev,
1537
+ onClick: () => onPageChange(safePage - 1),
1538
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4" })
1539
+ }
1540
+ ),
1541
+ /* @__PURE__ */ jsxRuntime.jsx(
1542
+ IconButton,
1543
+ {
1544
+ label: "First page",
1545
+ disabled: !canGoPrev,
1546
+ onClick: () => onPageChange(1),
1547
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsLeft, { className: "size-4" })
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ jsxRuntime.jsx(
1551
+ IconButton,
1552
+ {
1553
+ label: "Next page",
1554
+ disabled: !canGoNext,
1555
+ onClick: () => onPageChange(safePage + 1),
1556
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4" })
1557
+ }
1558
+ ),
1559
+ /* @__PURE__ */ jsxRuntime.jsx(
1560
+ IconButton,
1561
+ {
1562
+ label: "Last page",
1563
+ disabled: !canGoNext,
1564
+ onClick: () => onPageChange(totalPages),
1565
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsRight, { className: "size-4" })
1566
+ }
1567
+ )
1568
+ ] })
1569
+ ] })
1570
+ ] });
1571
+ }
1572
+ function IconButton({ label, icon, disabled, onClick }) {
1573
+ return /* @__PURE__ */ jsxRuntime.jsx(
1574
+ "button",
1575
+ {
1576
+ type: "button",
1577
+ onClick,
1578
+ disabled,
1579
+ "aria-label": label,
1580
+ className: "inline-flex size-8 items-center justify-center rounded-md border border-input bg-background text-foreground shadow-sm transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
1581
+ children: icon
1582
+ }
1583
+ );
1584
+ }
1585
+
1586
+ exports.SPC_AccessDenied = AccessDenied;
1587
+ exports.SPC_AppShell = AppShell;
1588
+ exports.SPC_AuthContext = AuthContext;
1589
+ exports.SPC_AuthContextProvider = AuthContextProvider;
1590
+ exports.SPC_AuthGuard = AuthGuard;
1591
+ exports.SPC_BackofficeApp = BackofficeApp;
1592
+ exports.SPC_Button = Button;
1593
+ exports.SPC_Can = Can;
1594
+ exports.SPC_DataTable = DataTable;
1595
+ exports.SPC_Input = Input;
1596
+ exports.SPC_Label = Label;
1597
+ exports.SPC_LoadingScreen = LoadingScreen;
1598
+ exports.SPC_LoginPage = LoginPage;
1599
+ exports.SPC_RenderModal = RenderModal;
1600
+ exports.SPC_ResourceGuard = ResourceGuard;
1601
+ exports.SPC_Select = Select;
1602
+ exports.SPC_SelectContent = SelectContent;
1603
+ exports.SPC_SelectGroup = SelectGroup;
1604
+ exports.SPC_SelectItem = SelectItem;
1605
+ exports.SPC_SelectScrollDownButton = SelectScrollDownButton;
1606
+ exports.SPC_SelectScrollUpButton = SelectScrollUpButton;
1607
+ exports.SPC_SelectSeparator = SelectSeparator;
1608
+ exports.SPC_SelectTrigger = SelectTrigger;
1609
+ exports.SPC_SelectValue = SelectValue;
1610
+ exports.SPC_SettingsModal = SettingsModal;
1611
+ exports.SPC_Sidebar = Sidebar;
1612
+ exports.SPC_SidebarToggle = SidebarToggle;
1613
+ exports.SPC_Table = Table;
1614
+ exports.SPC_TableActionMenu = TableActionMenu;
1615
+ exports.SPC_TableBadge = TableBadge;
1616
+ exports.SPC_TableBody = TableBody;
1617
+ exports.SPC_TableCaption = TableCaption;
1618
+ exports.SPC_TableCell = TableCell;
1619
+ exports.SPC_TableFooter = TableFooter;
1620
+ exports.SPC_TableHead = TableHead;
1621
+ exports.SPC_TableHeader = TableHeader;
1622
+ exports.SPC_TablePagination = TablePagination;
1623
+ exports.SPC_TableRow = TableRow;
1624
+ exports.SPC_buttonVariants = buttonVariants;
1625
+ exports.SPC_canAccessResource = canAccessResource;
1626
+ exports.SPC_cn = cn;
1627
+ exports.SPC_evaluateCan = evaluateCan;
1628
+ exports.SPC_evaluateCanAll = evaluateCanAll;
1629
+ exports.SPC_evaluateCanAny = evaluateCanAny;
1630
+ exports.SPC_evaluateHasRole = evaluateHasRole;
1631
+ exports.SPC_useAuth = useAuth;
1632
+ exports.SPC_useAuthContext = useAuthContext;
1633
+ exports.SPC_usePermissions = usePermissions;
1634
+ //# sourceMappingURL=index.cjs.map
1635
+ //# sourceMappingURL=index.cjs.map