@umituz/web-dashboard 1.0.1 → 1.0.4
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/domain/types/index.d.ts +4 -0
- package/dist/index.d.ts +107 -3
- package/dist/index.js +49 -65
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/hooks/index.js +1 -1
- package/dist/infrastructure/hooks/index.js.map +1 -1
- package/dist/infrastructure/utils/index.js +1 -1
- package/dist/infrastructure/utils/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/presentation/index.d.ts +0 -109
- package/dist/presentation/index.js +0 -497
- package/dist/presentation/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/infrastructure/utils/index.ts"],"sourcesContent":["/**\n * Dashboard Utilities\n *\n * Utility functions for dashboard operations\n */\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/infrastructure/utils/index.ts"],"sourcesContent":["/**\n * Dashboard Utilities\n *\n * Utility functions for dashboard operations\n */\n\n/**\n * Format notification timestamp to relative time\n *\n * @param createdAt - Notification creation timestamp\n * @param t - i18n translation function\n * @returns Formatted time string\n */\nexport function formatNotificationTime(\n createdAt: Date | string | number,\n t: (key: string, params?: Record<string, unknown>) => string\n): string {\n const date = new Date(createdAt);\n const secs = Math.floor((Date.now() - date.getTime()) / 1000);\n\n if (secs < 60) return t(\"dashboard.activityFeed.times.justNow\");\n if (secs < 3600) return t(\"dashboard.activityFeed.times.minutesAgo\", { val: Math.floor(secs / 60) });\n if (secs < 86400) return t(\"dashboard.activityFeed.times.hoursAgo\", { val: Math.floor(secs / 3600) });\n return t(\"dashboard.activityFeed.times.daysAgo\", { val: Math.floor(secs / 86400) });\n}\n\n/**\n * Check if a path is active\n *\n * @param currentPath - Current location pathname\n * @param targetPath - Target path to check\n * @returns True if paths match\n */\nexport function isPathActive(currentPath: string, targetPath: string): boolean {\n return currentPath === targetPath;\n}\n\n/**\n * Get page title from sidebar configuration\n *\n * @param pathname - Current pathname\n * @param sidebarGroups - Sidebar groups configuration\n * @param extraTitleMap - Extra title mappings\n * @returns Page title or null\n */\nexport function getPageTitle(\n pathname: string,\n sidebarGroups: Array<{ items: Array<{ path: string; label: string }> }>,\n extraTitleMap?: Record<string, string>\n): string | null {\n // Search in sidebar groups\n for (const group of sidebarGroups) {\n const item = group.items.find((i) => i.path === pathname);\n if (item) return item.label;\n }\n\n // Search in extra title map\n if (extraTitleMap?.[pathname]) {\n return extraTitleMap[pathname];\n }\n\n return null;\n}\n\n/**\n * Filter sidebar items by app type and enabled status\n *\n * @param items - Sidebar items to filter\n * @param user - Current user object\n * @returns Filtered items\n */\nexport function filterSidebarItems<T extends { enabled?: boolean; requiredApp?: \"mobile\" | \"web\" }>(\n items: T[],\n user?: { hasMobileApp?: boolean; hasWebApp?: boolean }\n): T[] {\n return items.filter((item) => {\n // Skip disabled items\n if (item.enabled === false) return false;\n\n // Skip items that require specific app types\n if (!item.requiredApp) return true;\n if (item.requiredApp === \"mobile\") return user?.hasMobileApp ?? false;\n if (item.requiredApp === \"web\") return user?.hasWebApp ?? false;\n\n return true;\n });\n}\n\n/**\n * Generate notification ID\n *\n * @returns Unique notification ID\n */\nexport function generateNotificationId(): string {\n return crypto.randomUUID();\n}\n"],"mappings":";;;AAaO,SAAS,uBACd,WACA,GACQ;AACR,QAAM,OAAO,IAAI,KAAK,SAAS;AAC/B,QAAM,OAAO,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK,GAAI;AAE5D,MAAI,OAAO,GAAI,QAAO,EAAE,sCAAsC;AAC9D,MAAI,OAAO,KAAM,QAAO,EAAE,2CAA2C,EAAE,KAAK,KAAK,MAAM,OAAO,EAAE,EAAE,CAAC;AACnG,MAAI,OAAO,MAAO,QAAO,EAAE,yCAAyC,EAAE,KAAK,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC;AACpG,SAAO,EAAE,wCAAwC,EAAE,KAAK,KAAK,MAAM,OAAO,KAAK,EAAE,CAAC;AACpF;AASO,SAAS,aAAa,aAAqB,YAA6B;AAC7E,SAAO,gBAAgB;AACzB;AAUO,SAAS,aACd,UACA,eACA,eACe;AAEf,aAAW,SAAS,eAAe;AACjC,UAAM,OAAO,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AACxD,QAAI,KAAM,QAAO,KAAK;AAAA,EACxB;AAGA,MAAI,gBAAgB,QAAQ,GAAG;AAC7B,WAAO,cAAc,QAAQ;AAAA,EAC/B;AAEA,SAAO;AACT;AASO,SAAS,mBACd,OACA,MACK;AACL,SAAO,MAAM,OAAO,CAAC,SAAS;AAE5B,QAAI,KAAK,YAAY,MAAO,QAAO;AAGnC,QAAI,CAAC,KAAK,YAAa,QAAO;AAC9B,QAAI,KAAK,gBAAgB,SAAU,QAAO,MAAM,gBAAgB;AAChE,QAAI,KAAK,gBAAgB,MAAO,QAAO,MAAM,aAAa;AAE1D,WAAO;AAAA,EACT,CAAC;AACH;AAOO,SAAS,yBAAiC;AAC/C,SAAO,OAAO,WAAW;AAC3B;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-dashboard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Dashboard Layout System - Customizable, themeable dashboard layouts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,12 +52,14 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"class-variance-authority": "^0.7.1",
|
|
54
54
|
"clsx": "^2.1.1",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
55
|
+
"lucide-react": "^0.577.0",
|
|
56
|
+
"tailwind-merge": "^3.5.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/react": "^19.2.14",
|
|
60
60
|
"@types/react-dom": "^19.2.3",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
62
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
61
63
|
"@umituz/web-design-system": "^2.0.0",
|
|
62
64
|
"eslint": "^10.0.2",
|
|
63
65
|
"react-i18next": "^16.5.4",
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { DashboardLayoutConfig, DashboardUser, DashboardNotification, DashboardHeaderProps, DashboardSidebarProps, SidebarGroup } from '../domain/types/index.js';
|
|
3
|
-
import 'lucide-react';
|
|
4
|
-
|
|
5
|
-
interface DashboardLayoutProps {
|
|
6
|
-
/** Layout configuration */
|
|
7
|
-
config: DashboardLayoutConfig;
|
|
8
|
-
/** Auth user */
|
|
9
|
-
user?: DashboardUser;
|
|
10
|
-
/** Auth loading state */
|
|
11
|
-
authLoading?: boolean;
|
|
12
|
-
/** Authenticated state */
|
|
13
|
-
isAuthenticated?: boolean;
|
|
14
|
-
/** Notifications */
|
|
15
|
-
notifications?: DashboardNotification[];
|
|
16
|
-
/** Logout function */
|
|
17
|
-
onLogout?: () => Promise<void>;
|
|
18
|
-
/** Mark all as read function */
|
|
19
|
-
onMarkAllRead?: () => void;
|
|
20
|
-
/** Dismiss notification function */
|
|
21
|
-
onDismissNotification?: (id: string) => void;
|
|
22
|
-
/** Login route for redirect */
|
|
23
|
-
loginRoute?: string;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Dashboard Layout Component
|
|
27
|
-
*
|
|
28
|
-
* Main layout wrapper for dashboard pages.
|
|
29
|
-
* Provides sidebar, header, and content area with responsive design.
|
|
30
|
-
*
|
|
31
|
-
* Features:
|
|
32
|
-
* - Collapsible sidebar
|
|
33
|
-
* - Mobile menu overlay
|
|
34
|
-
* - Breadcrumb page titles
|
|
35
|
-
* - Loading skeletons
|
|
36
|
-
* - Auth protection
|
|
37
|
-
*
|
|
38
|
-
* @param props - Dashboard layout props
|
|
39
|
-
*/
|
|
40
|
-
declare const DashboardLayout: ({ config, user, authLoading, isAuthenticated, notifications, onLogout, onMarkAllRead, onDismissNotification, loginRoute, }: DashboardLayoutProps) => react_jsx_runtime.JSX.Element | null;
|
|
41
|
-
|
|
42
|
-
interface DashboardHeaderPropsExtended extends DashboardHeaderProps {
|
|
43
|
-
/** Auth user */
|
|
44
|
-
user?: DashboardUser;
|
|
45
|
-
/** Notifications */
|
|
46
|
-
notifications?: DashboardNotification[];
|
|
47
|
-
/** Logout function */
|
|
48
|
-
onLogout?: () => Promise<void>;
|
|
49
|
-
/** Mark all as read function */
|
|
50
|
-
onMarkAllRead?: () => void;
|
|
51
|
-
/** Dismiss notification function */
|
|
52
|
-
onDismissNotification?: (id: string) => void;
|
|
53
|
-
/** Format date function */
|
|
54
|
-
formatDate?: (date: Date | string | number) => string;
|
|
55
|
-
/** Settings route */
|
|
56
|
-
settingsRoute?: string;
|
|
57
|
-
/** Profile route */
|
|
58
|
-
profileRoute?: string;
|
|
59
|
-
/** Billing route */
|
|
60
|
-
billingRoute?: string;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Dashboard Header Component
|
|
64
|
-
*
|
|
65
|
-
* Displays top navigation bar with theme toggle, notifications,
|
|
66
|
-
* user menu, and organisation selector.
|
|
67
|
-
*
|
|
68
|
-
* @param props - Dashboard header props
|
|
69
|
-
*/
|
|
70
|
-
declare const DashboardHeader: ({ collapsed, setCollapsed, setMobileOpen, title, user, notifications, onLogout, onMarkAllRead, onDismissNotification, formatDate, settingsRoute, profileRoute, billingRoute, }: DashboardHeaderPropsExtended) => react_jsx_runtime.JSX.Element;
|
|
71
|
-
|
|
72
|
-
interface DashboardSidebarPropsExtended extends DashboardSidebarProps {
|
|
73
|
-
/** Sidebar groups configuration */
|
|
74
|
-
sidebarGroups: SidebarGroup[];
|
|
75
|
-
/** Brand name */
|
|
76
|
-
brandName?: string;
|
|
77
|
-
/** Brand tagline */
|
|
78
|
-
brandTagline?: string;
|
|
79
|
-
/** Create post route */
|
|
80
|
-
createPostRoute?: string;
|
|
81
|
-
/** Auth user */
|
|
82
|
-
user?: DashboardUser;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Dashboard Sidebar Component
|
|
86
|
-
*
|
|
87
|
-
* Displays collapsible sidebar with navigation menu items.
|
|
88
|
-
* Supports app-based filtering (mobile/web) and collapsible groups.
|
|
89
|
-
*
|
|
90
|
-
* @param props - Dashboard sidebar props
|
|
91
|
-
*/
|
|
92
|
-
declare const DashboardSidebar: ({ collapsed, setCollapsed, sidebarGroups, brandName, brandTagline, createPostRoute, user, }: DashboardSidebarPropsExtended) => react_jsx_runtime.JSX.Element;
|
|
93
|
-
|
|
94
|
-
interface BrandLogoProps {
|
|
95
|
-
className?: string;
|
|
96
|
-
size?: number;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* BrandLogo Component
|
|
100
|
-
*
|
|
101
|
-
* Displays the application brand logo as an SVG.
|
|
102
|
-
* Supports custom sizing and styling through className.
|
|
103
|
-
*
|
|
104
|
-
* @param className - Optional CSS classes for styling
|
|
105
|
-
* @param size - Width and height in pixels (default: 32)
|
|
106
|
-
*/
|
|
107
|
-
declare const BrandLogo: ({ className, size }: BrandLogoProps) => react_jsx_runtime.JSX.Element;
|
|
108
|
-
|
|
109
|
-
export { BrandLogo, DashboardHeader, DashboardLayout, DashboardSidebar };
|
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// src/presentation/organisms/DashboardLayout.tsx
|
|
4
|
-
import { useState as useState3, useEffect } from "react";
|
|
5
|
-
import { useLocation as useLocation2, Outlet, Navigate } from "react-router-dom";
|
|
6
|
-
import { Skeleton } from "@umituz/web-design-system/atoms";
|
|
7
|
-
|
|
8
|
-
// src/presentation/molecules/DashboardSidebar.tsx
|
|
9
|
-
import { useState } from "react";
|
|
10
|
-
import { Link, useLocation } from "react-router-dom";
|
|
11
|
-
import { useTranslation } from "react-i18next";
|
|
12
|
-
import { Button } from "@umituz/web-design-system/atoms";
|
|
13
|
-
|
|
14
|
-
// src/presentation/molecules/BrandLogo.tsx
|
|
15
|
-
import { cn } from "@umituz/web-design-system/utils";
|
|
16
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
-
var BrandLogo = ({ className, size = 32 }) => {
|
|
18
|
-
return /* @__PURE__ */ jsxs(
|
|
19
|
-
"svg",
|
|
20
|
-
{
|
|
21
|
-
width: size,
|
|
22
|
-
height: size,
|
|
23
|
-
viewBox: "0 0 100 100",
|
|
24
|
-
fill: "none",
|
|
25
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
26
|
-
className: cn("shrink-0", className),
|
|
27
|
-
children: [
|
|
28
|
-
/* @__PURE__ */ jsx(
|
|
29
|
-
"rect",
|
|
30
|
-
{
|
|
31
|
-
x: "15",
|
|
32
|
-
y: "65",
|
|
33
|
-
width: "70",
|
|
34
|
-
height: "15",
|
|
35
|
-
rx: "4",
|
|
36
|
-
fill: "hsl(var(--primary))"
|
|
37
|
-
}
|
|
38
|
-
),
|
|
39
|
-
/* @__PURE__ */ jsx(
|
|
40
|
-
"rect",
|
|
41
|
-
{
|
|
42
|
-
x: "25",
|
|
43
|
-
y: "25",
|
|
44
|
-
width: "12",
|
|
45
|
-
height: "40",
|
|
46
|
-
rx: "2",
|
|
47
|
-
fill: "hsl(var(--primary))"
|
|
48
|
-
}
|
|
49
|
-
),
|
|
50
|
-
/* @__PURE__ */ jsx(
|
|
51
|
-
"rect",
|
|
52
|
-
{
|
|
53
|
-
x: "44",
|
|
54
|
-
y: "35",
|
|
55
|
-
width: "12",
|
|
56
|
-
height: "30",
|
|
57
|
-
rx: "2",
|
|
58
|
-
fill: "hsl(var(--primary))"
|
|
59
|
-
}
|
|
60
|
-
),
|
|
61
|
-
/* @__PURE__ */ jsx(
|
|
62
|
-
"rect",
|
|
63
|
-
{
|
|
64
|
-
x: "63",
|
|
65
|
-
y: "20",
|
|
66
|
-
width: "12",
|
|
67
|
-
height: "45",
|
|
68
|
-
rx: "2",
|
|
69
|
-
fill: "hsl(var(--primary))"
|
|
70
|
-
}
|
|
71
|
-
),
|
|
72
|
-
/* @__PURE__ */ jsx(
|
|
73
|
-
"rect",
|
|
74
|
-
{
|
|
75
|
-
x: "20",
|
|
76
|
-
y: "45",
|
|
77
|
-
width: "60",
|
|
78
|
-
height: "10",
|
|
79
|
-
rx: "2",
|
|
80
|
-
fill: "hsl(var(--secondary))"
|
|
81
|
-
}
|
|
82
|
-
),
|
|
83
|
-
/* @__PURE__ */ jsx(
|
|
84
|
-
"circle",
|
|
85
|
-
{
|
|
86
|
-
cx: "50",
|
|
87
|
-
cy: "20",
|
|
88
|
-
r: "5",
|
|
89
|
-
fill: "hsl(var(--secondary))"
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// src/presentation/molecules/DashboardSidebar.tsx
|
|
98
|
-
import { PenTool, Menu, ChevronLeft, ChevronDown, ChevronRight } from "lucide-react";
|
|
99
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
100
|
-
var DashboardSidebar = ({
|
|
101
|
-
collapsed,
|
|
102
|
-
setCollapsed,
|
|
103
|
-
sidebarGroups,
|
|
104
|
-
brandName = "App",
|
|
105
|
-
brandTagline = "grow smarter",
|
|
106
|
-
createPostRoute = "/dashboard/create",
|
|
107
|
-
user
|
|
108
|
-
}) => {
|
|
109
|
-
const location = useLocation();
|
|
110
|
-
const { t } = useTranslation();
|
|
111
|
-
const [collapsedGroups, setCollapsedGroups] = useState({});
|
|
112
|
-
const toggleGroup = (title) => {
|
|
113
|
-
setCollapsedGroups((prev) => ({
|
|
114
|
-
...prev,
|
|
115
|
-
[title]: !prev[title]
|
|
116
|
-
}));
|
|
117
|
-
};
|
|
118
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col", children: [
|
|
119
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex h-16 items-center gap-3 border-b border-sidebar-border px-4 transition-all duration-300", children: [
|
|
120
|
-
/* @__PURE__ */ jsx2(BrandLogo, { size: 32 }),
|
|
121
|
-
!collapsed && /* @__PURE__ */ jsxs2("div", { className: "flex flex-col -gap-1", children: [
|
|
122
|
-
/* @__PURE__ */ jsx2("span", { className: "text-2xl font-black text-sidebar-foreground tracking-tighter leading-none", children: brandName }),
|
|
123
|
-
/* @__PURE__ */ jsx2("span", { className: "text-[11px] font-bold text-primary/70 lowercase tracking-tight mt-2 ml-1 select-none underline decoration-primary/40 underline-offset-[6px] decoration-2", children: brandTagline })
|
|
124
|
-
] })
|
|
125
|
-
] }),
|
|
126
|
-
/* @__PURE__ */ jsx2("div", { className: "px-3 py-4 border-b border-sidebar-border/50", children: /* @__PURE__ */ jsx2(Link, { to: createPostRoute, children: /* @__PURE__ */ jsxs2(
|
|
127
|
-
Button,
|
|
128
|
-
{
|
|
129
|
-
variant: "default",
|
|
130
|
-
className: `w-full gap-3 shadow-glow transition-all active:scale-95 group overflow-hidden rounded-xl ${collapsed ? "px-0 justify-center h-10 w-10 mx-auto" : "justify-start px-4 h-11"}`,
|
|
131
|
-
title: collapsed ? t("sidebar.createPost") : void 0,
|
|
132
|
-
children: [
|
|
133
|
-
/* @__PURE__ */ jsx2(PenTool, { className: `shrink-0 transition-transform duration-300 ${collapsed ? "h-5 w-5" : "h-4 w-4 group-hover:scale-110"}` }),
|
|
134
|
-
!collapsed && /* @__PURE__ */ jsx2("span", { className: "font-bold tracking-tight", children: t("sidebar.createPost") })
|
|
135
|
-
]
|
|
136
|
-
}
|
|
137
|
-
) }) }),
|
|
138
|
-
/* @__PURE__ */ jsx2("nav", { className: "flex-1 overflow-y-auto px-2 py-3 scrollbar-hide", children: /* @__PURE__ */ jsx2("div", { className: "space-y-6", children: sidebarGroups.map((group) => {
|
|
139
|
-
const filteredItems = group.items.filter((item) => {
|
|
140
|
-
if (item.enabled === false) return false;
|
|
141
|
-
if (!item.requiredApp) return true;
|
|
142
|
-
if (item.requiredApp === "mobile") return user?.hasMobileApp;
|
|
143
|
-
if (item.requiredApp === "web") return user?.hasWebApp;
|
|
144
|
-
return true;
|
|
145
|
-
});
|
|
146
|
-
if (filteredItems.length === 0) return null;
|
|
147
|
-
const isGroupCollapsed = collapsedGroups[group.title];
|
|
148
|
-
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
149
|
-
!collapsed && /* @__PURE__ */ jsxs2(
|
|
150
|
-
"button",
|
|
151
|
-
{
|
|
152
|
-
onClick: () => toggleGroup(group.title),
|
|
153
|
-
className: "w-full flex items-center justify-between px-3 mb-2 group/header",
|
|
154
|
-
children: [
|
|
155
|
-
/* @__PURE__ */ jsx2("span", { className: "text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-hover/header:text-sidebar-foreground/70 transition-colors", children: group.title === "sidebar.ai" ? `${brandName} AI` : t(group.title) }),
|
|
156
|
-
isGroupCollapsed ? /* @__PURE__ */ jsx2(ChevronRight, { className: "h-3 w-3 text-sidebar-foreground/30 flex-shrink-0 group-hover/header:text-sidebar-foreground/50 transition-colors" }) : /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3 w-3 text-sidebar-foreground/30 flex-shrink-0 group-hover/header:text-sidebar-foreground/50 transition-colors" })
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
),
|
|
160
|
-
(!isGroupCollapsed || collapsed) && filteredItems.map((item) => {
|
|
161
|
-
const active = location.pathname === item.path;
|
|
162
|
-
return /* @__PURE__ */ jsxs2(
|
|
163
|
-
Link,
|
|
164
|
-
{
|
|
165
|
-
to: item.path,
|
|
166
|
-
className: `flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200 ${active ? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm" : "text-sidebar-foreground/70 hover:bg-sidebar-accent/40 hover:text-sidebar-foreground"} ${collapsed ? "justify-center" : ""}`,
|
|
167
|
-
title: collapsed ? t(item.label) : void 0,
|
|
168
|
-
children: [
|
|
169
|
-
/* @__PURE__ */ jsx2(item.icon, { className: `h-4 w-4 shrink-0 transition-transform ${active && "scale-110"}` }),
|
|
170
|
-
!collapsed && /* @__PURE__ */ jsx2("span", { children: t(item.label) })
|
|
171
|
-
]
|
|
172
|
-
},
|
|
173
|
-
item.path
|
|
174
|
-
);
|
|
175
|
-
})
|
|
176
|
-
] }, group.title);
|
|
177
|
-
}) }) }),
|
|
178
|
-
/* @__PURE__ */ jsx2("div", { className: "border-t border-sidebar-border p-3", children: /* @__PURE__ */ jsxs2("div", { className: `flex items-center ${collapsed ? "justify-center" : "justify-between"}`, children: [
|
|
179
|
-
!collapsed && /* @__PURE__ */ jsx2("p", { className: "text-[10px] uppercase tracking-wider text-sidebar-foreground/40 font-bold px-2", children: t("sidebar.system") }),
|
|
180
|
-
/* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "icon", onClick: () => setCollapsed(!collapsed), className: "text-sidebar-foreground/70", children: collapsed ? /* @__PURE__ */ jsx2(Menu, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx2(ChevronLeft, { className: "h-4 w-4" }) })
|
|
181
|
-
] }) })
|
|
182
|
-
] });
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// src/presentation/organisms/DashboardHeader.tsx
|
|
186
|
-
import React, { useState as useState2, useCallback } from "react";
|
|
187
|
-
import {
|
|
188
|
-
Bell,
|
|
189
|
-
X,
|
|
190
|
-
Sun,
|
|
191
|
-
Moon,
|
|
192
|
-
Menu as Menu2,
|
|
193
|
-
User,
|
|
194
|
-
Settings,
|
|
195
|
-
LogOut,
|
|
196
|
-
ChevronDown as ChevronDown2,
|
|
197
|
-
CreditCard
|
|
198
|
-
} from "lucide-react";
|
|
199
|
-
import { Button as Button2 } from "@umituz/web-design-system/atoms";
|
|
200
|
-
import { useNavigate } from "react-router-dom";
|
|
201
|
-
import { useTranslation as useTranslation2 } from "react-i18next";
|
|
202
|
-
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
203
|
-
var DashboardHeader = ({
|
|
204
|
-
collapsed,
|
|
205
|
-
setCollapsed,
|
|
206
|
-
setMobileOpen,
|
|
207
|
-
title,
|
|
208
|
-
user,
|
|
209
|
-
notifications = [],
|
|
210
|
-
onLogout,
|
|
211
|
-
onMarkAllRead,
|
|
212
|
-
onDismissNotification,
|
|
213
|
-
formatDate,
|
|
214
|
-
settingsRoute = "/dashboard/settings",
|
|
215
|
-
profileRoute = "/dashboard/profile",
|
|
216
|
-
billingRoute = "/dashboard/billing"
|
|
217
|
-
}) => {
|
|
218
|
-
const navigate = useNavigate();
|
|
219
|
-
const { t } = useTranslation2();
|
|
220
|
-
const [notifOpen, setNotifOpen] = useState2(false);
|
|
221
|
-
const [profileOpen, setProfileOpen] = useState2(false);
|
|
222
|
-
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
223
|
-
const markAllRead = () => {
|
|
224
|
-
onMarkAllRead?.();
|
|
225
|
-
};
|
|
226
|
-
const formatTimeAgo = useCallback((createdAt) => {
|
|
227
|
-
if (!formatDate) return "";
|
|
228
|
-
const date = new Date(createdAt);
|
|
229
|
-
const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
230
|
-
if (secs < 60) return t("dashboard.activityFeed.times.justNow");
|
|
231
|
-
if (secs < 3600) return t("dashboard.activityFeed.times.minutesAgo", { val: Math.floor(secs / 60) });
|
|
232
|
-
if (secs < 86400) return t("dashboard.activityFeed.times.hoursAgo", { val: Math.floor(secs / 3600) });
|
|
233
|
-
return t("dashboard.activityFeed.times.daysAgo", { val: Math.floor(secs / 86400) });
|
|
234
|
-
}, [t, formatDate]);
|
|
235
|
-
const handleLogout = async () => {
|
|
236
|
-
try {
|
|
237
|
-
await onLogout?.();
|
|
238
|
-
navigate("/login");
|
|
239
|
-
} catch (error) {
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
const ThemeToggle = () => {
|
|
243
|
-
const [resolvedTheme, setResolvedTheme] = React.useState("light");
|
|
244
|
-
return /* @__PURE__ */ jsx3(
|
|
245
|
-
Button2,
|
|
246
|
-
{
|
|
247
|
-
variant: "ghost",
|
|
248
|
-
size: "icon",
|
|
249
|
-
onClick: () => setResolvedTheme(resolvedTheme === "light" ? "dark" : "light"),
|
|
250
|
-
className: "text-muted-foreground h-9 w-9",
|
|
251
|
-
title: resolvedTheme === "dark" ? t("common.tooltips.switchLight") : t("common.tooltips.switchDark"),
|
|
252
|
-
children: resolvedTheme === "dark" ? /* @__PURE__ */ jsx3(Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx3(Moon, { className: "h-4 w-4" })
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
};
|
|
256
|
-
return /* @__PURE__ */ jsxs3("header", { className: "flex h-14 items-center justify-between border-b border-border bg-card/50 backdrop-blur-md px-4 shrink-0 z-30", children: [
|
|
257
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3", children: [
|
|
258
|
-
/* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", className: "md:hidden", onClick: () => setMobileOpen(true), children: /* @__PURE__ */ jsx3(Menu2, { className: "h-5 w-5" }) }),
|
|
259
|
-
collapsed && /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "icon", className: "hidden md:inline-flex", onClick: () => setCollapsed(false), children: /* @__PURE__ */ jsx3(Menu2, { className: "h-5 w-5" }) }),
|
|
260
|
-
/* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold text-foreground", children: title })
|
|
261
|
-
] }),
|
|
262
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
263
|
-
/* @__PURE__ */ jsx3(ThemeToggle, {}),
|
|
264
|
-
/* @__PURE__ */ jsxs3("div", { className: "relative", children: [
|
|
265
|
-
/* @__PURE__ */ jsxs3(
|
|
266
|
-
Button2,
|
|
267
|
-
{
|
|
268
|
-
variant: "ghost",
|
|
269
|
-
size: "icon",
|
|
270
|
-
className: "text-muted-foreground relative h-9 w-9",
|
|
271
|
-
onClick: () => {
|
|
272
|
-
setNotifOpen(!notifOpen);
|
|
273
|
-
setProfileOpen(false);
|
|
274
|
-
},
|
|
275
|
-
children: [
|
|
276
|
-
/* @__PURE__ */ jsx3(Bell, { className: "h-4 w-4" }),
|
|
277
|
-
unreadCount > 0 && /* @__PURE__ */ jsxs3("span", { className: "absolute top-2 right-2 flex h-2 w-2", children: [
|
|
278
|
-
/* @__PURE__ */ jsx3("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-destructive opacity-75" }),
|
|
279
|
-
/* @__PURE__ */ jsx3("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-destructive" })
|
|
280
|
-
] })
|
|
281
|
-
]
|
|
282
|
-
}
|
|
283
|
-
),
|
|
284
|
-
notifOpen && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
285
|
-
/* @__PURE__ */ jsx3("div", { className: "fixed inset-0 z-40", onClick: () => setNotifOpen(false) }),
|
|
286
|
-
/* @__PURE__ */ jsxs3("div", { className: "absolute top-12 right-0 w-80 bg-popover border border-border rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200", children: [
|
|
287
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border bg-muted/50", children: [
|
|
288
|
-
/* @__PURE__ */ jsx3("h3", { className: "text-xs font-bold uppercase tracking-wider text-foreground", children: t("dashboard.notifications.title") }),
|
|
289
|
-
unreadCount > 0 && /* @__PURE__ */ jsx3("button", { onClick: markAllRead, className: "text-[10px] font-bold text-primary hover:underline uppercase", children: t("dashboard.notifications.markAllRead") })
|
|
290
|
-
] }),
|
|
291
|
-
/* @__PURE__ */ jsxs3("div", { className: "max-h-[400px] overflow-y-auto", children: [
|
|
292
|
-
notifications.map((n) => /* @__PURE__ */ jsxs3("div", { className: `px-4 py-3 border-b border-border last:border-0 flex items-start gap-3 transition-colors hover:bg-muted/30 ${!n.read ? "bg-primary/5" : ""}`, children: [
|
|
293
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
294
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm text-foreground leading-snug", children: n.text }),
|
|
295
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-[10px] text-muted-foreground mt-1 flex items-center gap-1", children: [
|
|
296
|
-
/* @__PURE__ */ jsx3("span", { className: "inline-block w-1 h-1 rounded-full bg-muted-foreground/30" }),
|
|
297
|
-
formatTimeAgo(n.createdAt)
|
|
298
|
-
] })
|
|
299
|
-
] }),
|
|
300
|
-
/* @__PURE__ */ jsx3(
|
|
301
|
-
"button",
|
|
302
|
-
{
|
|
303
|
-
onClick: () => onDismissNotification?.(n.id),
|
|
304
|
-
className: "text-muted-foreground/50 hover:text-foreground shrink-0 transition-colors",
|
|
305
|
-
children: /* @__PURE__ */ jsx3(X, { className: "h-3 w-3" })
|
|
306
|
-
}
|
|
307
|
-
)
|
|
308
|
-
] }, n.id)),
|
|
309
|
-
notifications.length === 0 && /* @__PURE__ */ jsxs3("div", { className: "px-4 py-10 text-center", children: [
|
|
310
|
-
/* @__PURE__ */ jsx3("div", { className: "mx-auto w-10 h-10 rounded-full bg-muted flex items-center justify-center mb-3", children: /* @__PURE__ */ jsx3(Bell, { className: "h-5 w-5 text-muted-foreground/50" }) }),
|
|
311
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: t("dashboard.notifications.none") })
|
|
312
|
-
] })
|
|
313
|
-
] })
|
|
314
|
-
] })
|
|
315
|
-
] })
|
|
316
|
-
] }),
|
|
317
|
-
/* @__PURE__ */ jsx3("div", { className: "h-6 w-px bg-border mx-1" }),
|
|
318
|
-
/* @__PURE__ */ jsxs3("div", { className: "relative", children: [
|
|
319
|
-
/* @__PURE__ */ jsxs3(
|
|
320
|
-
"button",
|
|
321
|
-
{
|
|
322
|
-
onClick: () => {
|
|
323
|
-
setProfileOpen(!profileOpen);
|
|
324
|
-
setNotifOpen(false);
|
|
325
|
-
},
|
|
326
|
-
className: "flex items-center gap-2 p-1 pl-1 rounded-full hover:bg-muted transition-colors group",
|
|
327
|
-
children: [
|
|
328
|
-
/* @__PURE__ */ jsx3("div", { className: "w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-[10px] font-bold text-primary overflow-hidden border border-primary/20 ring-primary/20 group-hover:ring-4 transition-all", children: user?.avatar && /* @__PURE__ */ jsx3("img", { src: user.avatar, alt: "User", className: "w-full h-full object-cover" }) }),
|
|
329
|
-
/* @__PURE__ */ jsx3(ChevronDown2, { className: `h-4 w-4 text-muted-foreground transition-transform duration-200 ${profileOpen && "rotate-180"}` })
|
|
330
|
-
]
|
|
331
|
-
}
|
|
332
|
-
),
|
|
333
|
-
profileOpen && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
334
|
-
/* @__PURE__ */ jsx3("div", { className: "fixed inset-0 z-40", onClick: () => setProfileOpen(false) }),
|
|
335
|
-
/* @__PURE__ */ jsxs3("div", { className: "absolute top-12 right-0 w-56 bg-popover border border-border rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200 p-1.5", children: [
|
|
336
|
-
/* @__PURE__ */ jsxs3("div", { className: "px-3 py-2 border-b border-border/50 mb-1", children: [
|
|
337
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm font-bold text-foreground", children: user?.name || t("common.roles.user") }),
|
|
338
|
-
/* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground truncate", children: user?.email })
|
|
339
|
-
] }),
|
|
340
|
-
/* @__PURE__ */ jsxs3("div", { className: "space-y-0.5", children: [
|
|
341
|
-
/* @__PURE__ */ jsxs3(
|
|
342
|
-
"button",
|
|
343
|
-
{
|
|
344
|
-
onClick: () => {
|
|
345
|
-
navigate(profileRoute);
|
|
346
|
-
setProfileOpen(false);
|
|
347
|
-
},
|
|
348
|
-
className: "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors",
|
|
349
|
-
children: [
|
|
350
|
-
/* @__PURE__ */ jsx3(User, { className: "h-4 w-4 text-muted-foreground" }),
|
|
351
|
-
t("common.profile")
|
|
352
|
-
]
|
|
353
|
-
}
|
|
354
|
-
),
|
|
355
|
-
/* @__PURE__ */ jsxs3(
|
|
356
|
-
"button",
|
|
357
|
-
{
|
|
358
|
-
onClick: () => {
|
|
359
|
-
navigate(billingRoute);
|
|
360
|
-
setProfileOpen(false);
|
|
361
|
-
},
|
|
362
|
-
className: "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors",
|
|
363
|
-
children: [
|
|
364
|
-
/* @__PURE__ */ jsx3(CreditCard, { className: "h-4 w-4 text-muted-foreground" }),
|
|
365
|
-
t("common.billing")
|
|
366
|
-
]
|
|
367
|
-
}
|
|
368
|
-
),
|
|
369
|
-
/* @__PURE__ */ jsxs3(
|
|
370
|
-
"button",
|
|
371
|
-
{
|
|
372
|
-
onClick: () => {
|
|
373
|
-
navigate(settingsRoute);
|
|
374
|
-
setProfileOpen(false);
|
|
375
|
-
},
|
|
376
|
-
className: "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-foreground hover:bg-muted rounded-lg transition-colors",
|
|
377
|
-
children: [
|
|
378
|
-
/* @__PURE__ */ jsx3(Settings, { className: "h-4 w-4 text-muted-foreground" }),
|
|
379
|
-
t("common.settings")
|
|
380
|
-
]
|
|
381
|
-
}
|
|
382
|
-
)
|
|
383
|
-
] }),
|
|
384
|
-
/* @__PURE__ */ jsx3("div", { className: "h-px bg-border my-1.5" }),
|
|
385
|
-
/* @__PURE__ */ jsxs3(
|
|
386
|
-
"button",
|
|
387
|
-
{
|
|
388
|
-
onClick: handleLogout,
|
|
389
|
-
className: "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors font-medium",
|
|
390
|
-
children: [
|
|
391
|
-
/* @__PURE__ */ jsx3(LogOut, { className: "h-4 w-4" }),
|
|
392
|
-
t("common.logout")
|
|
393
|
-
]
|
|
394
|
-
}
|
|
395
|
-
)
|
|
396
|
-
] })
|
|
397
|
-
] })
|
|
398
|
-
] })
|
|
399
|
-
] })
|
|
400
|
-
] });
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// src/presentation/organisms/DashboardLayout.tsx
|
|
404
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
405
|
-
var DashboardLayout = ({
|
|
406
|
-
config,
|
|
407
|
-
user,
|
|
408
|
-
authLoading = false,
|
|
409
|
-
isAuthenticated = true,
|
|
410
|
-
notifications = [],
|
|
411
|
-
onLogout,
|
|
412
|
-
onMarkAllRead,
|
|
413
|
-
onDismissNotification,
|
|
414
|
-
loginRoute = "/login"
|
|
415
|
-
}) => {
|
|
416
|
-
const location = useLocation2();
|
|
417
|
-
const [collapsed, setCollapsed] = useState3(false);
|
|
418
|
-
const [mobileOpen, setMobileOpen] = useState3(false);
|
|
419
|
-
const [loading, setLoading] = useState3(true);
|
|
420
|
-
useEffect(() => {
|
|
421
|
-
setLoading(true);
|
|
422
|
-
const timer = setTimeout(() => setLoading(false), 300);
|
|
423
|
-
return () => clearTimeout(timer);
|
|
424
|
-
}, [location.pathname]);
|
|
425
|
-
useEffect(() => {
|
|
426
|
-
setMobileOpen(false);
|
|
427
|
-
}, [location.pathname]);
|
|
428
|
-
if (authLoading) return null;
|
|
429
|
-
if (!isAuthenticated) return /* @__PURE__ */ jsx4(Navigate, { to: loginRoute, replace: true });
|
|
430
|
-
const activeItem = config.sidebarGroups.flatMap((group) => group.items).find((i) => i.path === location.pathname);
|
|
431
|
-
const getTitle = () => {
|
|
432
|
-
if (!activeItem) return config.extraTitleMap?.[location.pathname] || "Dashboard";
|
|
433
|
-
return activeItem.label;
|
|
434
|
-
};
|
|
435
|
-
const currentTitle = getTitle();
|
|
436
|
-
return /* @__PURE__ */ jsxs4("div", { className: "flex h-screen w-full bg-background font-sans", children: [
|
|
437
|
-
/* @__PURE__ */ jsx4(
|
|
438
|
-
"aside",
|
|
439
|
-
{
|
|
440
|
-
className: `hidden md:flex flex-col shrink-0 border-r border-sidebar-border bg-sidebar transition-all duration-300 ${collapsed ? "w-16" : "w-60"}`,
|
|
441
|
-
children: /* @__PURE__ */ jsx4(
|
|
442
|
-
DashboardSidebar,
|
|
443
|
-
{
|
|
444
|
-
collapsed,
|
|
445
|
-
setCollapsed,
|
|
446
|
-
sidebarGroups: config.sidebarGroups,
|
|
447
|
-
brandName: config.brandName,
|
|
448
|
-
brandTagline: config.brandTagline,
|
|
449
|
-
user
|
|
450
|
-
}
|
|
451
|
-
)
|
|
452
|
-
}
|
|
453
|
-
),
|
|
454
|
-
mobileOpen && /* @__PURE__ */ jsxs4("div", { className: "fixed inset-0 z-50 md:hidden", children: [
|
|
455
|
-
/* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-background/80 backdrop-blur-sm", onClick: () => setMobileOpen(false) }),
|
|
456
|
-
/* @__PURE__ */ jsx4("aside", { className: "absolute left-0 top-0 h-full w-60 border-r border-sidebar-border bg-sidebar shadow-xl", children: /* @__PURE__ */ jsx4(
|
|
457
|
-
DashboardSidebar,
|
|
458
|
-
{
|
|
459
|
-
collapsed: false,
|
|
460
|
-
setCollapsed: () => setMobileOpen(false),
|
|
461
|
-
sidebarGroups: config.sidebarGroups,
|
|
462
|
-
brandName: config.brandName,
|
|
463
|
-
brandTagline: config.brandTagline,
|
|
464
|
-
user
|
|
465
|
-
}
|
|
466
|
-
) })
|
|
467
|
-
] }),
|
|
468
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex flex-1 flex-col overflow-hidden min-w-0", children: [
|
|
469
|
-
/* @__PURE__ */ jsx4(
|
|
470
|
-
DashboardHeader,
|
|
471
|
-
{
|
|
472
|
-
collapsed,
|
|
473
|
-
setCollapsed,
|
|
474
|
-
setMobileOpen,
|
|
475
|
-
title: currentTitle,
|
|
476
|
-
user,
|
|
477
|
-
notifications,
|
|
478
|
-
onLogout,
|
|
479
|
-
onMarkAllRead,
|
|
480
|
-
onDismissNotification
|
|
481
|
-
}
|
|
482
|
-
),
|
|
483
|
-
/* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-y-auto p-4 md:p-8", children: loading ? /* @__PURE__ */ jsxs4("div", { className: "mx-auto w-full max-w-7xl space-y-6", children: [
|
|
484
|
-
/* @__PURE__ */ jsx4(Skeleton, { className: "h-8 w-1/3 rounded-xl" }),
|
|
485
|
-
/* @__PURE__ */ jsx4("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx4(Skeleton, { className: "h-28 rounded-2xl" }, i)) }),
|
|
486
|
-
/* @__PURE__ */ jsx4(Skeleton, { className: "h-64 rounded-[32px]" })
|
|
487
|
-
] }) : /* @__PURE__ */ jsx4(Outlet, {}) })
|
|
488
|
-
] })
|
|
489
|
-
] });
|
|
490
|
-
};
|
|
491
|
-
export {
|
|
492
|
-
BrandLogo,
|
|
493
|
-
DashboardHeader,
|
|
494
|
-
DashboardLayout,
|
|
495
|
-
DashboardSidebar
|
|
496
|
-
};
|
|
497
|
-
//# sourceMappingURL=index.js.map
|