@umituz/web-dashboard 3.1.6 → 3.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "3.1.6",
3
+ "version": "3.1.7",
4
4
  "description": "Dashboard Layout System - Comprehensive analytics, calendar, customizable layouts, and config-based architecture",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -145,7 +145,7 @@ export const AnalyticsChart = ({ config, className, height }: AnalyticsChartProp
145
145
  innerRadius={config.type === "donut" ? 40 : 0}
146
146
  dataKey="value"
147
147
  >
148
- {config.data.map((entry, index: number) => (
148
+ {config.data.map((entry: any, index: number) => (
149
149
  <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
150
150
  ))}
151
151
  </Pie>
@@ -2,10 +2,9 @@
2
2
  * useAnalytics Hook
3
3
  *
4
4
  * Core analytics hook for fetching and managing analytics data
5
- * Enhanced with config support and advanced analytics services
6
5
  */
7
6
 
8
- import { useState, useCallback, useEffect, useMemo } from "react";
7
+ import { useState, useCallback, useEffect } from "react";
9
8
  import type {
10
9
  KPIs,
11
10
  TimeSeriesData,
@@ -14,8 +13,6 @@ import type {
14
13
  AnalyticsExportOptions,
15
14
  } from "../types/analytics";
16
15
  import { createKPI } from "../utils/analytics";
17
- import { analyticsEngineService, performanceService } from "../services";
18
- import type { DashboardConfig } from "../../../domain/config";
19
16
 
20
17
  interface UseAnalyticsOptions {
21
18
  /** Analytics API base URL */
@@ -24,10 +21,6 @@ interface UseAnalyticsOptions {
24
21
  initialDateRange?: DateRangeValue;
25
22
  /** Auto-refresh interval in ms (0 to disable) */
26
23
  refreshInterval?: number;
27
- /** Dashboard configuration */
28
- config?: DashboardConfig;
29
- /** Error callback */
30
- onError?: (error: Error) => void;
31
24
  }
32
25
 
33
26
  interface AnalyticsData {
@@ -50,19 +43,7 @@ interface AnalyticsData {
50
43
  * @returns Analytics data and actions
51
44
  */
52
45
  export function useAnalytics(options: UseAnalyticsOptions = {}) {
53
- const {
54
- apiUrl = "/api/analytics",
55
- initialDateRange,
56
- refreshInterval = 0,
57
- config,
58
- onError,
59
- } = options;
60
-
61
- // Apply config defaults
62
- const effectiveRefreshInterval = useMemo(
63
- () => refreshInterval || config?.data?.refreshInterval || 0,
64
- [refreshInterval, config]
65
- );
46
+ const { apiUrl = "/api/analytics", initialDateRange, refreshInterval = 0 } = options;
66
47
 
67
48
  // State
68
49
  const [dateRange, setDateRange] = useState<DateRangeValue>(
@@ -181,81 +162,11 @@ export function useAnalytics(options: UseAnalyticsOptions = {}) {
181
162
 
182
163
  // Auto-refresh
183
164
  useEffect(() => {
184
- if (effectiveRefreshInterval > 0) {
185
- const interval = setInterval(fetchAnalytics, effectiveRefreshInterval);
165
+ if (refreshInterval > 0) {
166
+ const interval = setInterval(fetchAnalytics, refreshInterval);
186
167
  return () => clearInterval(interval);
187
168
  }
188
- }, [fetchAnalytics, effectiveRefreshInterval]);
189
-
190
- // Advanced analytics methods
191
- const calculateRetention = useCallback(
192
- (userData: import("../services/AnalyticsEngineService").UserData[]) => {
193
- try {
194
- return analyticsEngineService.calculateRetention(userData);
195
- } catch (err) {
196
- onError?.(err as Error);
197
- return [];
198
- }
199
- },
200
- [onError]
201
- );
202
-
203
- const calculateFunnel = useCallback(
204
- (data: import("../services/AnalyticsEngineService").FunnelItem[], steps: string[]) => {
205
- try {
206
- return analyticsEngineService.calculateFunnel(data, steps);
207
- } catch (err) {
208
- onError?.(err as Error);
209
- return null;
210
- }
211
- },
212
- [onError]
213
- );
214
-
215
- const segmentUsers = useCallback(
216
- (userData: import("../services/AnalyticsEngineService").UserData[]) => {
217
- try {
218
- return analyticsEngineService.segmentUsers(userData);
219
- } catch (err) {
220
- onError?.(err as Error);
221
- return [];
222
- }
223
- },
224
- [onError]
225
- );
226
-
227
- const predictUserBehavior = useCallback(
228
- (user: Record<string, unknown>, historicalData: import("../services/AnalyticsEngineService").UserData[]) => {
229
- try {
230
- return analyticsEngineService.predictUserBehavior(user, historicalData);
231
- } catch (err) {
232
- onError?.(err as Error);
233
- return null;
234
- }
235
- },
236
- [onError]
237
- );
238
-
239
- const getPerformanceMetrics = useCallback(() => {
240
- try {
241
- return performanceService.getDashboardMetrics();
242
- } catch (err) {
243
- onError?.(err as Error);
244
- return null;
245
- }
246
- }, [onError]);
247
-
248
- const getRealtimeMetrics = useCallback(
249
- (previous?: import("../services/PerformanceService").RealtimeMetrics) => {
250
- try {
251
- return performanceService.simulateRealtimeMetrics(previous);
252
- } catch (err) {
253
- onError?.(err as Error);
254
- return null;
255
- }
256
- },
257
- [onError]
258
- );
169
+ }, [fetchAnalytics, refreshInterval]);
259
170
 
260
171
  return {
261
172
  ...data,
@@ -263,12 +174,5 @@ export function useAnalytics(options: UseAnalyticsOptions = {}) {
263
174
  updateDateRange,
264
175
  refresh,
265
176
  exportData,
266
- // Advanced analytics
267
- calculateRetention,
268
- calculateFunnel,
269
- segmentUsers,
270
- predictUserBehavior,
271
- getPerformanceMetrics,
272
- getRealtimeMetrics,
273
177
  };
274
178
  }
@@ -17,26 +17,6 @@ export {
17
17
  useAnalytics,
18
18
  } from "./hooks";
19
19
 
20
- // Services
21
- export {
22
- AnalyticsEngineService,
23
- analyticsEngineService,
24
- PerformanceService,
25
- performanceService,
26
- } from "./services";
27
- export type {
28
- ConversionPath,
29
- HeatmapData,
30
- UserBehaviorPrediction,
31
- UserData,
32
- ConversionData,
33
- FunnelItem,
34
- ActivityItem,
35
- PerformanceMetric,
36
- DashboardMetrics,
37
- RealtimeMetrics,
38
- } from "./services";
39
-
40
20
  // Utils
41
21
  export {
42
22
  formatNumber,
@@ -219,7 +219,7 @@ export function getDateRangePresets(): DateRangePreset[] {
219
219
  export function aggregateByPeriod(
220
220
  data: Array<{ date: string; [key: string]: number | string }>,
221
221
  period: "day" | "week" | "month" = "day"
222
- ): Array<{ date: string; [key: string]: string | number }> {
222
+ ): Array<{ date: string; [key: string]: number }> {
223
223
  const grouped = new Map<string, Array<typeof data[0]>>();
224
224
 
225
225
  data.forEach((item) => {
@@ -244,14 +244,13 @@ export function aggregateByPeriod(
244
244
  });
245
245
 
246
246
  return Array.from(grouped.entries()).map(([date, items]) => {
247
- const aggregated: { date: string; [key: string]: string | number } = { date };
247
+ const aggregated: any = { date };
248
248
 
249
249
  // Sum all numeric fields
250
250
  items.forEach((item) => {
251
251
  Object.entries(item).forEach(([key, value]) => {
252
252
  if (key !== "date" && typeof value === "number") {
253
- const currentValue = aggregated[key];
254
- aggregated[key] = typeof currentValue === "number" ? currentValue + value : value;
253
+ aggregated[key] = (aggregated[key] || 0) + value;
255
254
  }
256
255
  });
257
256
  });
@@ -24,8 +24,6 @@ export interface RegisterData {
24
24
  email: string;
25
25
  /** Password */
26
26
  password: string;
27
- /** Password confirmation */
28
- confirmPassword?: string;
29
27
  /** Full name */
30
28
  name?: string;
31
29
  /** Additional user data */
@@ -93,9 +91,9 @@ export interface User {
93
91
  */
94
92
  export interface AuthActions {
95
93
  /** Login with email/password */
96
- login: (credentials: LoginCredentials) => Promise<User>;
94
+ login: (credentials: LoginCredentials) => Promise<void>;
97
95
  /** Register new account */
98
- register: (data: RegisterData) => Promise<User>;
96
+ register: (data: RegisterData) => Promise<void>;
99
97
  /** Logout current user */
100
98
  logout: () => Promise<void>;
101
99
  /** Send password reset email */
@@ -105,7 +103,7 @@ export interface AuthActions {
105
103
  /** Update user profile */
106
104
  updateProfile: (data: Partial<User>) => Promise<void>;
107
105
  /** Refresh authentication session */
108
- refresh: () => Promise<User | null>;
106
+ refresh: () => Promise<void>;
109
107
  }
110
108
 
111
109
  /**
@@ -195,7 +195,7 @@ export function isEmailVerified(user: User | null): boolean {
195
195
  * @returns Formatted date string or empty string
196
196
  */
197
197
  export function formatUserCreatedAt(user: User | null, locale: string = "en-US"): string {
198
- if (!user || !user.createdAt) return "";
198
+ if (!user.createdAt) return "";
199
199
  return new Date(user.createdAt).toLocaleDateString(locale, {
200
200
  year: "numeric",
201
201
  month: "long",
@@ -11,7 +11,7 @@ import type { BillingPortalProps } from "../types/billing";
11
11
  import { UsageCard } from "./UsageCard";
12
12
  import { PaymentMethodsList } from "./PaymentMethodsList";
13
13
  import { InvoiceCard } from "./InvoiceCard";
14
- import { getDaysRemaining, getStatusColor, getStatusLabel, formatPrice, getPlanPrice } from "../utils/billing";
14
+ import { getDaysRemaining, getStatusColor, getStatusLabel, formatPrice } from "../utils/billing";
15
15
 
16
16
  export const BillingPortal = ({
17
17
  billing,
@@ -22,14 +22,6 @@ interface UseBillingOptions {
22
22
  initialData?: BillingSummary;
23
23
  }
24
24
 
25
- interface CardDetails {
26
- last4: string;
27
- brand: string;
28
- expiryMonth: number;
29
- expiryYear: number;
30
- name?: string;
31
- }
32
-
33
25
  interface BillingActions {
34
26
  /** Load billing summary */
35
27
  loadBilling: () => Promise<void>;
@@ -40,7 +32,7 @@ interface BillingActions {
40
32
  /** Update billing cycle */
41
33
  updateCycle: (cycle: BillingCycle) => Promise<void>;
42
34
  /** Add payment method */
43
- addPaymentMethod: (paymentMethodDetails: CardDetails) => Promise<PaymentMethod>;
35
+ addPaymentMethod: (paymentMethodDetails: any) => Promise<PaymentMethod>;
44
36
  /** Remove payment method */
45
37
  removePaymentMethod: (methodId: string) => Promise<void>;
46
38
  /** Set default payment method */
@@ -264,7 +256,7 @@ export function useBilling(options: UseBillingOptions = {}) {
264
256
  }, [apiUrl]);
265
257
 
266
258
  // Add payment method
267
- const addPaymentMethod = useCallback(async (paymentMethodDetails: CardDetails) => {
259
+ const addPaymentMethod = useCallback(async (paymentMethodDetails: any) => {
268
260
  setIsLoading(true);
269
261
  setError(null);
270
262
 
@@ -9,33 +9,12 @@ import type {
9
9
  Currency,
10
10
  PlanTier,
11
11
  Subscription,
12
- SubscriptionStatus,
13
12
  PaymentMethod,
14
13
  Invoice,
15
14
  InvoiceStatus,
16
15
  UsageMetric,
17
16
  } from "../types/billing";
18
17
 
19
- /**
20
- * Format number with K/M/B suffixes
21
- *
22
- * @param num - Number to format
23
- * @param decimals - Number of decimal places (default: 1)
24
- * @returns Formatted string
25
- */
26
- export function formatNumber(num: number, decimals: number = 1): string {
27
- if (num >= 1_000_000_000) {
28
- return (num / 1_000_000_000).toFixed(decimals) + "B";
29
- }
30
- if (num >= 1_000_000) {
31
- return (num / 1_000_000).toFixed(decimals) + "M";
32
- }
33
- if (num >= 1_000) {
34
- return (num / 1_000).toFixed(decimals) + "K";
35
- }
36
- return num.toFixed(decimals);
37
- }
38
-
39
18
  /**
40
19
  * Format price with currency
41
20
  *
@@ -135,7 +114,6 @@ export function getStatusColor(status: SubscriptionStatus): string {
135
114
  canceled: "text-gray-600 dark:text-gray-500",
136
115
  unpaid: "text-red-600 dark:text-red-500",
137
116
  incomplete: "text-yellow-600 dark:text-yellow-500",
138
- revoked: "text-purple-600 dark:text-purple-500",
139
117
  };
140
118
 
141
119
  return colorMap[status] || "text-gray-600";
@@ -155,7 +133,6 @@ export function getStatusLabel(status: SubscriptionStatus): string {
155
133
  canceled: "Canceled",
156
134
  unpaid: "Unpaid",
157
135
  incomplete: "Incomplete",
158
- revoked: "Revoked",
159
136
  };
160
137
 
161
138
  return labelMap[status] || status;
@@ -174,7 +151,6 @@ export function getInvoiceStatusColor(status: InvoiceStatus): string {
174
151
  paid: "text-green-600 dark:text-green-500",
175
152
  void: "text-gray-600 dark:text-gray-500",
176
153
  uncollectible: "text-red-600 dark:text-red-500",
177
- refunded: "text-blue-600 dark:text-blue-500",
178
154
  };
179
155
 
180
156
  return colorMap[status] || "text-gray-600";
@@ -193,7 +169,6 @@ export function getInvoiceStatusLabel(status: InvoiceStatus): string {
193
169
  paid: "Paid",
194
170
  void: "Void",
195
171
  uncollectible: "Uncollectible",
196
- refunded: "Refunded",
197
172
  };
198
173
 
199
174
  return labelMap[status] || status;
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  import { cn } from "@umituz/web-design-system/utils";
2
3
 
3
4
  interface BrandLogoProps {
@@ -1,6 +1,5 @@
1
1
  import { useState, useEffect } from "react";
2
2
  import { useLocation, Outlet, Navigate } from "react-router-dom";
3
- import { useTranslation } from "react-i18next";
4
3
  import { Skeleton } from "@umituz/web-design-system/atoms";
5
4
  import { DashboardSidebar } from "./DashboardSidebar";
6
5
  import { DashboardHeader } from "./DashboardHeader";
@@ -27,8 +26,6 @@ interface DashboardLayoutProps {
27
26
  onDismissNotification?: (id: string) => void;
28
27
  /** Login route for redirect */
29
28
  loginRoute?: string;
30
- /** Children for routing */
31
- children?: React.ReactNode;
32
29
  }
33
30
 
34
31
  /**
@@ -56,10 +53,8 @@ export const DashboardLayout = ({
56
53
  onMarkAllRead,
57
54
  onDismissNotification,
58
55
  loginRoute = "/login",
59
- children,
60
56
  }: DashboardLayoutProps) => {
61
57
  const location = useLocation();
62
- const { t } = useTranslation();
63
58
  const [collapsed, setCollapsed] = useState(false);
64
59
  const [mobileOpen, setMobileOpen] = useState(false);
65
60
  const [loading, setLoading] = useState(true);
@@ -82,15 +77,8 @@ export const DashboardLayout = ({
82
77
  .find((i) => i.path === location.pathname);
83
78
 
84
79
  const getTitle = () => {
85
- // Check extra title map first (for dynamic routes)
86
- if (config.extraTitleMap?.[location.pathname]) {
87
- return config.extraTitleMap[location.pathname];
88
- }
89
- // Use translated label from active item
90
- if (activeItem) {
91
- return t(activeItem.label);
92
- }
93
- return "Dashboard";
80
+ if (!activeItem) return config.extraTitleMap?.[location.pathname] || "Dashboard";
81
+ return activeItem.label; // Note: In real app, this would be translated
94
82
  };
95
83
 
96
84
  const currentTitle = getTitle();
@@ -156,7 +144,7 @@ export const DashboardLayout = ({
156
144
  <Skeleton className="h-64 rounded-[32px]" />
157
145
  </div>
158
146
  ) : (
159
- children || <Outlet />
147
+ <Outlet />
160
148
  )}
161
149
  </main>
162
150
  </div>
@@ -54,12 +54,12 @@ export const DashboardSidebar = ({
54
54
  return (
55
55
  <div className="flex h-full flex-col">
56
56
  {/* Brand Section */}
57
- <div className="flex h-16 items-center gap-3 border-b border-sidebar-border/60 px-4 transition-all duration-300">
57
+ <div className="flex h-16 items-center gap-3 border-b border-sidebar-border px-4 transition-all duration-300">
58
58
  <BrandLogo size={32} />
59
59
  {!collapsed && (
60
- <div className="flex flex-col">
61
- <span className="text-xl font-bold text-sidebar-foreground tracking-tight leading-none">{brandName}</span>
62
- <span className="text-[10px] font-medium text-primary/80 lowercase tracking-wide mt-0.5 select-none">
60
+ <div className="flex flex-col -gap-1">
61
+ <span className="text-2xl font-black text-sidebar-foreground tracking-tighter leading-none">{brandName}</span>
62
+ <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">
63
63
  {brandTagline}
64
64
  </span>
65
65
  </div>
@@ -67,24 +67,24 @@ export const DashboardSidebar = ({
67
67
  </div>
68
68
 
69
69
  {/* Create Button */}
70
- <div className="px-3 py-4 border-b border-sidebar-border/60">
70
+ <div className="px-3 py-4 border-b border-sidebar-border/50">
71
71
  <Link to={createPostRoute}>
72
72
  <Button
73
73
  variant="default"
74
- className={`w-full gap-3 shadow-glow transition-all active:scale-95 group overflow-hidden rounded-lg ${
75
- collapsed ? "px-0 justify-center h-10 w-10 mx-auto" : "justify-start px-4 h-10"
74
+ className={`w-full gap-3 shadow-glow transition-all active:scale-95 group overflow-hidden rounded-xl ${
75
+ collapsed ? "px-0 justify-center h-10 w-10 mx-auto" : "justify-start px-4 h-11"
76
76
  }`}
77
77
  title={collapsed ? t('sidebar.createPost') : undefined}
78
78
  >
79
79
  <PenTool className={`shrink-0 transition-transform duration-300 ${collapsed ? "h-5 w-5" : "h-4 w-4 group-hover:scale-110"}`} />
80
- {!collapsed && <span className="font-semibold tracking-tight">{t('sidebar.createPost')}</span>}
80
+ {!collapsed && <span className="font-bold tracking-tight">{t('sidebar.createPost')}</span>}
81
81
  </Button>
82
82
  </Link>
83
83
  </div>
84
84
 
85
85
  {/* Navigation */}
86
- <nav className="flex-1 overflow-y-auto px-2 py-4 scrollbar-hide">
87
- <div className="space-y-5">
86
+ <nav className="flex-1 overflow-y-auto px-2 py-3 scrollbar-hide">
87
+ <div className="space-y-6">
88
88
  {sidebarGroups.map((group) => {
89
89
  const filteredItems = filterSidebarItems(group.items, user);
90
90
 
@@ -97,16 +97,16 @@ export const DashboardSidebar = ({
97
97
  {!collapsed && (
98
98
  <button
99
99
  onClick={() => toggleGroup(group.title)}
100
- className="w-full flex items-center justify-between px-3 py-1.5 mb-1.5 rounded-md hover:bg-sidebar-accent/15 transition-all duration-200 group/header"
100
+ className="w-full flex items-center justify-between px-3 py-2 mb-1 rounded-lg hover:bg-sidebar-accent/30 transition-all duration-200 group/header"
101
101
  >
102
- <span className="text-[10px] font-semibold uppercase tracking-wider text-sidebar-foreground/50 group-hover/header:text-sidebar-foreground transition-colors">
102
+ <span className="text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-hover/header:text-sidebar-foreground/70 transition-colors">
103
103
  {group.title === "sidebar.ai" ? `${brandName} AI` : t(group.title)}
104
104
  </span>
105
105
  <div className="flex-shrink-0">
106
106
  {isGroupCollapsed ? (
107
- <ChevronRight className="h-3.5 w-3.5 text-sidebar-foreground/40 transition-transform duration-200 group-hover/header:text-sidebar-foreground/60" />
107
+ <ChevronRight className="h-3.5 w-3.5 text-sidebar-foreground/30 transition-transform duration-200 group-hover/header:text-sidebar-foreground/50" />
108
108
  ) : (
109
- <ChevronDown className="h-3.5 w-3.5 text-sidebar-foreground/40 transition-transform duration-200 group-hover/header:text-sidebar-foreground/60" />
109
+ <ChevronDown className="h-3.5 w-3.5 text-sidebar-foreground/30 transition-transform duration-200 group-hover/header:text-sidebar-foreground/50" />
110
110
  )}
111
111
  </div>
112
112
  </button>
@@ -118,10 +118,10 @@ export const DashboardSidebar = ({
118
118
  <Link
119
119
  key={item.path}
120
120
  to={item.path}
121
- className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-all duration-200 ${
121
+ className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200 ${
122
122
  active
123
123
  ? "bg-sidebar-accent text-sidebar-accent-foreground shadow-sm"
124
- : "text-sidebar-foreground/75 hover:bg-sidebar-accent/12 hover:text-sidebar-foreground"
124
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent/40 hover:text-sidebar-foreground"
125
125
  } ${collapsed ? "justify-center" : ""}`}
126
126
  title={collapsed ? t(item.label) : undefined}
127
127
  >
@@ -137,14 +137,14 @@ export const DashboardSidebar = ({
137
137
  </nav>
138
138
 
139
139
  {/* Collapse Toggle */}
140
- <div className="border-t border-sidebar-border/60 p-3">
140
+ <div className="border-t border-sidebar-border p-3">
141
141
  <div className={`flex items-center ${collapsed ? "justify-center" : "justify-between"}`}>
142
142
  {!collapsed && (
143
- <p className="text-[10px] uppercase tracking-wider text-sidebar-foreground/50 font-semibold px-2">
143
+ <p className="text-[10px] uppercase tracking-wider text-sidebar-foreground/40 font-bold px-2">
144
144
  {t('sidebar.system')}
145
145
  </p>
146
146
  )}
147
- <Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)} className="text-sidebar-foreground/60 hover:text-sidebar-foreground hover:bg-sidebar-accent/15">
147
+ <Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)} className="text-sidebar-foreground/70">
148
148
  {collapsed ? <Menu className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
149
149
  </Button>
150
150
  </div>