Files
dashboard/examples DO NOT USE OR EDIT/EXAMPLE ONLY AnalyticsDashboard.jsx

365 lines
12 KiB
JavaScript

//src/components/dashboard/AnalyticsDashboard.jsx
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";
import { Loader2 } from "lucide-react";
import { googleAnalyticsService } from "../../services/googleAnalyticsService";
export const AnalyticsDashboard = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [timeRange, setTimeRange] = useState("30");
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const result = await googleAnalyticsService.getBasicMetrics({
startDate: `${timeRange}daysAgo`,
});
if (result) {
const processedData = result.map((item) => ({
...item,
date: formatGADate(item.date),
}));
const sortedData = processedData.sort((a, b) => a.date - b.date);
setData(sortedData);
} else {
console.log("No result data received");
}
} catch (error) {
console.error("Failed to fetch analytics:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, [timeRange]);
const formatGADate = (gaDate) => {
const year = gaDate.substring(0, 4);
const month = gaDate.substring(4, 6);
const day = gaDate.substring(6, 8);
return new Date(year, month - 1, day);
};
const [selectedMetrics, setSelectedMetrics] = useState({
activeUsers: true,
newUsers: true,
pageViews: true,
conversions: true,
});
const MetricToggle = ({ label, checked, onChange }) => (
<div className="flex items-center space-x-2">
<Checkbox
id={label}
checked={checked}
onCheckedChange={onChange}
className="dark:border-gray-600"
/>
<label
htmlFor={label}
className="text-sm font-medium leading-none text-gray-900 dark:text-gray-200 peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>
</div>
);
const CustomLegend = ({ metrics, selectedMetrics }) => {
// Separate items for left and right axes
const leftAxisItems = Object.entries(metrics).filter(
([key, metric]) => metric.yAxis === "left" && selectedMetrics[key]
);
const rightAxisItems = Object.entries(metrics).filter(
([key, metric]) => metric.yAxis === "right" && selectedMetrics[key]
);
return (
<div className="flex justify-between mt-4">
<div className="flex flex-col space-y-2">
<h4 className="font-semibold text-gray-700 dark:text-gray-300">
Left Axis
</h4>
{leftAxisItems.map(([key, metric]) => (
<div key={key} className="flex items-center space-x-2">
<div
style={{ backgroundColor: metric.color }}
className="w-3 h-3 rounded-full"
></div>
<span className="text-gray-900 dark:text-gray-100">
{metric.label}
</span>
</div>
))}
</div>
<div className="flex flex-col space-y-2">
<h4 className="font-semibold text-gray-700 dark:text-gray-300">
Right Axis
</h4>
{rightAxisItems.map(([key, metric]) => (
<div key={key} className="flex items-center space-x-2">
<div
style={{ backgroundColor: metric.color }}
className="w-3 h-3 rounded-full"
></div>
<span className="text-gray-900 dark:text-gray-100">
{metric.label}
</span>
</div>
))}
</div>
</div>
);
};
const metrics = {
activeUsers: { label: "Active Users", color: "#8b5cf6" },
newUsers: { label: "New Users", color: "#10b981" },
pageViews: { label: "Page Views", color: "#f59e0b" },
conversions: { label: "Conversions", color: "#3b82f6" },
};
const calculateSummary = () => {
if (!data.length) return null;
const totals = data.reduce(
(acc, day) => ({
activeUsers: acc.activeUsers + (Number(day.activeUsers) || 0),
newUsers: acc.newUsers + (Number(day.newUsers) || 0),
pageViews: acc.pageViews + (Number(day.pageViews) || 0),
conversions: acc.conversions + (Number(day.conversions) || 0),
avgSessionDuration:
acc.avgSessionDuration + (Number(day.avgSessionDuration) || 0),
bounceRate: acc.bounceRate + (Number(day.bounceRate) || 0),
}),
{
activeUsers: 0,
newUsers: 0,
pageViews: 0,
conversions: 0,
avgSessionDuration: 0,
bounceRate: 0,
}
);
return {
...totals,
avgSessionDuration: totals.avgSessionDuration / data.length,
bounceRate: totals.bounceRate / data.length,
};
};
const summary = calculateSummary();
if (loading) {
return (
<Card className="bg-white dark:bg-gray-900">
<CardContent className="h-96 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-gray-400 dark:text-gray-500" />
</CardContent>
</Card>
);
}
const formatXAxisDate = (date) => {
if (!(date instanceof Date)) return "";
return `${date.getMonth() + 1}/${date.getDate()}`;
};
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div className="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-lg shadow-lg p-3">
<p className="text-sm font-medium mb-2 text-gray-900 dark:text-gray-100">
{label instanceof Date ? label.toLocaleDateString() : label}
</p>
{payload.map((entry, index) => (
<p key={index} className="text-sm" style={{ color: entry.color }}>
{`${entry.name}: ${Number(entry.value).toLocaleString()}`}
</p>
))}
</div>
);
}
return null;
};
return (
<Card className="bg-white dark:bg-gray-900">
<CardHeader>
<div className="flex justify-between items-start">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
Analytics Overview
</CardTitle>
<Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-36 bg-white dark:bg-gray-800">
<SelectValue placeholder="Select range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7">Last 7 days</SelectItem>
<SelectItem value="14">Last 14 days</SelectItem>
<SelectItem value="30">Last 30 days</SelectItem>
<SelectItem value="90">Last 90 days</SelectItem>
</SelectContent>
</Select>
</div>
{summary && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800">
<div className="text-sm text-gray-500 dark:text-gray-400">
Total Users
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{summary.activeUsers.toLocaleString()}
</div>
</div>
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800">
<div className="text-sm text-gray-500 dark:text-gray-400">
New Users
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{summary.newUsers.toLocaleString()}
</div>
</div>
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800">
<div className="text-sm text-gray-500 dark:text-gray-400">
Page Views
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{summary.pageViews.toLocaleString()}
</div>
</div>
<div className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800">
<div className="text-sm text-gray-500 dark:text-gray-400">
Conversions
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{summary.conversions.toLocaleString()}
</div>
</div>
</div>
)}
<div className="flex flex-wrap gap-4 mt-4">
<MetricToggle
label="Active Users"
checked={selectedMetrics.activeUsers}
onChange={(checked) =>
setSelectedMetrics((prev) => ({ ...prev, activeUsers: checked }))
}
/>
<MetricToggle
label="New Users"
checked={selectedMetrics.newUsers}
onChange={(checked) =>
setSelectedMetrics((prev) => ({ ...prev, newUsers: checked }))
}
/>
<MetricToggle
label="Page Views"
checked={selectedMetrics.pageViews}
onChange={(checked) =>
setSelectedMetrics((prev) => ({ ...prev, pageViews: checked }))
}
/>
<MetricToggle
label="Conversions"
checked={selectedMetrics.conversions}
onChange={(checked) =>
setSelectedMetrics((prev) => ({ ...prev, conversions: checked }))
}
/>
</div>
</CardHeader>
<CardContent>
<div className="h-96">
<ResponsiveContainer width="100%" height="100%">
<LineChart
data={data}
margin={{ top: 5, right: 5, left: -10, bottom: 5 }}
animationBegin={0}
animationDuration={1000}
>
<CartesianGrid
strokeDasharray="3 3"
stroke="#e5e7eb"
className="dark:stroke-gray-800"
/>
<XAxis
dataKey="date"
tickFormatter={formatXAxisDate}
type="category"
tick={{ fill: "#6b7280" }}
stroke="#9ca3af"
className="dark:text-gray-400"
/>
<YAxis
yAxisId="left"
orientation="left"
tick={{ fill: "#6b7280" }}
stroke="#9ca3af"
className="dark:text-gray-400"
/>
<YAxis
yAxisId="right"
orientation="right"
tick={{ fill: "#6b7280" }}
stroke="#9ca3af"
className="dark:text-gray-400"
domain={[0, "auto"]}
/>
<Tooltip content={<CustomTooltip />} />
<Legend
formatter={(value) => (
<span className="text-gray-900 dark:text-gray-100">
{value}
</span>
)}
/>
{/* Always render Lines and control visibility */}
{Object.entries(metrics).map(([key, { color, label }]) => (
<Line
key={key}
yAxisId={key === "pageViews" ? "right" : "left"}
type="monotone"
dataKey={key}
stroke={color}
name={label}
strokeWidth={2}
dot={{ strokeWidth: 2, r: 2, fill: color }}
activeDot={{ r: 6, fill: color }}
hide={!selectedMetrics[key]} // Control visibility
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
);
};
export default AnalyticsDashboard;