@deenruv/admin-dashboard 1.0.16 → 1.0.17-dev.14
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/DeenruvAdminPanel.js +35 -46
- package/dist/access/access-context.js +10 -0
- package/dist/access/built-in-routes.js +300 -0
- package/dist/access/index.js +6 -0
- package/dist/access/navigation.js +33 -0
- package/dist/access/permission-access.js +9 -0
- package/dist/access/permission-routes.js +3 -0
- package/dist/access/types.js +1 -0
- package/dist/components/GlobalSearch.js +37 -62
- package/dist/components/Menu/ActiveAdmins.js +1 -1
- package/dist/components/Menu/ChannelSwitcher.js +1 -1
- package/dist/components/Menu/LanguagesDropdown.js +1 -1
- package/dist/components/Menu/Navigation.js +68 -229
- package/dist/components/Menu/NavigationFooter.js +7 -4
- package/dist/components/Menu/Notifications.js +1 -1
- package/dist/components/Menu/index.js +50 -16
- package/dist/index.css +54 -44
- package/dist/locales/en/admins.json +62 -0
- package/dist/locales/en/collections.json +7 -0
- package/dist/locales/en/common.json +2 -0
- package/dist/locales/en/orders.json +8 -0
- package/dist/locales/en/products.json +14 -2
- package/dist/locales/en/promotions.json +4 -0
- package/dist/locales/pl/admins.json +62 -0
- package/dist/locales/pl/collections.json +7 -0
- package/dist/locales/pl/common.json +2 -0
- package/dist/locales/pl/orders.json +8 -0
- package/dist/locales/pl/products.json +14 -2
- package/dist/locales/pl/promotions.json +4 -0
- package/dist/pages/Root.js +31 -15
- package/dist/pages/admins/Detail.js +1 -1
- package/dist/pages/admins/List.js +8 -3
- package/dist/pages/admins/Provision.js +173 -0
- package/dist/pages/admins/Provision.logic.js +53 -0
- package/dist/pages/admins/index.js +1 -0
- package/dist/pages/assets/List.js +3 -3
- package/dist/pages/channels/Detail.js +1 -1
- package/dist/pages/collections/Detail.js +21 -6
- package/dist/pages/collections/List.js +87 -20
- package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
- package/dist/pages/collections/_components/FiltersCard.js +51 -11
- package/dist/pages/countries/Detail.js +1 -1
- package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
- package/dist/pages/extensions/Extensions.js +3 -7
- package/dist/pages/facets/Detail.js +1 -1
- package/dist/pages/orders/Detail.js +38 -5
- package/dist/pages/orders/List.js +1 -16
- package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
- package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
- package/dist/pages/orders/_components/ProductsTable.js +1 -2
- package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
- package/dist/pages/payment-methods/Detail.js +1 -1
- package/dist/pages/product-variants/List.js +3 -3
- package/dist/pages/products/Detail.js +58 -4
- package/dist/pages/products/_components/AssetsCard.js +15 -2
- package/dist/pages/products/_components/OptionsCard.js +7 -5
- package/dist/pages/products/_components/PriceCard.js +21 -8
- package/dist/pages/products/_components/ProductDetailView.js +7 -3
- package/dist/pages/products/_components/Variant.js +53 -26
- package/dist/pages/products/_components/VariantsTab.js +1 -1
- package/dist/pages/promotions/Detail.js +34 -3
- package/dist/pages/promotions/_components/EnabledCard.js +2 -2
- package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
- package/dist/pages/roles/Detail.js +1 -1
- package/dist/pages/roles/_components/PermissionsTable.js +0 -1
- package/dist/pages/sellers/Detail.js +1 -1
- package/dist/pages/shipping-methods/Detail.js +10 -6
- package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
- package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
- package/dist/pages/stock-locations/Detail.js +1 -1
- package/dist/pages/tax-rates/Detail.js +1 -1
- package/dist/pages/zones/Detail.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +68 -68
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem,
|
|
2
|
+
import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, useGlobalSearch, capitalizeFirstLetter, useTranslation, useServer, } from '@deenruv/react-ui-devkit';
|
|
3
3
|
import { useEffect, useMemo } from 'react';
|
|
4
4
|
import { useNavigate } from 'react-router';
|
|
5
5
|
import { LayoutDashboard, ListPlus, FileText, Puzzle, ArrowRight } from 'lucide-react';
|
|
6
|
+
import { canAccessAdminItem, useAdminAccess } from "../access/index.js";
|
|
6
7
|
export const GlobalSearch = () => {
|
|
7
8
|
const { t, tEntity } = useTranslation('common');
|
|
8
|
-
const {
|
|
9
|
+
const { routes } = useAdminAccess();
|
|
10
|
+
const userPermissions = useServer((p) => p.userPermissions);
|
|
9
11
|
const isOpen = useGlobalSearch((s) => s.isOpen);
|
|
10
12
|
const toggle = useGlobalSearch((s) => s.toggle);
|
|
11
13
|
const close = useGlobalSearch((s) => s.close);
|
|
12
14
|
const navigate = useNavigate();
|
|
15
|
+
const isEnabled = true;
|
|
13
16
|
useEffect(() => {
|
|
17
|
+
if (!isEnabled)
|
|
18
|
+
return;
|
|
14
19
|
const handleKeyDown = (e) => {
|
|
15
20
|
if ((e.metaKey || e.ctrlKey) && e.key === ' ') {
|
|
16
21
|
e.preventDefault();
|
|
@@ -19,63 +24,33 @@ export const GlobalSearch = () => {
|
|
|
19
24
|
};
|
|
20
25
|
document.addEventListener('keydown', handleKeyDown);
|
|
21
26
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
22
|
-
}, [toggle]);
|
|
27
|
+
}, [isEnabled, toggle]);
|
|
23
28
|
const allRoutes = useMemo(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
else if (typeof value === 'string') {
|
|
47
|
-
routes.push({
|
|
48
|
-
name: capitalizeFirstLetter(t(`menu.${key}`)),
|
|
49
|
-
path: value,
|
|
50
|
-
type: 'default',
|
|
51
|
-
description: `${t('globalSearch.navigateTo')} ${t(`menu.${key}`)}`,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
plugins.forEach((plugin) => {
|
|
56
|
-
plugin.pages?.forEach((page, pageIndex) => {
|
|
57
|
-
if (page.path) {
|
|
58
|
-
routes.push({
|
|
59
|
-
name: `${plugin.name} - ${page.path || 'Page'}`,
|
|
60
|
-
path: page.path,
|
|
61
|
-
type: 'plugin',
|
|
62
|
-
description: t('globalSearch.accessPlugin', { pluginName: plugin.name }),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
});
|
|
29
|
+
return routes
|
|
30
|
+
.filter((route) => route.search)
|
|
31
|
+
.filter((route) => canAccessAdminItem({ item: route, userPermissions }))
|
|
32
|
+
.map((route) => {
|
|
33
|
+
const search = route.search;
|
|
34
|
+
const isPlugin = search.type === 'plugin';
|
|
35
|
+
const translatedName = isPlugin ? search.menuKey : capitalizeFirstLetter(t(`menu.${search.menuKey}`));
|
|
36
|
+
const description = search.type === 'new'
|
|
37
|
+
? tEntity('Utwórz', search.menuKey)
|
|
38
|
+
: search.type === 'list'
|
|
39
|
+
? tEntity('Zobacz wszystkie', search.menuKey, 'many')
|
|
40
|
+
: search.type === 'plugin'
|
|
41
|
+
? t('globalSearch.accessPlugin', { pluginName: search.menuKey })
|
|
42
|
+
: `${t('globalSearch.navigateTo')} ${t(`menu.${search.menuKey}`)}`;
|
|
43
|
+
return {
|
|
44
|
+
id: route.id,
|
|
45
|
+
name: translatedName,
|
|
46
|
+
path: route.path,
|
|
47
|
+
type: search.type,
|
|
48
|
+
description,
|
|
49
|
+
};
|
|
66
50
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
id: `${routeIndex}-${childIndex}-${child.type}-${route.name}`,
|
|
71
|
-
}))
|
|
72
|
-
: [
|
|
73
|
-
{
|
|
74
|
-
...route,
|
|
75
|
-
id: `${routeIndex}-main-${route.name}`,
|
|
76
|
-
},
|
|
77
|
-
]);
|
|
78
|
-
}, [plugins]);
|
|
51
|
+
}, [routes, t, tEntity, userPermissions]);
|
|
52
|
+
if (!isEnabled)
|
|
53
|
+
return null;
|
|
79
54
|
// Group routes by type for better organization
|
|
80
55
|
const groupedRoutes = useMemo(() => {
|
|
81
56
|
const core = [];
|
|
@@ -110,25 +85,25 @@ export const GlobalSearch = () => {
|
|
|
110
85
|
return _jsx(LayoutDashboard, { className: "mr-2 h-4 w-4 text-gray-500" });
|
|
111
86
|
}
|
|
112
87
|
};
|
|
113
|
-
return (_jsxs(CommandDialog, { open: isOpen, onOpenChange: toggle, modal: true, children: [_jsx(CommandInput, { placeholder: t('globalSearch.placeholder'), className: "flex-1 bg-transparent text-
|
|
88
|
+
return (_jsxs(CommandDialog, { open: isOpen, onOpenChange: toggle, modal: true, children: [_jsx(CommandInput, { placeholder: t('globalSearch.placeholder'), className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground" }), _jsxs(CommandList, { className: "max-h-[320px] overflow-y-auto py-2", children: [_jsx(CommandEmpty, { className: "py-6 text-center text-sm", children: t('globalSearch.emptyState') }), groupedRoutes.core.length > 0 && (_jsx(CommandGroup, { heading: t('globalSearch.navigation'), className: "px-2", children: groupedRoutes.core.map((route) => (_jsxs(CommandItem, { onSelect: () => {
|
|
114
89
|
if (route.path) {
|
|
115
90
|
navigate(route.path);
|
|
116
91
|
}
|
|
117
92
|
close();
|
|
118
|
-
}, className: "group flex cursor-pointer items-center
|
|
93
|
+
}, className: "group flex cursor-pointer items-center px-3 py-2.5 transition-colors hover:bg-accent", children: [getRouteIcon(route.type), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: route.name }), route.description && _jsx("span", { className: "text-xs text-muted-foreground", children: route.description })] }), _jsx(ArrowRight, { className: "ml-auto h-4 w-4 opacity-0 transition-opacity group-hover:opacity-70" })] }, route.id || `core-${route.name}-${route.path}`))) })), groupedRoutes.newItems.length > 0 && (_jsx(CommandGroup, { heading: t('globalSearch.createNew'), className: "px-2", children: groupedRoutes.newItems.map((route) => (_jsxs(CommandItem, { onSelect: () => {
|
|
119
94
|
if (route.path) {
|
|
120
95
|
navigate(route.path);
|
|
121
96
|
}
|
|
122
97
|
close();
|
|
123
|
-
}, className: "group flex cursor-pointer items-center
|
|
98
|
+
}, className: "group flex cursor-pointer items-center px-3 py-2.5 transition-colors hover:bg-accent", children: [getRouteIcon(route.type), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: route.name }), route.description && _jsx("span", { className: "text-xs text-muted-foreground", children: route.description })] }), _jsx(ArrowRight, { className: "ml-auto h-4 w-4 opacity-0 transition-opacity group-hover:opacity-70" })] }, route.id || `new-${route.name}-${route.path}`))) })), groupedRoutes.lists.length > 0 && (_jsx(CommandGroup, { heading: t('globalSearch.viewLists'), className: "px-2", children: groupedRoutes.lists.map((route) => (_jsxs(CommandItem, { onSelect: () => {
|
|
124
99
|
if (route.path) {
|
|
125
100
|
navigate(route.path);
|
|
126
101
|
}
|
|
127
102
|
close();
|
|
128
|
-
}, className: "group flex cursor-pointer items-center
|
|
103
|
+
}, className: "group flex cursor-pointer items-center px-3 py-2.5 transition-colors hover:bg-accent", children: [getRouteIcon(route.type), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: route.name }), route.description && _jsx("span", { className: "text-xs text-muted-foreground", children: route.description })] }), _jsx(ArrowRight, { className: "ml-auto h-4 w-4 opacity-0 transition-opacity group-hover:opacity-70" })] }, route.id || `list-${route.name}-${route.path}`))) })), groupedRoutes.pluginRoutes.length > 0 && (_jsx(CommandGroup, { heading: t('globalSearch.plugins'), className: "px-2", children: groupedRoutes.pluginRoutes.map((route) => (_jsxs(CommandItem, { onSelect: () => {
|
|
129
104
|
if (route.path) {
|
|
130
105
|
navigate(route.path);
|
|
131
106
|
}
|
|
132
107
|
close();
|
|
133
|
-
}, className: "group flex cursor-pointer items-center
|
|
108
|
+
}, className: "group flex cursor-pointer items-center px-3 py-2.5 transition-colors hover:bg-accent", children: [getRouteIcon(route.type), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: route.name }), route.description && _jsx("span", { className: "text-xs text-muted-foreground", children: route.description })] }), _jsx(ArrowRight, { className: "ml-auto h-4 w-4 opacity-0 transition-opacity group-hover:opacity-70" })] }, route.id || `plugin-${route.name}-${route.path}`))) }))] }), _jsx("div", { className: "border-t bg-card p-2", children: _jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [_jsxs("div", { className: "flex gap-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("kbd", { className: "border bg-muted px-1.5 py-0.5 text-[10px]", children: "\u2191" }), _jsx("kbd", { className: "border bg-muted px-1.5 py-0.5 text-[10px]", children: "\u2193" }), _jsx("span", { children: t('globalSearch.navigate') })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("kbd", { className: "border bg-muted px-1.5 py-0.5 text-[10px]", children: "Enter" }), _jsx("span", { children: t('globalSearch.select') })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("kbd", { className: "border bg-muted px-1.5 py-0.5 text-[10px]", children: "Esc" }), _jsx("span", { children: t('globalSearch.close') })] })] }) })] }));
|
|
134
109
|
};
|
|
@@ -20,5 +20,5 @@ export const ActiveAdmins = () => {
|
|
|
20
20
|
else
|
|
21
21
|
return t('awesomeMenu.above5MinAgo');
|
|
22
22
|
};
|
|
23
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "sm", className: "h-10", children: t('awesomeMenu.activeAdministratorsValue', { value: activeClients.length }) }) }), _jsx(PopoverContent, { className: "mr-4", children: _jsxs("div", { className: "flex flex-col gap-4
|
|
23
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "sm", className: "h-10", children: t('awesomeMenu.activeAdministratorsValue', { value: activeClients.length }) }) }), _jsx(PopoverContent, { className: "mr-4", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Label, { className: "select-none", children: t('awesomeMenu.activeAdministrators') }), activeClients.length ? (activeClients.map((client) => (_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "flex w-full flex-col", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-bold", children: [client.firstName, " ", client.lastName, " ", client.me && '(me)'] }), _jsx(motion.div, { animate: { scale: [1, 1.2, 1] }, transition: { duration: 1.5, repeat: Infinity }, className: "size-2 rounded-full bg-green-500" })] }), _jsx("span", { className: "text-sm text-muted-foreground", children: formatTimeAgo(client.lastActive) }), !client.me && (_jsx(NavLink, { to: client.location, className: "text-sm text-muted-foreground", viewTransition: true, children: client.location.replace(window.location.origin, '') }))] }) }, client.id)))) : (_jsx("div", { children: _jsx("span", { className: "text-sm text-muted-foreground", children: t('awesomeMenu.noActiveAdministrators') }) }))] }) })] }));
|
|
24
24
|
};
|
|
@@ -16,5 +16,5 @@ export function ChannelSwitcher({ className }) {
|
|
|
16
16
|
if (!channels || channels.length === 0) {
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
-
return (_jsx("div", { className: className, children: _jsxs(Select, { defaultValue: selectedChannel?.id, onValueChange: onChannelChange, value: selectedChannel?.id, children: [_jsx(SelectTrigger, { className: cn('flex items-center gap-2 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate'), "aria-label": "Select
|
|
19
|
+
return (_jsx("div", { className: className, children: _jsxs(Select, { defaultValue: selectedChannel?.id, onValueChange: onChannelChange, value: selectedChannel?.id, children: [_jsx(SelectTrigger, { className: cn('flex h-9 items-center gap-2 border-border/80 bg-card px-3 text-xs font-medium [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate'), "aria-label": "Select a channel", children: _jsx(SelectValue, { children: _jsx("span", { className: "ml-1", children: getChannelLabel(channels.find((account) => account.id === selectedChannel?.id)?.code) }) }) }), _jsx(SelectContent, { children: channels.map((channel) => (_jsx(SelectItem, { value: channel.id, children: _jsx("div", { className: "flex items-center gap-3 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-foreground", children: getChannelLabel(channel.code) }) }, channel.code))) })] }) }));
|
|
20
20
|
}
|
|
@@ -23,5 +23,5 @@ export const LanguagesDropdown = () => {
|
|
|
23
23
|
})));
|
|
24
24
|
const Flag = langFlagDict[contentLng] || EU;
|
|
25
25
|
const contentLanguages = selectedChannel?.availableLanguageCodes || [];
|
|
26
|
-
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", className: "h-9", children: [t('language'), _jsx(Badge, { variant: "outline", className: "ml-2", children: _jsx(Flag, { className: "size-4" }) })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "z-[150]", children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: t('content') }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: contentLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== contentLng && setContentLng(lng), children: [_jsx("span", { children: getLanguageName(lng, uiLng) }), lng === contentLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: "UI" }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: uiLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== uiLng && setUiLng(lng), children: [_jsx("span", { children: getLanguageName(lng) }), lng === uiLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] })] })] }));
|
|
26
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", className: "h-9 px-3 text-xs", children: [t('language'), _jsx(Badge, { variant: "outline", className: "ml-2 h-5 px-1.5", children: _jsx(Flag, { className: "size-4" }) })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "z-[150]", children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: t('content') }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: contentLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== contentLng && setContentLng(lng), children: [_jsx("span", { children: getLanguageName(lng, uiLng) }), lng === contentLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: "UI" }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: uiLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== uiLng && setUiLng(lng), children: [_jsx("span", { children: getLanguageName(lng) }), lng === uiLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] })] })] }));
|
|
27
27
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { NavLink, useLocation } from 'react-router';
|
|
3
|
-
import React, { useMemo } from 'react';
|
|
4
|
-
import { cn, buttonVariants,
|
|
5
|
-
import {
|
|
6
|
-
import { Permission } from '@deenruv/admin-types';
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { cn, buttonVariants, usePluginStore, Tooltip, TooltipContent, TooltipTrigger, useServer, Accordion, AccordionItem, AccordionTrigger, AccordionContent, useNotifications, useTranslation, capitalizeFirstLetter, Skeleton, } from '@deenruv/react-ui-devkit';
|
|
5
|
+
import { adminNavigationGroups, canAccessAdminItem, getActiveNavigationLinkIds, getActiveNavigationGroupIds, getNavigationLinkActivePaths, insertNavigationLink, useAdminAccess, } from "../../access/index.js";
|
|
7
6
|
export function Navigation({ isCollapsed }) {
|
|
8
7
|
const { t } = useTranslation('common');
|
|
9
8
|
const { t: _pluginT } = useTranslation();
|
|
10
9
|
const location = useLocation();
|
|
11
10
|
const { navMenuData, viewMarkers } = usePluginStore();
|
|
11
|
+
const { routes } = useAdminAccess();
|
|
12
12
|
const userPermissions = useServer((p) => p.userPermissions);
|
|
13
13
|
const loaded = useServer((p) => p.loaded);
|
|
14
14
|
const getNavigationNotification = useNotifications(({ getNavigationNotification }) => getNavigationNotification);
|
|
@@ -19,201 +19,34 @@ export function Navigation({ isCollapsed }) {
|
|
|
19
19
|
return _pluginT(key, { ns });
|
|
20
20
|
};
|
|
21
21
|
const navigationGroups = useMemo(() => {
|
|
22
|
-
const navData =
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
icon: UsersRound,
|
|
47
|
-
requiredPermissions: [Permission.ReadCustomerGroup],
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
label: t('menuGroups.assortment'),
|
|
53
|
-
id: 'assortment-group',
|
|
54
|
-
links: [
|
|
55
|
-
{
|
|
56
|
-
title: t('menu.products'),
|
|
57
|
-
href: Routes.products.list,
|
|
58
|
-
id: 'link-products',
|
|
59
|
-
icon: Barcode,
|
|
60
|
-
requiredPermissions: [Permission.ReadProduct, Permission.ReadCatalog],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
title: t('menu.productVariants'),
|
|
64
|
-
href: Routes.productVariants.list,
|
|
65
|
-
id: 'link-product-variants',
|
|
66
|
-
icon: ScanBarcode,
|
|
67
|
-
requiredPermissions: [Permission.ReadProduct, Permission.ReadCatalog],
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
title: t('menu.collections'),
|
|
71
|
-
href: Routes.collections.list,
|
|
72
|
-
id: 'link-collections',
|
|
73
|
-
icon: Folder,
|
|
74
|
-
requiredPermissions: [Permission.ReadCollection, Permission.ReadCatalog],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
title: t('menu.facets'),
|
|
78
|
-
href: Routes.facets.list,
|
|
79
|
-
id: 'link-facets',
|
|
80
|
-
icon: Tag,
|
|
81
|
-
requiredPermissions: [Permission.ReadFacet, Permission.ReadCatalog],
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
title: t('menu.assets'),
|
|
85
|
-
href: Routes.assets.list,
|
|
86
|
-
id: 'link-assets',
|
|
87
|
-
icon: Images,
|
|
88
|
-
requiredPermissions: [Permission.ReadAsset, Permission.ReadCatalog],
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
label: t('menuGroups.users'),
|
|
94
|
-
id: 'users-group',
|
|
95
|
-
links: [
|
|
96
|
-
{
|
|
97
|
-
title: t('menu.admins'),
|
|
98
|
-
href: Routes.admins.list,
|
|
99
|
-
id: 'link-admins',
|
|
100
|
-
icon: UserCog,
|
|
101
|
-
requiredPermissions: [Permission.ReadAdministrator],
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
title: t('menu.roles'),
|
|
105
|
-
href: Routes.roles.list,
|
|
106
|
-
id: 'link-roles',
|
|
107
|
-
icon: Users,
|
|
108
|
-
requiredPermissions: [Permission.ReadAdministrator],
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
title: t('menu.sellers'),
|
|
112
|
-
href: Routes.sellers.list,
|
|
113
|
-
id: 'link-sellers',
|
|
114
|
-
icon: Store,
|
|
115
|
-
requiredPermissions: [Permission.ReadSeller],
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
label: t('menuGroups.promotions'),
|
|
121
|
-
id: 'promotions-group',
|
|
122
|
-
links: [
|
|
123
|
-
{
|
|
124
|
-
title: t('menu.promotions'),
|
|
125
|
-
href: Routes.promotions.list,
|
|
126
|
-
id: 'link-promotions',
|
|
127
|
-
icon: ShoppingCart,
|
|
128
|
-
requiredPermissions: [Permission.ReadPromotion],
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
label: t('menuGroups.shipping'),
|
|
134
|
-
id: 'shipping-group',
|
|
135
|
-
links: [
|
|
136
|
-
{
|
|
137
|
-
title: t('menu.paymentMethods'),
|
|
138
|
-
href: Routes.paymentMethods.list,
|
|
139
|
-
id: 'link-payment-methods',
|
|
140
|
-
icon: CreditCard,
|
|
141
|
-
requiredPermissions: [Permission.ReadPaymentMethod],
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
title: t('menu.shippingMethods'),
|
|
145
|
-
href: Routes.shippingMethods.list,
|
|
146
|
-
id: 'link-shipping-methods',
|
|
147
|
-
icon: Truck,
|
|
148
|
-
requiredPermissions: [Permission.ReadShippingMethod],
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
title: t('menu.stock'),
|
|
152
|
-
href: Routes.stockLocations.list,
|
|
153
|
-
id: 'link-stock',
|
|
154
|
-
icon: MapPin,
|
|
155
|
-
requiredPermissions: [Permission.ReadStockLocation],
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
label: t('menuGroups.settings'),
|
|
161
|
-
id: 'settings-group',
|
|
162
|
-
links: [
|
|
163
|
-
{
|
|
164
|
-
title: t('menu.channels'),
|
|
165
|
-
href: Routes.channels.list,
|
|
166
|
-
id: 'link-channels',
|
|
167
|
-
icon: Globe2,
|
|
168
|
-
requiredPermissions: [Permission.ReadChannel],
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
title: t('menu.zones'),
|
|
172
|
-
href: Routes.zones.list,
|
|
173
|
-
id: 'link-zones',
|
|
174
|
-
icon: Globe,
|
|
175
|
-
requiredPermissions: [Permission.ReadZone],
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
title: t('menu.countries'),
|
|
179
|
-
href: Routes.countries.list,
|
|
180
|
-
id: 'link-countries',
|
|
181
|
-
icon: Flag,
|
|
182
|
-
requiredPermissions: [Permission.ReadCountry],
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
title: t('menu.taxCategories'),
|
|
186
|
-
href: Routes.taxCategories.list,
|
|
187
|
-
id: 'link-tax-categories',
|
|
188
|
-
icon: Coins,
|
|
189
|
-
requiredPermissions: [Permission.ReadTaxCategory],
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
title: t('menu.taxRates'),
|
|
193
|
-
href: Routes.taxRates.list,
|
|
194
|
-
id: 'link-tax-rates',
|
|
195
|
-
icon: Percent,
|
|
196
|
-
requiredPermissions: [Permission.ReadTaxRate],
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
title: t('menu.globalSettings'),
|
|
200
|
-
href: Routes.globalSettings,
|
|
201
|
-
id: 'link-global-settings',
|
|
202
|
-
icon: Cog,
|
|
203
|
-
requiredPermissions: [Permission.ReadSettings],
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
title: t('menu.systemStatus'),
|
|
207
|
-
href: Routes.status,
|
|
208
|
-
id: 'link-system-status',
|
|
209
|
-
icon: Server,
|
|
210
|
-
requiredPermissions: [Permission.ReadSystem],
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
},
|
|
214
|
-
];
|
|
22
|
+
const navData = adminNavigationGroups.map((group) => ({
|
|
23
|
+
label: t(`menuGroups.${group.labelKey}`),
|
|
24
|
+
id: group.id,
|
|
25
|
+
links: [],
|
|
26
|
+
}));
|
|
27
|
+
routes.forEach((route) => {
|
|
28
|
+
if (!route.nav)
|
|
29
|
+
return;
|
|
30
|
+
if (!canAccessAdminItem({ item: route, userPermissions }))
|
|
31
|
+
return;
|
|
32
|
+
const foundGroupIdx = navData.findIndex((group) => group.id === route.nav?.groupId);
|
|
33
|
+
if (foundGroupIdx === -1)
|
|
34
|
+
return;
|
|
35
|
+
navData[foundGroupIdx].links.push({
|
|
36
|
+
title: t(`menu.${route.nav.menuKey}`),
|
|
37
|
+
href: route.path,
|
|
38
|
+
id: route.nav.linkId,
|
|
39
|
+
icon: route.nav.icon,
|
|
40
|
+
access: route,
|
|
41
|
+
routeId: route.id,
|
|
42
|
+
groupId: route.nav.groupId,
|
|
43
|
+
activePaths: getNavigationLinkActivePaths(route, routes),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
215
46
|
const { groups, links } = navMenuData;
|
|
216
|
-
groups.forEach(({ id, labelId, placement }) => {
|
|
47
|
+
groups.forEach(({ id, labelId, placement, access }) => {
|
|
48
|
+
if (!canAccessAdminItem({ item: access, userPermissions }))
|
|
49
|
+
return;
|
|
217
50
|
let foundGroupIdx = -1;
|
|
218
51
|
const newGroup = { id, label: pluginT(labelId), links: [] };
|
|
219
52
|
if (placement?.groupId) {
|
|
@@ -226,46 +59,52 @@ export function Navigation({ isCollapsed }) {
|
|
|
226
59
|
navData.splice(foundGroupIdx + 1, 0, newGroup);
|
|
227
60
|
}
|
|
228
61
|
});
|
|
229
|
-
links.forEach(({ groupId, href, labelId, id, icon, placement }) => {
|
|
62
|
+
links.forEach(({ groupId, href, labelId, id, icon, placement, access }) => {
|
|
63
|
+
if (!canAccessAdminItem({ item: access, userPermissions }))
|
|
64
|
+
return;
|
|
230
65
|
const foundGroupIdx = navData.findIndex((group) => group.id === groupId);
|
|
231
66
|
if (foundGroupIdx == -1)
|
|
232
|
-
|
|
233
|
-
const newElement = {
|
|
67
|
+
return;
|
|
68
|
+
const newElement = {
|
|
69
|
+
title: pluginT(labelId),
|
|
70
|
+
label: pluginT(labelId),
|
|
71
|
+
href: `/${href}`,
|
|
72
|
+
id,
|
|
73
|
+
icon,
|
|
74
|
+
access,
|
|
75
|
+
groupId,
|
|
76
|
+
};
|
|
234
77
|
if (!placement) {
|
|
235
78
|
navData[foundGroupIdx].links.push(newElement);
|
|
236
79
|
return;
|
|
237
80
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
81
|
+
navData[foundGroupIdx].links = insertNavigationLink(navData[foundGroupIdx].links, newElement, placement);
|
|
82
|
+
});
|
|
83
|
+
return navData.filter((group) => group.links.length > 0);
|
|
84
|
+
}, [navMenuData.groups, navMenuData.links, pluginT, routes, t, userPermissions]);
|
|
85
|
+
const permittedNavigationGroups = navigationGroups;
|
|
86
|
+
const activeGroupIds = useMemo(() => getActiveNavigationGroupIds(permittedNavigationGroups.flatMap((group) => group.links), location.pathname), [location.pathname, permittedNavigationGroups]);
|
|
87
|
+
const activeLinkIds = useMemo(() => new Set(getActiveNavigationLinkIds(permittedNavigationGroups.flatMap((group) => group.links), location.pathname).map((link) => link.id)), [location.pathname, permittedNavigationGroups]);
|
|
88
|
+
const previousPathname = useRef(location.pathname);
|
|
89
|
+
const [openGroupIds, setOpenGroupIds] = useState(activeGroupIds);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const hasPathnameChanged = previousPathname.current !== location.pathname;
|
|
92
|
+
previousPathname.current = location.pathname;
|
|
93
|
+
if (!hasPathnameChanged)
|
|
94
|
+
return;
|
|
95
|
+
setOpenGroupIds((currentGroupIds) => {
|
|
96
|
+
const nextGroupIds = [...new Set([...currentGroupIds, ...activeGroupIds])];
|
|
97
|
+
return nextGroupIds.length === currentGroupIds.length ? currentGroupIds : nextGroupIds;
|
|
241
98
|
});
|
|
242
|
-
|
|
243
|
-
}, [navMenuData.groups, navMenuData.links, t]);
|
|
244
|
-
const permittedNavigationGroups = useMemo(() => {
|
|
245
|
-
return navigationGroups
|
|
246
|
-
.map((group) => ({
|
|
247
|
-
...group,
|
|
248
|
-
links: group.links.filter((link) => 'requiredPermissions' in link
|
|
249
|
-
? link.requiredPermissions?.some((permission) => userPermissions.includes(permission))
|
|
250
|
-
: true),
|
|
251
|
-
}))
|
|
252
|
-
.filter((group) => group.links.length > 0);
|
|
253
|
-
}, [userPermissions, navigationGroups]);
|
|
254
|
-
// const defaultAccordionOpenValue = useMemo(
|
|
255
|
-
// () =>
|
|
256
|
-
// permittedNavigationGroups
|
|
257
|
-
// .filter((g) => !navMenuData.groups.find((pluginGroup) => pluginGroup.id === g.id))
|
|
258
|
-
// .map((g) => g.id),
|
|
259
|
-
// [permittedNavigationGroups, navMenuData],
|
|
260
|
-
// );
|
|
261
|
-
const defaultAccordionOpenValue = ['shop-group', 'assortment-group'];
|
|
99
|
+
}, [activeGroupIds, location.pathname]);
|
|
262
100
|
if (!loaded) {
|
|
263
|
-
return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { className: "flex h-[calc(100%-
|
|
101
|
+
return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { className: "flex h-[calc(100%-64px)] flex-col gap-3 pb-2 lg:h-[calc(100%-72px)]", children: Array.from({ length: 3 }).map((_, groupIdx) => (_jsxs("div", { className: "flex flex-col gap-1 px-2.5", children: [!isCollapsed && (_jsx("div", { className: "px-3 py-2", children: _jsx(Skeleton, { className: "h-3 w-20" }) })), Array.from({ length: groupIdx === 0 ? 4 : groupIdx === 1 ? 5 : 3 }).map((_, linkIdx) => (_jsxs("div", { className: cn('flex items-center px-3 py-2', isCollapsed && 'justify-center px-0'), children: [_jsx(Skeleton, { className: cn('size-4 shrink-0', isCollapsed && 'size-6') }), !isCollapsed && _jsx(Skeleton, { className: "ml-2 h-4 w-24" })] }, linkIdx)))] }, groupIdx))) }) }));
|
|
264
102
|
}
|
|
265
|
-
return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { "data-collapsed": isCollapsed, className: "group flex h-[calc(100%-
|
|
103
|
+
return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { "data-collapsed": isCollapsed, className: "group flex h-[calc(100%-64px)] flex-col gap-2 py-2 data-[collapsed=true]:py-2 lg:h-[calc(100%-72px)]", children: _jsx(Accordion, { type: "multiple", className: "w-full", value: isCollapsed ? permittedNavigationGroups.map((g) => g.id) : openGroupIds, onValueChange: setOpenGroupIds, children: permittedNavigationGroups.map((group) => (_jsxs(AccordionItem, { value: group.id, className: "border-none", children: [!isCollapsed && (_jsx(AccordionTrigger, { className: cn('flex items-center justify-between px-3 py-2 hover:no-underline'), children: _jsxs("div", { className: "flex items-center gap-2 px-1", children: [_jsx("h4", { className: "text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase", children: group.label }), getNavigationNotification(group.id), viewMarkers ? (_jsx("p", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: group.id })) : null] }) })), _jsx(AccordionContent, { className: cn(isCollapsed ? 'py-1' : 'pb-2'), children: _jsx("nav", { id: group.id, className: "grid gap-1 px-2.5 text-muted-foreground group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2", children: group.links.map((link, index) => {
|
|
266
104
|
const notifications = getNavigationNotification(link.id);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
105
|
+
const isActive = activeLinkIds.has(link.id);
|
|
106
|
+
return (_jsx(React.Fragment, { children: isCollapsed ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { children: _jsx(NavLink, { to: link.href, viewTransition: true, "aria-current": isActive ? 'page' : undefined, children: _jsxs("div", { className: cn(buttonVariants({ variant: 'navigation-link', size: 'icon' }), 'h-9 w-9 rounded-lg ring-offset-background transition-all hover:scale-[1.03] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', isActive &&
|
|
107
|
+
'bg-primary text-primary-foreground opacity-100 shadow-sm hover:scale-100 hover:bg-primary hover:text-primary-foreground'), children: [_jsx(link.icon, { className: "size-5" }), _jsx("span", { className: "sr-only", children: link.title })] }) }) }) }), _jsxs(TooltipContent, { side: "right", className: "relative flex items-center gap-4", children: [viewMarkers ? (_jsx("div", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, capitalizeFirstLetter(link.title), notifications] })] }, index)) : (_jsx(NavLink, { to: link.href, viewTransition: true, "aria-current": isActive ? 'page' : undefined, children: _jsxs("div", { id: link.id, className: cn('relative flex h-10 items-center justify-start rounded-lg px-3 text-sm font-medium capitalize transition-all hover:bg-muted hover:text-foreground', isActive &&
|
|
108
|
+
'bg-primary text-primary-foreground opacity-100 shadow-sm hover:bg-primary hover:text-primary-foreground'), children: [viewMarkers ? (_jsx("div", { className: "absolute top-1/2 right-2 -translate-y-1/2 text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, _jsx("span", { className: cn('mr-2.5 flex size-6 shrink-0 items-center justify-center rounded-md bg-muted/80 text-muted-foreground transition-colors', isActive && 'bg-primary-foreground/15 text-primary-foreground'), children: _jsx(link.icon, { className: "size-4" }) }), _jsx("span", { className: "truncate", children: capitalizeFirstLetter(link.title) }), notifications] }) })) }, link.id));
|
|
270
109
|
}) }) })] }, group.id))) }) }) }));
|
|
271
110
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Permission } from '@deenruv/admin-types';
|
|
3
2
|
import { capitalizeFirstLetter, cn, Routes, Separator, useServer, useTranslation } from '@deenruv/react-ui-devkit';
|
|
4
3
|
import { Puzzle } from 'lucide-react';
|
|
5
4
|
import { NavLink } from 'react-router';
|
|
5
|
+
import { canAccessAdminItem, useAdminAccess } from "../../access/index.js";
|
|
6
6
|
export const NavigationFooter = ({ isCollapsed }) => {
|
|
7
7
|
const { t } = useTranslation('common');
|
|
8
8
|
const userPermissions = useServer((p) => p.userPermissions);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const { routes } = useAdminAccess();
|
|
10
|
+
const extensionsRoute = routes.find((route) => route.id === 'extensions');
|
|
11
|
+
const isPermittedToExtensions = !!extensionsRoute && canAccessAdminItem({ item: extensionsRoute, userPermissions });
|
|
12
|
+
const extensionsPath = extensionsRoute?.path || Routes.extensions;
|
|
13
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-2 border-t bg-card/80 py-2 text-xs text-muted-foreground select-none", children: [isPermittedToExtensions && !isCollapsed && (_jsxs(_Fragment, { children: [_jsx("div", { children: _jsx(NavLink, { to: extensionsPath, viewTransition: true, children: _jsxs("div", { className: cn('relative mx-2 flex h-8 items-center justify-center px-3 text-sm font-medium capitalize transition-colors hover:bg-muted/70 hover:text-foreground', location.pathname === extensionsPath &&
|
|
14
|
+
'bg-primary/10 text-primary opacity-100 hover:bg-primary/10 hover:text-primary'), children: [_jsx(Puzzle, { className: "mr-2 size-4" }), capitalizeFirstLetter(t('menu.extensions'))] }) }) }), _jsx(Separator, {})] })), _jsxs("div", { className: "flex items-center justify-center gap-1 px-2", children: [!isCollapsed && _jsx("p", { className: "uppercase", children: "Deenruv" }), _jsxs("span", { children: [!isCollapsed ? 'ver. ' : 'v. ', window.__DEENRUV_SETTINGS__.appVersion] })] })] }));
|
|
12
15
|
};
|
|
@@ -87,5 +87,5 @@ export const Notifications = () => {
|
|
|
87
87
|
prevJobQueuesRef.current = [...jobQueues];
|
|
88
88
|
}
|
|
89
89
|
}, [jobQueues, status]);
|
|
90
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-
|
|
90
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-9", children: [_jsx(Bell, { className: "size-4" }), hasUnread && (_jsx("span", { className: "absolute top-1.5 right-1.5 size-2 rounded-full bg-red-500", children: _jsx("span", { className: "absolute inset-0 animate-ping rounded-full bg-red-400 opacity-75" }) })), _jsx("span", { className: "sr-only", children: t('notificationsBox.toggleNotifications') })] }) }), _jsx(PopoverContent, { className: "z-[2138] w-[min(28rem,calc(100vw-2rem))] p-0", align: "end", children: _jsx(Tabs, { defaultValue: "ALL", className: "w-full", children: _jsxs(Card, { className: "w-full border-0 shadow-none", children: [_jsxs(CardHeader, { className: cn('flex w-full flex-col items-start justify-start gap-2 bg-card p-0', components.length === 0 && 'pb-3'), children: [_jsxs("div", { className: "flex h-full w-full flex-1 items-center justify-between px-4 pt-3", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(CardTitle, { className: "text-base", children: t('notificationsBox.notifications') }), _jsx(CardDescription, { className: "mt-1 text-xs", children: t('notificationsBox.recentNotifications') })] }), notifications.length > 0 && (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 text-xs", onClick: clearAllNotifications, children: [_jsx(Trash2, { className: "mr-1 size-3" }), t('notificationsBox.clearAll')] }))] }), components.length !== 0 && (_jsxs(TabsList, { className: "m-0 w-full bg-muted/50", children: [_jsx(TabsTrigger, { value: "ALL", className: "w-full text-left", children: t('notificationsBox.all') }), cyclingNotifications.map((notification) => (_jsx(TabsTrigger, { value: notification.id, className: "w-full text-left", children: notification.id }, notification.id)))] }))] }), _jsxs(CardContent, { className: "max-h-[400px] overflow-auto p-0", children: [_jsx(TabsContent, { value: "ALL", className: "mt-0", children: notifications.length > 0 ? (_jsx("div", { className: "divide-y", children: notifications.map((notification) => (_jsxs("div", { className: `flex items-start gap-3 p-4 transition-colors ${notification.read ? 'bg-card' : 'bg-accent/40'}`, children: [_jsx("div", { className: "mt-0.5", children: notification.icon }), _jsxs("div", { className: "flex-1 space-y-1", children: [_jsx("p", { className: "text-sm leading-none font-medium", children: notification.title }), _jsx("p", { className: "text-xs text-muted-foreground", children: notification.description }), _jsxs("div", { className: "flex items-center pt-1", children: [_jsx(Clock, { className: "mr-1 size-3 text-muted-foreground" }), _jsx("span", { className: "text-xs text-muted-foreground", children: notification.time })] })] })] }, notification.id))) })) : (_jsxs("div", { className: "flex flex-col items-center justify-center px-4 py-8 text-center", children: [_jsx("div", { className: "mb-3 bg-muted p-3", children: _jsx(Bell, { className: "size-6 text-muted-foreground" }) }), _jsx("h3", { className: "text-sm font-medium", children: t('notificationsBox.emptyStateMessage') }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t('notificationsBox.emptyStateDescription') })] })) }), components.map((notification) => (_jsx(TabsContent, { value: notification.id, children: notification.component }, notification.id)))] }), hasUnread && (_jsx(CardFooter, { className: "flex items-center justify-end p-4", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 text-xs", onClick: markAllAsRead, children: [_jsx(Check, { className: "mr-1 size-3" }), t('notificationsBox.markAllAsRead')] }) }))] }) }) })] }));
|
|
91
91
|
};
|