dashboard
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { type FC } from 'react'
|
||||
import { useGetDashboard, useGetOrderChart } from './hooks/useDashboardData'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import Error from '../../components/Error'
|
||||
import DashboardOverview from './components/DashboardOverview'
|
||||
import ProductsCountSection from './components/ProductsCountSection'
|
||||
import OrdersCountSection from './components/OrdersCountSection'
|
||||
import UnreadCommentsSection from './components/UnreadCommentsSection'
|
||||
import UsersCountSection from './components/UsersCountSection'
|
||||
import OrdersChartSection from './components/OrdersChartSection'
|
||||
|
||||
const Dashboard: FC = () => {
|
||||
const location = useLocation()
|
||||
const { data, isLoading, error } = useGetDashboard()
|
||||
const { data: orderChartData } = useGetOrderChart()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<PageLoading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<Error errorText="خطا در بارگذاری اطلاعات داشبورد" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderDashboardContent = () => {
|
||||
const path = location.pathname
|
||||
|
||||
if (path.includes('products-count')) {
|
||||
return <ProductsCountSection data={data?.results} />
|
||||
} else if (path.includes('orders-count')) {
|
||||
return <OrdersCountSection data={data?.results} />
|
||||
} else if (path.includes('unread-comments')) {
|
||||
return <UnreadCommentsSection data={data?.results} />
|
||||
} else if (path.includes('users-count')) {
|
||||
return <UsersCountSection data={data?.results} />
|
||||
} else if (path.includes('orders-chart')) {
|
||||
return <OrdersChartSection
|
||||
dashboardData={data?.results}
|
||||
orderChartData={orderChartData?.results}
|
||||
/>
|
||||
} else {
|
||||
// نمایش داشبورد کلی
|
||||
return <DashboardOverview data={data?.results} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
{renderDashboardContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default Dashboard
|
||||
@@ -0,0 +1,87 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData } from '../types/Types'
|
||||
import { Box, ShoppingCart, Message, User } from 'iconsax-react'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
const DashboardOverview: FC<{ data?: DashboardData }> = ({ data }) => {
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* دیباگ دادههای API */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<h4 className="text-sm font-medium text-blue-800 mb-2">ℹ️ وضعیت اتصال به API</h4>
|
||||
<div className="text-xs text-blue-700 space-y-1">
|
||||
<div><strong>دادههای واقعی API:</strong> {Array.isArray(data?.FiveDaysAgoSales) ? `✅ ${data.FiveDaysAgoSales.length} آیتم دریافت شد` : '❌ دادهای وجود ندارد'}</div>
|
||||
<div><strong>نمودار:</strong> {Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 ? '✅ نمایش دادههای واقعی API' : '⏳ در انتظار دادههای API'}</div>
|
||||
<div><strong>منطق نمایش:</strong> فقط از دادههای واقعی API استفاده میشود</div>
|
||||
</div>
|
||||
{Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 && (
|
||||
<div className="mt-3 p-2 bg-white rounded text-xs">
|
||||
<div className="font-medium mb-1">نمونه داده واقعی API:</div>
|
||||
<pre className="text-gray-600 overflow-x-auto">
|
||||
{JSON.stringify(data.FiveDaysAgoSales[0], null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-3 p-2 bg-green-50 rounded text-xs">
|
||||
<div className="font-medium mb-1 text-green-800">🎯 نحوه عملکرد نمودار:</div>
|
||||
<div className="text-green-700">
|
||||
۱. فقط از دادههای واقعی API استفاده میشود<br />
|
||||
۲. اگر داده وجود نداشته باشد، نمودار خالی نمایش داده میشود<br />
|
||||
۳. هیچ داده نمونهای استفاده نمیشود<br />
|
||||
۴. ساختار دادههای مورد انتظار: totalSales, totalOrders
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TitleLine title="نمای کلی داشبورد" />
|
||||
|
||||
{/* آمار کلی */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="تعداد محصولات"
|
||||
value={data.productCount}
|
||||
icon={<Box size={24} color="#3B82F6" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="تعداد سفارشات"
|
||||
value={data.proccessingOrders}
|
||||
icon={<ShoppingCart size={24} color="#10B981" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="کامنت های خوانده نشده"
|
||||
value={data.pendingComments}
|
||||
icon={<Message size={24} color="#F59E0B" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="تعداد کاربران"
|
||||
value={data.usersCount}
|
||||
icon={<User size={24} color="#8B5CF6" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* آمار فروش روزانه */}
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm">
|
||||
<h3 className="text-lg font-medium mb-4">آمار فروش روزانه</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">{data.dailySales.totalOrders}</div>
|
||||
<div className="text-sm text-gray-600">تعداد سفارشات</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{data.dailySales.totalSales.toLocaleString()} تومان</div>
|
||||
<div className="text-sm text-gray-600">مجموع فروش</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">{data.dailySales.itemsSold}</div>
|
||||
<div className="text-sm text-gray-600">تعداد آیتم فروش رفته</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardOverview
|
||||
@@ -0,0 +1,240 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData, type OrderChartData } from '../types/Types'
|
||||
import { Chart } from 'iconsax-react'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'
|
||||
|
||||
export interface OrdersChartSectionProps {
|
||||
dashboardData?: DashboardData
|
||||
orderChartData?: OrderChartData
|
||||
}
|
||||
|
||||
const OrdersChartSection: FC<OrdersChartSectionProps> = ({
|
||||
dashboardData,
|
||||
orderChartData
|
||||
}) => {
|
||||
// پردازش دادههای Order Chart API
|
||||
const processOrderChartData = (chartData?: OrderChartData) => {
|
||||
if (chartData && chartData.xAxis && chartData.yAxis) {
|
||||
return chartData.xAxis.map((xValue, index) => ({
|
||||
name: xValue,
|
||||
value: chartData.yAxis[index] || 0,
|
||||
sales: chartData.yAxis[index] || 0,
|
||||
}))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// پردازش دادههای ۵ روز گذشته برای نمودار (fallback)
|
||||
const processDashboardChartData = (rawData: unknown) => {
|
||||
if (Array.isArray(rawData) && rawData.length > 0) {
|
||||
return (rawData as Array<Record<string, unknown>>).slice(-5).map((item, index: number) => ({
|
||||
name: `${5 - index} روز پیش`,
|
||||
sales: (item.totalSales as number) || (item.sales as number) || 0,
|
||||
orders: (item.totalOrders as number) || (item.orders as number) || 0,
|
||||
date: item.date || item.created_at || `روز ${5 - index}`,
|
||||
}))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// اولویت با دادههای Order Chart API
|
||||
const orderChartProcessed = processOrderChartData(orderChartData)
|
||||
const dashboardChartProcessed = processDashboardChartData(dashboardData?.FiveDaysAgoSales)
|
||||
|
||||
// استفاده از دادههای Order Chart اگر موجود باشد، در غیر این صورت از Dashboard استفاده کن
|
||||
const chartData = orderChartProcessed.length > 0 ? orderChartProcessed : dashboardChartProcessed
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<TitleLine title="نمودار سفارشات" />
|
||||
|
||||
{/* نمودار فروش ۵ روز گذشته */}
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Chart size={24} color="#3B82F6" />
|
||||
<h3 className="text-lg font-medium">آمار فروش ۵ روز گذشته</h3>
|
||||
</div>
|
||||
|
||||
<div className="h-80 w-full">
|
||||
{chartData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickFormatter={(value) => orderChartData ? value.toLocaleString() : `${(value / 1000000).toFixed(1)}M`}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number, name: string) => {
|
||||
if (orderChartData) {
|
||||
return [
|
||||
`${value.toLocaleString()} ${orderChartData.unit || 'تومان'}`,
|
||||
orderChartData.title || 'مقدار'
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
name === 'sales' ? `${value.toLocaleString()} تومان` : value,
|
||||
name === 'sales' ? 'فروش' : 'سفارشات'
|
||||
]
|
||||
}
|
||||
}}
|
||||
labelStyle={{ color: '#374151' }}
|
||||
contentStyle={{
|
||||
backgroundColor: '#ffffff',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="sales"
|
||||
stroke="#3B82F6"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: '#3B82F6', strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6, stroke: '#3B82F6', strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Chart size={48} color="#9CA3AF" className="mx-auto mb-4" />
|
||||
<div className="text-gray-500 text-sm">
|
||||
دادهای برای نمایش نمودار وجود ندارد
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs mt-1">
|
||||
منتظر دریافت داده از API...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* نمودار مقایسه فروش و سفارشات */}
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm">
|
||||
<h3 className="text-lg font-medium mb-6">مقایسه فروش و تعداد سفارشات</h3>
|
||||
|
||||
<div className="h-80 w-full">
|
||||
{chartData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="left"
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tickFormatter={(value) => orderChartData ? value.toLocaleString() : `${(value / 1000000).toFixed(1)}M`}
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number, name: string) => {
|
||||
if (orderChartData) {
|
||||
return [
|
||||
`${value.toLocaleString()} ${orderChartData.unit || 'تومان'}`,
|
||||
orderChartData.title || 'مقدار'
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
name === 'sales' ? `${value.toLocaleString()} تومان` : value,
|
||||
name === 'sales' ? 'فروش' : 'سفارشات'
|
||||
]
|
||||
}
|
||||
}}
|
||||
labelStyle={{ color: '#374151' }}
|
||||
contentStyle={{
|
||||
backgroundColor: '#ffffff',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
yAxisId="left"
|
||||
dataKey="sales"
|
||||
fill="#10B981"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
<Bar
|
||||
yAxisId="right"
|
||||
dataKey="orders"
|
||||
fill="#8B5CF6"
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Chart size={48} color="#9CA3AF" className="mx-auto mb-4" />
|
||||
<div className="text-gray-500 text-sm">
|
||||
دادهای برای نمایش نمودار مقایسه وجود ندارد
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs mt-1">
|
||||
منتظر دریافت داده از API...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{chartData.length > 0 && (
|
||||
<div className="flex justify-center gap-6 mt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-green-500 rounded"></div>
|
||||
<span className="text-sm text-gray-600">فروش (تومان)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-purple-500 rounded"></div>
|
||||
<span className="text-sm text-gray-600">تعداد سفارشات</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* آمار ماهانه */}
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm">
|
||||
<h3 className="text-lg font-medium mb-4">آمار فروش ماهانه</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">{dashboardData?.monthlySales?.totalOrders || 0}</div>
|
||||
<div className="text-sm text-gray-600">تعداد سفارشات</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{dashboardData?.monthlySales?.totalSales?.toLocaleString() || 0} تومان</div>
|
||||
<div className="text-sm text-gray-600">مجموع فروش</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">{dashboardData?.monthlySales?.itemsSold || 0}</div>
|
||||
<div className="text-sm text-gray-600">تعداد آیتم فروش رفته</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrdersChartSection
|
||||
@@ -0,0 +1,49 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData } from '../types/Types'
|
||||
import { ShoppingCart } from 'iconsax-react'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
const OrdersCountSection: FC<{ data?: DashboardData }> = ({ data }) => {
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<TitleLine title="آمار سفارشات" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<StatCard
|
||||
title="سفارشات در حال پردازش"
|
||||
value={data.proccessingOrders}
|
||||
icon={<ShoppingCart size={24} color="#10B981" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="درخواستهای برداشت وجه"
|
||||
value={data.withdrawalRequests}
|
||||
icon={<ShoppingCart size={24} color="#F59E0B" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* آمار فروش روزانه */}
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm">
|
||||
<h3 className="text-lg font-medium mb-4">آمار فروش روزانه</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-blue-600">{data.dailySales.totalOrders}</div>
|
||||
<div className="text-sm text-gray-600">تعداد سفارشات</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-green-600">{data.dailySales.totalSales.toLocaleString()} تومان</div>
|
||||
<div className="text-sm text-gray-600">مجموع فروش</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-purple-600">{data.dailySales.itemsSold}</div>
|
||||
<div className="text-sm text-gray-600">تعداد آیتم فروش رفته</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrdersCountSection
|
||||
@@ -0,0 +1,35 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData } from '../types/Types'
|
||||
import { Box, Shop } from 'iconsax-react'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
const ProductsCountSection: FC<{ data?: DashboardData }> = ({ data }) => {
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<TitleLine title="آمار محصولات" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<StatCard
|
||||
title="کل محصولات"
|
||||
value={data.productCount}
|
||||
icon={<Box size={24} color="#3B82F6" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="محصولات در انتظار تایید"
|
||||
value={data.pendingProductCount}
|
||||
icon={<Box size={24} color="#F59E0B" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="برندهای در انتظار تایید"
|
||||
value={data.pendingBrandsCount}
|
||||
icon={<Shop size={24} color="#8B5CF6" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductsCountSection
|
||||
@@ -0,0 +1,25 @@
|
||||
import { type FC } from 'react'
|
||||
|
||||
interface StatCardProps {
|
||||
title: string
|
||||
value: number
|
||||
icon: React.ReactNode
|
||||
}
|
||||
|
||||
const StatCard: FC<StatCardProps> = ({ title, value, icon }) => {
|
||||
return (
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm hover:shadow-md transition-shadow cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">{title}</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{value?.toLocaleString() || 0}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-gray-50 rounded-full">
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatCard
|
||||
@@ -0,0 +1,30 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData } from '../types/Types'
|
||||
import { Message } from 'iconsax-react'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
const UnreadCommentsSection: FC<{ data?: DashboardData }> = ({ data }) => {
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<TitleLine title="کامنتها و پرسشها" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<StatCard
|
||||
title="کامنت های خوانده نشده"
|
||||
value={data.pendingComments}
|
||||
icon={<Message size={24} color="#F59E0B" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="پرسش های خوانده نشده"
|
||||
value={data.pendingQuestions}
|
||||
icon={<Message size={24} color="#8B5CF6" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnreadCommentsSection
|
||||
@@ -0,0 +1,30 @@
|
||||
import { type FC } from 'react'
|
||||
import TitleLine from '../../../components/TitleLine'
|
||||
import { type DashboardData } from '../types/Types'
|
||||
import { User, Shop } from 'iconsax-react'
|
||||
import StatCard from './StatCard'
|
||||
|
||||
const UsersCountSection: FC<{ data?: DashboardData }> = ({ data }) => {
|
||||
if (!data) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<TitleLine title="آمار کاربران" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<StatCard
|
||||
title="کل کاربران"
|
||||
value={data.usersCount}
|
||||
icon={<User size={24} color="#8B5CF6" />}
|
||||
/>
|
||||
<StatCard
|
||||
title="تعداد فروشندگان"
|
||||
value={data.sellersCount}
|
||||
icon={<Shop size={24} color="#10B981" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersCountSection
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as api from "../service/Service";
|
||||
|
||||
export const useGetDashboard = () => {
|
||||
return useQuery({
|
||||
queryKey: ["dashboard"],
|
||||
queryFn: api.getDashboard,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetOrderChart = () => {
|
||||
return useQuery({
|
||||
queryKey: ["order-chart"],
|
||||
queryFn: api.getOrderChart,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import axios from "@/config/axios";
|
||||
import type { DashboardResponse, OrderChartResponse } from "../types/Types";
|
||||
|
||||
export const getDashboard = async (): Promise<DashboardResponse> => {
|
||||
const { data } = await axios.get<DashboardResponse>(`/admin/reports`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getOrderChart = async (): Promise<OrderChartResponse> => {
|
||||
const { data } = await axios.get<OrderChartResponse>(`/admin/orders/chart`);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
// تایپهای مربوط به فروش
|
||||
interface SalesStats {
|
||||
_id: string | null;
|
||||
totalSales: number;
|
||||
totalNetSales: number;
|
||||
itemsSold: number;
|
||||
totalOrders: number;
|
||||
}
|
||||
|
||||
// تایپهای مربوط به فروش حمل و نقل
|
||||
interface ShipmentSalesStats {
|
||||
_id: string | null;
|
||||
totalShipmentCost: number;
|
||||
itemsSold: number;
|
||||
totalOrders: number;
|
||||
}
|
||||
|
||||
// تایپ فروشگاه
|
||||
interface Shop {
|
||||
_id: string;
|
||||
shopName: string;
|
||||
}
|
||||
|
||||
// تایپ جزئیات محصول
|
||||
interface ProductDetails {
|
||||
_id: number;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
model: string;
|
||||
description: string;
|
||||
shop: Shop;
|
||||
}
|
||||
|
||||
// تایپ محصولات برتر
|
||||
interface TopProduct {
|
||||
totalSold: number;
|
||||
productDetails: ProductDetails;
|
||||
productId: number;
|
||||
}
|
||||
|
||||
// تایپ فروش ۵ روز گذشته (ساختار دقیق مشخص نیست، فعلاً unknown استفاده میکنیم)
|
||||
type FiveDaysAgoSales = unknown[];
|
||||
|
||||
// تایپ اصلی داشبورد
|
||||
export interface DashboardData {
|
||||
dailySales: SalesStats;
|
||||
weeklySales: SalesStats;
|
||||
monthlySales: SalesStats | null;
|
||||
yearlySales: SalesStats;
|
||||
dailyShipmentSales: ShipmentSalesStats;
|
||||
weeklyShipmentSales: ShipmentSalesStats;
|
||||
monthlyShipmentSales: ShipmentSalesStats;
|
||||
yearlyShipmentSales: ShipmentSalesStats;
|
||||
withdrawalRequests: number;
|
||||
FiveDaysAgoSales: FiveDaysAgoSales;
|
||||
topProducts: TopProduct[];
|
||||
proccessingOrders: number;
|
||||
pendingComments: number;
|
||||
pendingQuestions: number;
|
||||
sellersCount: number;
|
||||
usersCount: number;
|
||||
wholesaleRequestCount: number;
|
||||
pendingBrandsCount: number;
|
||||
pendingWarranties: number;
|
||||
productCount: number;
|
||||
pendingProductCount: number;
|
||||
}
|
||||
|
||||
// تایپ دادههای نمودار سفارش
|
||||
export interface OrderChartData {
|
||||
chartType: string;
|
||||
title: string;
|
||||
unit: string;
|
||||
granularity: string;
|
||||
xAxis: string[];
|
||||
yAxis: number[];
|
||||
totalSales: number;
|
||||
_debug: {
|
||||
aggregatedPeriods: number;
|
||||
};
|
||||
}
|
||||
|
||||
// تایپ پاسخ API نمودار سفارش
|
||||
export interface OrderChartResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: OrderChartData;
|
||||
}
|
||||
|
||||
// تایپ پاسخ API
|
||||
export interface DashboardResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: DashboardData;
|
||||
}
|
||||
@@ -61,6 +61,7 @@ import SiteSetting from '@/pages/setting/SiteSetting'
|
||||
import JobsList from '@/pages/jobs/List'
|
||||
import Resumes from '@/pages/jobs/Resume'
|
||||
import ContactUsList from '@/pages/contactUs/List'
|
||||
import Dashboard from '@/pages/dashboard/Dashboard'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
@@ -149,6 +150,13 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.jobs.resumes} element={<Resumes />} />
|
||||
|
||||
<Route path={Pages.contactUs.list} element={<ContactUsList />} />
|
||||
|
||||
<Route path={Pages.dashboard} element={<Dashboard />} />
|
||||
<Route path="/dashboard/products-count" element={<Dashboard />} />
|
||||
<Route path="/dashboard/orders-count" element={<Dashboard />} />
|
||||
<Route path="/dashboard/unread-comments" element={<Dashboard />} />
|
||||
<Route path="/dashboard/users-count" element={<Dashboard />} />
|
||||
<Route path="/dashboard/orders-chart" element={<Dashboard />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user