Add gorgias component and services
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { googleAnalyticsService } from "../../services/googleAnalyticsService";
|
||||
|
||||
export const UserBehaviorDashboard = () => {
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [timeRange, setTimeRange] = useState("30");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await googleAnalyticsService.getUserBehavior(timeRange);
|
||||
if (result) {
|
||||
setData(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch behavior data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [timeRange]);
|
||||
|
||||
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 COLORS = {
|
||||
desktop: "#8b5cf6", // Purple
|
||||
mobile: "#10b981", // Green
|
||||
tablet: "#f59e0b", // Yellow
|
||||
};
|
||||
|
||||
const deviceData = data?.data?.pageData?.deviceData || [];
|
||||
const totalViews = deviceData.reduce((sum, item) => sum + item.pageViews, 0);
|
||||
const totalSessions = deviceData.reduce(
|
||||
(sum, item) => sum + item.sessions,
|
||||
0
|
||||
);
|
||||
|
||||
const CustomTooltip = ({ active, payload }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
const percentage = ((data.pageViews / totalViews) * 100).toFixed(1);
|
||||
const sessionPercentage = ((data.sessions / totalSessions) * 100).toFixed(
|
||||
1
|
||||
);
|
||||
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 text-gray-900 dark:text-gray-100">
|
||||
{data.device}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{data.pageViews.toLocaleString()} views ({percentage}%)
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{data.sessions.toLocaleString()} sessions ({sessionPercentage}%)
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatDuration = (seconds) => {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
};
|
||||
|
||||
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">
|
||||
User Behavior Analysis
|
||||
</CardTitle>
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger className="w-36 bg-white dark:bg-gray-800">
|
||||
<SelectValue>
|
||||
{timeRange === "7" && "Last 7 days"}
|
||||
{timeRange === "14" && "Last 14 days"}
|
||||
{timeRange === "30" && "Last 30 days"}
|
||||
{timeRange === "90" && "Last 90 days"}
|
||||
</SelectValue>
|
||||
</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>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="pages" className="w-full">
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="pages">Top Pages</TabsTrigger>
|
||||
<TabsTrigger value="sources">Traffic Sources</TabsTrigger>
|
||||
<TabsTrigger value="devices">Device Usage</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent
|
||||
value="pages"
|
||||
className="mt-4 space-y-2 h-full max-h-[440px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="dark:border-gray-800">
|
||||
<TableHead className="text-gray-900 dark:text-gray-100">
|
||||
Page Path
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Views
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Bounce Rate
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Avg. Duration
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.data?.pageData?.pageData.map((page, index) => (
|
||||
<TableRow key={index} className="dark:border-gray-800">
|
||||
<TableCell className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{page.path}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{page.pageViews.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{page.bounceRate.toFixed(1)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{formatDuration(page.avgSessionDuration)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="sources"
|
||||
className="mt-4 space-y-2 h-full max-h-[440px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="dark:border-gray-800">
|
||||
<TableHead className="text-gray-900 dark:text-gray-100">
|
||||
Source
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Sessions
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Conversions
|
||||
</TableHead>
|
||||
<TableHead className="text-right text-gray-900 dark:text-gray-100">
|
||||
Conv. Rate
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.data?.sourceData?.map((source, index) => (
|
||||
<TableRow key={index} className="dark:border-gray-800">
|
||||
<TableCell className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{source.source}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{source.sessions.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{source.conversions.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-gray-600 dark:text-gray-300">
|
||||
{((source.conversions / source.sessions) * 100).toFixed(
|
||||
1
|
||||
)}
|
||||
%
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="devices"
|
||||
className="mt-4 space-y-2 h-full max-h-[440px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2"
|
||||
>
|
||||
<div className="h-60">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={deviceData}
|
||||
dataKey="pageViews"
|
||||
nameKey="device"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
labelLine={false}
|
||||
label={({ name, percent }) =>
|
||||
`${name} ${(percent * 100).toFixed(1)}%`
|
||||
}
|
||||
>
|
||||
{deviceData.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={COLORS[entry.device.toLowerCase()]}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend
|
||||
formatter={(value) => (
|
||||
<span className="text-gray-900 dark:text-gray-100">
|
||||
{value}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{deviceData.map((device) => (
|
||||
<div
|
||||
key={device.device}
|
||||
className="p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
|
||||
>
|
||||
<div className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{device.device}
|
||||
</div>
|
||||
<div className="mt-0">
|
||||
<div className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{device.pageViews.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{((device.pageViews / totalViews) * 100).toFixed(1)}% of
|
||||
views
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
{device.sessions.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{((device.sessions / totalSessions) * 100).toFixed(1)}% of
|
||||
sessions
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user