@umituz/web-dashboard 2.1.0 → 2.2.0

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": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Dashboard Layout System - Customizable, themeable dashboard layouts and settings",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -28,6 +28,11 @@
28
28
  "./auth/hooks": "./src/domains/auth/hooks/index.ts",
29
29
  "./auth/utils": "./src/domains/auth/utils/index.ts",
30
30
  "./auth/types": "./src/domains/auth/types/index.ts",
31
+ "./analytics": "./src/domains/analytics/index.ts",
32
+ "./analytics/components": "./src/domains/analytics/components/index.ts",
33
+ "./analytics/hooks": "./src/domains/analytics/hooks/index.ts",
34
+ "./analytics/utils": "./src/domains/analytics/utils/index.ts",
35
+ "./analytics/types": "./src/domains/analytics/types/index.ts",
31
36
  "./package.json": "./package.json"
32
37
  },
33
38
  "files": [
@@ -50,6 +55,7 @@
50
55
  "class-variance-authority": "^0.7.1",
51
56
  "clsx": "^2.1.1",
52
57
  "lucide-react": "^0.577.0",
58
+ "recharts": "^2.15.0",
53
59
  "tailwind-merge": "^3.5.0"
54
60
  },
55
61
  "devDependencies": {
@@ -73,6 +79,12 @@
73
79
  "authentication",
74
80
  "login",
75
81
  "register",
82
+ "analytics",
83
+ "charts",
84
+ "metrics",
85
+ "kpi",
86
+ "visualization",
87
+ "recharts",
76
88
  "react",
77
89
  "typescript",
78
90
  "components",
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Analytics Card Component
3
+ *
4
+ * Configurable analytics card with chart or metrics
5
+ */
6
+
7
+ import { Loader2, AlertCircle } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import type { AnalyticsCardProps } from "../types/analytics";
10
+ import { MetricCard } from "./MetricCard";
11
+ import { AnalyticsChart } from "./AnalyticsChart";
12
+
13
+ export const AnalyticsCard = ({
14
+ title,
15
+ description,
16
+ chart,
17
+ metrics,
18
+ size = "md",
19
+ loading = false,
20
+ error,
21
+ className,
22
+ children,
23
+ }: AnalyticsCardProps) => {
24
+ const sizeClasses = {
25
+ sm: "p-4",
26
+ md: "p-6",
27
+ lg: "p-8",
28
+ full: "p-6",
29
+ };
30
+
31
+ if (loading) {
32
+ return (
33
+ <div className={cn("bg-background border border-border rounded-xl", sizeClasses[size], className)}>
34
+ <div className="flex items-center justify-center h-48">
35
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ if (error) {
42
+ return (
43
+ <div className={cn("bg-background border border-border rounded-xl", sizeClasses[size], className)}>
44
+ <div className="flex items-center justify-center h-48 gap-3 text-destructive">
45
+ <AlertCircle className="h-5 w-5" />
46
+ <p className="text-sm">{error}</p>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className={cn("bg-background border border-border rounded-xl", sizeClasses[size], className)}>
54
+ {/* Header */}
55
+ {(title || description) && (
56
+ <div className="mb-6">
57
+ {title && <h3 className="text-lg font-semibold text-foreground">{title}</h3>}
58
+ {description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
59
+ </div>
60
+ )}
61
+
62
+ {/* Chart */}
63
+ {chart && <AnalyticsChart config={chart} />}
64
+
65
+ {/* Metrics */}
66
+ {metrics && metrics.length > 0 && (
67
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
68
+ {metrics.map((metric) => (
69
+ <MetricCard key={metric.id} metric={metric} size="sm" />
70
+ ))}
71
+ </div>
72
+ )}
73
+
74
+ {/* Custom Content */}
75
+ {children}
76
+ </div>
77
+ );
78
+ };
79
+
80
+ export default AnalyticsCard;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Analytics Chart Component
3
+ *
4
+ * Configurable chart component using Recharts
5
+ */
6
+
7
+ import { useMemo } from "react";
8
+ import {
9
+ LineChart,
10
+ BarChart,
11
+ AreaChart,
12
+ PieChart,
13
+ ResponsiveContainer,
14
+ XAxis,
15
+ YAxis,
16
+ CartesianGrid,
17
+ Tooltip,
18
+ Legend,
19
+ Line,
20
+ Bar,
21
+ Area,
22
+ Pie,
23
+ Cell,
24
+ } from "recharts";
25
+ import { cn } from "@umituz/web-design-system/utils";
26
+ import type { ChartConfig } from "../types/analytics";
27
+ import { generateChartColors } from "../utils/analytics";
28
+
29
+ interface AnalyticsChartProps {
30
+ /** Chart configuration */
31
+ config: ChartConfig;
32
+ /** Custom class name */
33
+ className?: string;
34
+ /** Custom height */
35
+ height?: number | string;
36
+ }
37
+
38
+ export const AnalyticsChart = ({ config, className, height }: AnalyticsChartProps) => {
39
+ const colors = useMemo(
40
+ () => config.colors || generateChartColors(config.yAxisKeys?.length || 5),
41
+ [config.colors, config.yAxisKeys]
42
+ );
43
+
44
+ const renderChart = () => {
45
+ const commonProps = {
46
+ data: config.data,
47
+ margin: { top: 10, right: 10, left: 10, bottom: 10 },
48
+ };
49
+
50
+ switch (config.type) {
51
+ case "line":
52
+ return (
53
+ <LineChart {...commonProps}>
54
+ {config.showGrid && <CartesianGrid strokeDasharray="3 3" className="stroke-border/50" />}
55
+ <XAxis
56
+ dataKey={config.xAxisKey}
57
+ className="text-xs text-muted-foreground"
58
+ axisLine={false}
59
+ tickLine={false}
60
+ />
61
+ <YAxis
62
+ className="text-xs text-muted-foreground"
63
+ axisLine={false}
64
+ tickLine={false}
65
+ />
66
+ {config.showTooltip && <Tooltip />}
67
+ {config.showLegend && <Legend />}
68
+ {config.yAxisKeys?.map((key, index) => (
69
+ <Line
70
+ key={key}
71
+ type="monotone"
72
+ dataKey={key}
73
+ stroke={colors[index]}
74
+ strokeWidth={2}
75
+ dot={{ r: 4 }}
76
+ />
77
+ ))}
78
+ </LineChart>
79
+ );
80
+
81
+ case "bar":
82
+ return (
83
+ <BarChart {...commonProps}>
84
+ {config.showGrid && <CartesianGrid strokeDasharray="3 3" className="stroke-border/50" />}
85
+ <XAxis
86
+ dataKey={config.xAxisKey}
87
+ className="text-xs text-muted-foreground"
88
+ axisLine={false}
89
+ tickLine={false}
90
+ />
91
+ <YAxis
92
+ className="text-xs text-muted-foreground"
93
+ axisLine={false}
94
+ tickLine={false}
95
+ />
96
+ {config.showTooltip && <Tooltip />}
97
+ {config.showLegend && <Legend />}
98
+ {config.yAxisKeys?.map((key, index) => (
99
+ <Bar key={key} dataKey={key} fill={colors[index]} radius={[4, 4, 0, 0]} />
100
+ ))}
101
+ </BarChart>
102
+ );
103
+
104
+ case "area":
105
+ return (
106
+ <AreaChart {...commonProps}>
107
+ {config.showGrid && <CartesianGrid strokeDasharray="3 3" className="stroke-border/50" />}
108
+ <XAxis
109
+ dataKey={config.xAxisKey}
110
+ className="text-xs text-muted-foreground"
111
+ axisLine={false}
112
+ tickLine={false}
113
+ />
114
+ <YAxis
115
+ className="text-xs text-muted-foreground"
116
+ axisLine={false}
117
+ tickLine={false}
118
+ />
119
+ {config.showTooltip && <Tooltip />}
120
+ {config.showLegend && <Legend />}
121
+ {config.yAxisKeys?.map((key, index) => (
122
+ <Area
123
+ key={key}
124
+ type="monotone"
125
+ dataKey={key}
126
+ stroke={colors[index]}
127
+ fill={colors[index]}
128
+ fillOpacity={0.3}
129
+ />
130
+ ))}
131
+ </AreaChart>
132
+ );
133
+
134
+ case "pie":
135
+ case "donut":
136
+ return (
137
+ <PieChart {...commonProps}>
138
+ <Pie
139
+ data={config.data}
140
+ cx="50%"
141
+ cy="50%"
142
+ labelLine={false}
143
+ label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
144
+ outerRadius={80}
145
+ innerRadius={config.type === "donut" ? 40 : 0}
146
+ dataKey="value"
147
+ >
148
+ {config.data.map((entry: any, index: number) => (
149
+ <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />
150
+ ))}
151
+ </Pie>
152
+ {config.showTooltip && <Tooltip />}
153
+ {config.showLegend && <Legend />}
154
+ </PieChart>
155
+ );
156
+
157
+ default:
158
+ return (
159
+ <div className="flex items-center justify-center h-full text-muted-foreground">
160
+ Unsupported chart type: {config.type}
161
+ </div>
162
+ );
163
+ }
164
+ };
165
+
166
+ const chartHeight = height || config.height || 300;
167
+
168
+ return (
169
+ <div
170
+ className={cn(
171
+ "w-full",
172
+ config.aspectRatio || "aspect-video",
173
+ className
174
+ )}
175
+ style={{ height: typeof chartHeight === "number" ? `${chartHeight}px` : chartHeight }}
176
+ >
177
+ <ResponsiveContainer width="100%" height="100%">
178
+ {renderChart()}
179
+ </ResponsiveContainer>
180
+ </div>
181
+ );
182
+ };
183
+
184
+ export default AnalyticsChart;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Analytics Layout Component
3
+ *
4
+ * Configurable analytics page layout with KPIs and charts
5
+ */
6
+
7
+ import { RefreshCw, Download, Calendar } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import { Button } from "@umituz/web-design-system/atoms";
10
+ import type { AnalyticsLayoutProps } from "../types/analytics";
11
+ import { AnalyticsCard } from "./AnalyticsCard";
12
+ import { MetricCard } from "./MetricCard";
13
+ import { getDateRangePresets } from "../utils/analytics";
14
+
15
+ export const AnalyticsLayout = ({
16
+ title,
17
+ description,
18
+ showDateRange = true,
19
+ showRefresh = true,
20
+ showExport = true,
21
+ kpis,
22
+ charts,
23
+ headerContent,
24
+ children,
25
+ }: AnalyticsLayoutProps) => {
26
+ const dateRangePresets = getDateRangePresets();
27
+
28
+ const handleExport = async () => {
29
+ // In production, implement export functionality
30
+ console.log("Exporting analytics data...");
31
+ };
32
+
33
+ const handleRefresh = async () => {
34
+ // In production, refresh data
35
+ console.log("Refreshing analytics data...");
36
+ };
37
+
38
+ return (
39
+ <div className="w-full space-y-6">
40
+ {/* Header */}
41
+ <div className="flex items-center justify-between">
42
+ <div>
43
+ {title && <h1 className="text-3xl font-bold text-foreground">{title}</h1>}
44
+ {description && <p className="text-muted-foreground mt-1">{description}</p>}
45
+ </div>
46
+
47
+ {/* Actions */}
48
+ <div className="flex items-center gap-3">
49
+ {showDateRange && (
50
+ <div className="flex items-center gap-2 bg-background border border-border rounded-lg px-3 py-2">
51
+ <Calendar className="h-4 w-4 text-muted-foreground" />
52
+ <select className="bg-transparent text-sm text-foreground outline-none">
53
+ {dateRangePresets.map((preset) => (
54
+ <option key={preset.value} value={preset.value}>
55
+ {preset.label}
56
+ </option>
57
+ ))}
58
+ </select>
59
+ </div>
60
+ )}
61
+
62
+ {showRefresh && (
63
+ <Button variant="ghost" size="sm" onClick={handleRefresh}>
64
+ <RefreshCw className="h-4 w-4" />
65
+ </Button>
66
+ )}
67
+
68
+ {showExport && (
69
+ <Button variant="ghost" size="sm" onClick={handleExport}>
70
+ <Download className="h-4 w-4" />
71
+ Export
72
+ </Button>
73
+ )}
74
+
75
+ {headerContent}
76
+ </div>
77
+ </div>
78
+
79
+ {/* KPI Cards */}
80
+ {kpis && (
81
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
82
+ <MetricCard
83
+ metric={{
84
+ id: "downloads",
85
+ name: "Downloads",
86
+ value: kpis.downloads.current,
87
+ previousValue: kpis.downloads.previous,
88
+ unit: "K",
89
+ }}
90
+ />
91
+ <MetricCard
92
+ metric={{
93
+ id: "engagement",
94
+ name: "Engagement",
95
+ value: kpis.engagement.current,
96
+ previousValue: kpis.engagement.previous,
97
+ unit: "%",
98
+ }}
99
+ />
100
+ <MetricCard
101
+ metric={{
102
+ id: "users",
103
+ name: "Users",
104
+ value: kpis.users.current,
105
+ previousValue: kpis.users.previous,
106
+ unit: "K",
107
+ }}
108
+ />
109
+ <MetricCard
110
+ metric={{
111
+ id: "revenue",
112
+ name: "Revenue",
113
+ value: kpis.revenue.current,
114
+ previousValue: kpis.revenue.previous,
115
+ unit: "$",
116
+ }}
117
+ />
118
+ <MetricCard
119
+ metric={{
120
+ id: "retention",
121
+ name: "Retention",
122
+ value: kpis.retention.current,
123
+ previousValue: kpis.retention.previous,
124
+ unit: "%",
125
+ }}
126
+ />
127
+ </div>
128
+ )}
129
+
130
+ {/* Charts */}
131
+ {charts && charts.length > 0 && (
132
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
133
+ {charts.map((chartConfig, index) => (
134
+ <AnalyticsCard
135
+ key={index}
136
+ title={chartConfig.title}
137
+ description={chartConfig.description}
138
+ chart={chartConfig}
139
+ />
140
+ ))}
141
+ </div>
142
+ )}
143
+
144
+ {/* Custom Content */}
145
+ {children}
146
+ </div>
147
+ );
148
+ };
149
+
150
+ export default AnalyticsLayout;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Metric Card Component
3
+ *
4
+ * Displays a single metric with trend indicator
5
+ */
6
+
7
+ import { ArrowUp, ArrowDown, Minus } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import type { MetricCardProps } from "../types/analytics";
10
+ import { formatMetricValue, calculateGrowth } from "../utils/analytics";
11
+
12
+ export const MetricCard = ({
13
+ metric,
14
+ size = "md",
15
+ showTrend = true,
16
+ showIcon = true,
17
+ className,
18
+ onClick,
19
+ }: MetricCardProps) => {
20
+ const growth =
21
+ metric.previousValue !== undefined
22
+ ? calculateGrowth(metric.value, metric.previousValue)
23
+ : 0;
24
+
25
+ const sizeClasses = {
26
+ sm: "p-4",
27
+ md: "p-6",
28
+ lg: "p-8",
29
+ };
30
+
31
+ const titleSizeClasses = {
32
+ sm: "text-sm",
33
+ md: "text-base",
34
+ lg: "text-lg",
35
+ };
36
+
37
+ const valueSizeClasses = {
38
+ sm: "text-xl",
39
+ md: "text-3xl",
40
+ lg: "text-4xl",
41
+ };
42
+
43
+ return (
44
+ <div
45
+ onClick={onClick}
46
+ className={cn(
47
+ "bg-background border border-border rounded-xl",
48
+ sizeClasses[size],
49
+ onClick && "cursor-pointer hover:border-primary/50 transition-colors",
50
+ className
51
+ )}
52
+ >
53
+ <div className="flex items-start justify-between">
54
+ <div className="flex-1">
55
+ {/* Title */}
56
+ <p
57
+ className={cn(
58
+ "text-muted-foreground font-medium mb-1",
59
+ titleSizeClasses[size]
60
+ )}
61
+ >
62
+ {metric.name}
63
+ </p>
64
+
65
+ {/* Value */}
66
+ <div className={cn("font-bold text-foreground", valueSizeClasses[size])}>
67
+ {formatMetricValue(metric)}
68
+ </div>
69
+
70
+ {/* Trend */}
71
+ {showTrend && metric.previousValue !== undefined && (
72
+ <div className="flex items-center gap-1 mt-2">
73
+ {growth > 0 ? (
74
+ <ArrowUp className="h-4 w-4 text-green-600 dark:text-green-500" />
75
+ ) : growth < 0 ? (
76
+ <ArrowDown className="h-4 w-4 text-destructive" />
77
+ ) : (
78
+ <Minus className="h-4 w-4 text-muted-foreground" />
79
+ )}
80
+ <span
81
+ className={cn(
82
+ "text-sm font-medium",
83
+ growth > 0 && "text-green-600 dark:text-green-500",
84
+ growth < 0 && "text-destructive",
85
+ growth === 0 && "text-muted-foreground"
86
+ )}
87
+ >
88
+ {Math.abs(growth).toFixed(1)}%
89
+ </span>
90
+ <span className="text-sm text-muted-foreground">
91
+ vs last period
92
+ </span>
93
+ </div>
94
+ )}
95
+ </div>
96
+
97
+ {/* Icon */}
98
+ {showIcon && metric.icon && (
99
+ <div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
100
+ <metric.icon className="h-6 w-6 text-primary" />
101
+ </div>
102
+ )}
103
+ </div>
104
+ </div>
105
+ );
106
+ };
107
+
108
+ export default MetricCard;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Analytics Components
3
+ *
4
+ * Export all analytics components
5
+ */
6
+
7
+ export { MetricCard } from "./MetricCard";
8
+ export { AnalyticsChart } from "./AnalyticsChart";
9
+ export { AnalyticsCard } from "./AnalyticsCard";
10
+ export { AnalyticsLayout } from "./AnalyticsLayout";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Analytics Hooks
3
+ *
4
+ * Export all analytics hooks
5
+ */
6
+
7
+ export { useAnalytics } from "./useAnalytics";