Добавлены новые зависимости для работы с графиками и статистикой: интегрирован пакет recharts для визуализации данных. Обновлены компоненты бизнес-демо и сайдбара, добавлены новые функции для отображения информации о поставках и статистике. Улучшена структура кода и взаимодействие с пользователем. Обновлены GraphQL резолверы для получения статистики Wildberries.
This commit is contained in:
525
src/components/seller-statistics/sales-tab.tsx
Normal file
525
src/components/seller-statistics/sales-tab.tsx
Normal file
@ -0,0 +1,525 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { gql } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from '@/components/ui/chart'
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
|
||||
// GraphQL query для получения статистики WB
|
||||
const GET_WILDBERRIES_STATISTICS = gql`
|
||||
query GetWildberriesStatistics(
|
||||
$period: String
|
||||
$startDate: String
|
||||
$endDate: String
|
||||
) {
|
||||
getWildberriesStatistics(
|
||||
period: $period
|
||||
startDate: $startDate
|
||||
endDate: $endDate
|
||||
) {
|
||||
success
|
||||
message
|
||||
data {
|
||||
date
|
||||
sales
|
||||
orders
|
||||
advertising
|
||||
refusals
|
||||
returns
|
||||
revenue
|
||||
buyoutPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface SalesTabProps {
|
||||
selectedPeriod: string
|
||||
useCustomDates?: boolean
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
}
|
||||
|
||||
// Mock данные для графиков
|
||||
const mockChartData = [
|
||||
{ date: '01.11', sales: 45, orders: 52, advertising: 32, refusals: 8, returns: 3 },
|
||||
{ date: '02.11', sales: 35, orders: 41, advertising: 28, refusals: 6, returns: 2 },
|
||||
{ date: '03.11', sales: 52, orders: 61, advertising: 39, refusals: 9, returns: 4 },
|
||||
{ date: '04.11', sales: 38, orders: 45, advertising: 31, refusals: 7, returns: 2 },
|
||||
{ date: '05.11', sales: 58, orders: 69, advertising: 45, refusals: 11, returns: 5 },
|
||||
{ date: '06.11', sales: 47, orders: 55, advertising: 37, refusals: 8, returns: 3 },
|
||||
{ date: '07.11', sales: 56, orders: 66, advertising: 42, refusals: 10, returns: 4 },
|
||||
]
|
||||
|
||||
// Конфигурация chart
|
||||
const chartConfig = {
|
||||
sales: {
|
||||
label: "Продажи",
|
||||
color: "#10b981", // зеленый
|
||||
},
|
||||
orders: {
|
||||
label: "Заказы",
|
||||
color: "#3b82f6", // синий
|
||||
},
|
||||
advertising: {
|
||||
label: "Реклама",
|
||||
color: "#f59e0b", // оранжевый
|
||||
},
|
||||
refusals: {
|
||||
label: "Отказы",
|
||||
color: "#ef4444", // красный
|
||||
},
|
||||
returns: {
|
||||
label: "Возвраты",
|
||||
color: "#8b5cf6", // фиолетовый
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
// Mock данные для таблицы
|
||||
const mockTableData = [
|
||||
{
|
||||
date: '01.11.2024',
|
||||
salesUnits: 45,
|
||||
buyoutPercentage: 82.2,
|
||||
advertising: 320,
|
||||
orders: 52,
|
||||
refusals: 8,
|
||||
returns: 3,
|
||||
revenue: 1250
|
||||
},
|
||||
{
|
||||
date: '02.11.2024',
|
||||
salesUnits: 35,
|
||||
buyoutPercentage: 85.7,
|
||||
advertising: 280,
|
||||
orders: 41,
|
||||
refusals: 6,
|
||||
returns: 2,
|
||||
revenue: 980
|
||||
},
|
||||
{
|
||||
date: '03.11.2024',
|
||||
salesUnits: 52,
|
||||
buyoutPercentage: 78.8,
|
||||
advertising: 390,
|
||||
orders: 61,
|
||||
refusals: 9,
|
||||
returns: 4,
|
||||
revenue: 1450
|
||||
},
|
||||
{
|
||||
date: '04.11.2024',
|
||||
salesUnits: 38,
|
||||
buyoutPercentage: 84.4,
|
||||
advertising: 310,
|
||||
orders: 45,
|
||||
refusals: 7,
|
||||
returns: 2,
|
||||
revenue: 1120
|
||||
},
|
||||
{
|
||||
date: '05.11.2024',
|
||||
salesUnits: 58,
|
||||
buyoutPercentage: 80.6,
|
||||
advertising: 450,
|
||||
orders: 69,
|
||||
refusals: 11,
|
||||
returns: 5,
|
||||
revenue: 1680
|
||||
},
|
||||
{
|
||||
date: '06.11.2024',
|
||||
salesUnits: 47,
|
||||
buyoutPercentage: 83.0,
|
||||
advertising: 370,
|
||||
orders: 55,
|
||||
refusals: 8,
|
||||
returns: 3,
|
||||
revenue: 1350
|
||||
},
|
||||
{
|
||||
date: '07.11.2024',
|
||||
salesUnits: 56,
|
||||
buyoutPercentage: 81.2,
|
||||
advertising: 420,
|
||||
orders: 66,
|
||||
refusals: 10,
|
||||
returns: 4,
|
||||
revenue: 1580
|
||||
},
|
||||
]
|
||||
|
||||
export function SalesTab({ selectedPeriod, useCustomDates, startDate, endDate }: SalesTabProps) {
|
||||
// Состояния для чекбоксов фильтрации
|
||||
const [visibleMetrics, setVisibleMetrics] = useState({
|
||||
sales: true,
|
||||
orders: true,
|
||||
advertising: true,
|
||||
refusals: true,
|
||||
returns: true,
|
||||
})
|
||||
|
||||
// Получаем данные из WB API
|
||||
const { data: wbData, loading, error } = useQuery(GET_WILDBERRIES_STATISTICS, {
|
||||
variables: useCustomDates
|
||||
? { startDate, endDate }
|
||||
: { period: selectedPeriod },
|
||||
errorPolicy: 'all',
|
||||
skip: useCustomDates && (!startDate || !endDate) // Не запрашиваем пока не выбраны обе даты
|
||||
})
|
||||
|
||||
// Данные для графика и таблицы
|
||||
const [chartData, setChartData] = useState<typeof mockChartData>([])
|
||||
const [tableData, setTableData] = useState<typeof mockTableData>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (wbData?.getWildberriesStatistics?.success && wbData.getWildberriesStatistics.data) {
|
||||
const realData = wbData.getWildberriesStatistics.data
|
||||
|
||||
// Обновляем данные для графика
|
||||
const newChartData = realData.map((item: {
|
||||
date: string;
|
||||
sales: number;
|
||||
orders: number;
|
||||
advertising: number;
|
||||
refusals: number;
|
||||
returns: number;
|
||||
}) => ({
|
||||
date: new Date(item.date).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }),
|
||||
sales: item.sales,
|
||||
orders: item.orders,
|
||||
advertising: Math.round(item.advertising),
|
||||
refusals: item.refusals,
|
||||
returns: item.returns
|
||||
}))
|
||||
|
||||
// Обновляем данные для таблицы
|
||||
const newTableData = realData.map((item: {
|
||||
date: string;
|
||||
sales: number;
|
||||
orders: number;
|
||||
advertising: number;
|
||||
refusals: number;
|
||||
returns: number;
|
||||
revenue: number;
|
||||
buyoutPercentage: number;
|
||||
}) => ({
|
||||
date: new Date(item.date).toLocaleDateString('ru-RU'),
|
||||
salesUnits: item.sales,
|
||||
buyoutPercentage: item.buyoutPercentage,
|
||||
advertising: Math.round(item.advertising),
|
||||
orders: item.orders,
|
||||
refusals: item.refusals,
|
||||
returns: item.returns,
|
||||
revenue: Math.round(item.revenue)
|
||||
}))
|
||||
|
||||
setChartData(newChartData)
|
||||
setTableData(newTableData)
|
||||
}
|
||||
}, [wbData])
|
||||
|
||||
// Функция для переключения видимости метрики
|
||||
const toggleMetric = (metric: keyof typeof visibleMetrics) => {
|
||||
setVisibleMetrics(prev => ({
|
||||
...prev,
|
||||
[metric]: !prev[metric]
|
||||
}))
|
||||
}
|
||||
|
||||
// Проверяем состояние загрузки и данных
|
||||
const isLoading = loading || (useCustomDates && (!startDate || !endDate))
|
||||
const hasData = tableData.length > 0
|
||||
const hasAnyActivity = hasData && tableData.some(row =>
|
||||
row.salesUnits > 0 || row.orders > 0 || row.advertising > 0 || row.refusals > 0 || row.returns > 0
|
||||
)
|
||||
|
||||
// Вычисляем итоги на основе текущих данных
|
||||
const totals = {
|
||||
salesUnits: tableData.reduce((sum: number, row: { salesUnits: number }) => sum + row.salesUnits, 0),
|
||||
buyoutPercentage: tableData.length > 0 ? tableData.reduce((sum: number, row: { buyoutPercentage: number }) => sum + row.buyoutPercentage, 0) / tableData.length : 0,
|
||||
advertising: tableData.reduce((sum: number, row: { advertising: number }) => sum + row.advertising, 0),
|
||||
orders: tableData.reduce((sum: number, row: { orders: number }) => sum + row.orders, 0),
|
||||
refusals: tableData.reduce((sum: number, row: { refusals: number }) => sum + row.refusals, 0),
|
||||
returns: tableData.reduce((sum: number, row: { returns: number }) => sum + row.returns, 0),
|
||||
revenue: tableData.reduce((sum: number, row: { revenue: number }) => sum + row.revenue, 0),
|
||||
}
|
||||
|
||||
// Если загружается
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-full flex flex-col space-y-3">
|
||||
<Card className="glass-card p-4 flex-shrink-0" style={{ height: '380px' }}>
|
||||
<div className="h-full flex flex-col">
|
||||
<Skeleton className="h-6 w-48 mb-4" />
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{[1,2,3,4,5].map(i => (
|
||||
<Skeleton key={i} className="h-8 w-20" />
|
||||
))}
|
||||
</div>
|
||||
<Skeleton className="flex-1 w-full" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="glass-card p-4 flex-1">
|
||||
<Skeleton className="h-6 w-40 mb-4" />
|
||||
<div className="space-y-2">
|
||||
{[1,2,3,4,5].map(i => (
|
||||
<Skeleton key={i} className="h-8 w-full" />
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Если нет данных или активности
|
||||
if (!hasData || !hasAnyActivity) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<Card className="glass-card p-8 text-center max-w-md">
|
||||
<div className="mb-4 flex justify-center">
|
||||
<TrendingUp className="h-16 w-16 text-white/30" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">
|
||||
{!hasData ? 'Нет данных' : 'Нет активности'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm">
|
||||
{!hasData
|
||||
? 'За выбранный период данные отсутствуют'
|
||||
: 'За выбранный период не было продаж, заказов или рекламной активности'
|
||||
}
|
||||
</p>
|
||||
{error && (
|
||||
<div className="mt-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg">
|
||||
<p className="text-red-400 text-xs">Ошибка: {error.message}</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col space-y-3">
|
||||
{/* График с фильтрами */}
|
||||
<Card className="glass-card p-4 flex-shrink-0 overflow-hidden" style={{ height: '380px' }}>
|
||||
<div className="h-full flex flex-col min-h-0">
|
||||
{/* Компактный заголовок */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-white font-semibold">Динамика показателей</h3>
|
||||
{error && (
|
||||
<div className="text-red-400 text-xs">Предупреждение: {error.message}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Компактные чекбоксы для фильтрации */}
|
||||
<div className="mb-2 pb-2 border-b border-white/10">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
||||
{Object.entries(chartConfig).map(([key, config]) => {
|
||||
const isVisible = visibleMetrics[key as keyof typeof visibleMetrics]
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={`flex items-center space-x-1.5 p-1.5 rounded-lg transition-all duration-200 cursor-pointer ${
|
||||
isVisible
|
||||
? 'bg-white/10 border border-white/20 shadow-sm'
|
||||
: 'bg-white/5 border border-white/10 hover:bg-white/8'
|
||||
}`}
|
||||
onClick={() => toggleMetric(key as keyof typeof visibleMetrics)}
|
||||
>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={isVisible}
|
||||
onCheckedChange={() => toggleMetric(key as keyof typeof visibleMetrics)}
|
||||
className="border-white/30 data-[state=checked]:bg-white/20 data-[state=checked]:border-white/50"
|
||||
/>
|
||||
<label
|
||||
htmlFor={key}
|
||||
className={`text-xs cursor-pointer select-none flex items-center gap-1.5 transition-colors duration-200 ${
|
||||
isVisible ? 'text-white font-medium' : 'text-white/60'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-2.5 h-2.5 rounded-sm transition-all duration-200 ${
|
||||
isVisible ? 'opacity-100 scale-100' : 'opacity-50 scale-90'
|
||||
}`}
|
||||
style={{ backgroundColor: config.color }}
|
||||
/>
|
||||
{config.label}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0">
|
||||
<ChartContainer config={chartConfig} className="min-h-[200px] h-full w-full">
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 5)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dashed" />}
|
||||
/>
|
||||
|
||||
{/* Условно рендерим бары в зависимости от чекбоксов */}
|
||||
{visibleMetrics.sales && (
|
||||
<Bar
|
||||
dataKey="sales"
|
||||
fill={chartConfig.sales.color}
|
||||
radius={4}
|
||||
/>
|
||||
)}
|
||||
{visibleMetrics.orders && (
|
||||
<Bar
|
||||
dataKey="orders"
|
||||
fill={chartConfig.orders.color}
|
||||
radius={4}
|
||||
/>
|
||||
)}
|
||||
{visibleMetrics.advertising && (
|
||||
<Bar
|
||||
dataKey="advertising"
|
||||
fill={chartConfig.advertising.color}
|
||||
radius={4}
|
||||
/>
|
||||
)}
|
||||
{visibleMetrics.refusals && (
|
||||
<Bar
|
||||
dataKey="refusals"
|
||||
fill={chartConfig.refusals.color}
|
||||
radius={4}
|
||||
/>
|
||||
)}
|
||||
{visibleMetrics.returns && (
|
||||
<Bar
|
||||
dataKey="returns"
|
||||
fill={chartConfig.returns.color}
|
||||
radius={4}
|
||||
/>
|
||||
)}
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Таблица данных */}
|
||||
<Card className="glass-card flex-1 overflow-hidden">
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<h3 className="text-white font-semibold mb-3 text-sm">Детальная статистика</h3>
|
||||
|
||||
<div className="overflow-x-auto flex-1">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/20">
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Дата</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Продажи, шт</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">% выкупов</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Реклама, ₽</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Заказы</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Отказы</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Возвраты</th>
|
||||
<th className="text-left p-2 text-white font-semibold text-xs">Выручка, ₽</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableData.map((row, index) => (
|
||||
<tr key={index} className="border-b border-white/10 hover:bg-white/5 transition-colors">
|
||||
<td className="p-2 text-white/80 text-xs">{row.date}</td>
|
||||
<td className="p-2 text-white text-xs font-medium">{row.salesUnits}</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`${
|
||||
row.buyoutPercentage >= 80
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: 'bg-yellow-500/20 text-yellow-400'
|
||||
}`}
|
||||
>
|
||||
{row.buyoutPercentage}%
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-white/80 text-xs">{row.advertising.toLocaleString('ru-RU')}</td>
|
||||
<td className="p-2 text-white/80 text-xs">{row.orders}</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge variant="secondary" className="bg-red-500/20 text-red-400 text-xs px-2 py-0.5">
|
||||
{row.refusals}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge variant="secondary" className="bg-orange-500/20 text-orange-400 text-xs px-2 py-0.5">
|
||||
{row.returns}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-white text-xs font-medium">
|
||||
{row.revenue.toLocaleString('ru-RU')} ₽
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{/* Результирующая строка */}
|
||||
<tr className="border-t-2 border-white/30 bg-white/10 font-semibold">
|
||||
<td className="p-2 text-white text-xs font-bold">ИТОГО</td>
|
||||
<td className="p-2 text-white text-xs font-bold">{totals.salesUnits}</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`${
|
||||
totals.buyoutPercentage >= 80
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: 'bg-yellow-500/20 text-yellow-400'
|
||||
} font-bold`}
|
||||
>
|
||||
{totals.buyoutPercentage.toFixed(1)}%
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-white text-xs font-bold">{totals.advertising.toLocaleString('ru-RU')}</td>
|
||||
<td className="p-2 text-white text-xs font-bold">{totals.orders}</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge variant="secondary" className="bg-red-500/20 text-red-400 font-bold text-xs px-2 py-0.5">
|
||||
{totals.refusals}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-xs">
|
||||
<Badge variant="secondary" className="bg-orange-500/20 text-orange-400 font-bold text-xs px-2 py-0.5">
|
||||
{totals.returns}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-2 text-white text-xs font-bold">
|
||||
{totals.revenue.toLocaleString('ru-RU')} ₽
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
168
src/components/seller-statistics/seller-statistics-dashboard.tsx
Normal file
168
src/components/seller-statistics/seller-statistics-dashboard.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { SalesTab } from '@/components/seller-statistics/sales-tab'
|
||||
import { DateRangePicker } from '@/components/ui/date-picker'
|
||||
import { BarChart3, PieChart, TrendingUp, Calendar } from 'lucide-react'
|
||||
|
||||
export function SellerStatisticsDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('week')
|
||||
const [useCustomDates, setUseCustomDates] = useState(false)
|
||||
const [startDate, setStartDate] = useState('')
|
||||
const [endDate, setEndDate] = useState('')
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Компактный заголовок с переключателями */}
|
||||
<div className="flex items-center justify-between mb-3 flex-shrink-0">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Статистика продаж</h1>
|
||||
<p className="text-white/50 text-sm">Аналитика продаж, заказов и рекламы</p>
|
||||
</div>
|
||||
|
||||
{/* Переключатели периода и пользовательские даты */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Стильные переключатели периода */}
|
||||
<div className="flex gap-1 bg-white/5 backdrop-blur border border-white/10 rounded-xl p-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedPeriod('week')
|
||||
setUseCustomDates(false)
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||||
selectedPeriod === 'week' && !useCustomDates
|
||||
? 'bg-white/20 text-white shadow-sm'
|
||||
: 'text-white/60 hover:bg-white/10 hover:text-white/80'
|
||||
}`}
|
||||
>
|
||||
Неделя
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedPeriod('month')
|
||||
setUseCustomDates(false)
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||||
selectedPeriod === 'month' && !useCustomDates
|
||||
? 'bg-white/20 text-white shadow-sm'
|
||||
: 'text-white/60 hover:bg-white/10 hover:text-white/80'
|
||||
}`}
|
||||
>
|
||||
Месяц
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedPeriod('quarter')
|
||||
setUseCustomDates(false)
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 ${
|
||||
selectedPeriod === 'quarter' && !useCustomDates
|
||||
? 'bg-white/20 text-white shadow-sm'
|
||||
: 'text-white/60 hover:bg-white/10 hover:text-white/80'
|
||||
}`}
|
||||
>
|
||||
Квартал
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setUseCustomDates(true)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-200 flex items-center gap-1 ${
|
||||
useCustomDates
|
||||
? 'bg-white/20 text-white shadow-sm'
|
||||
: 'text-white/60 hover:bg-white/10 hover:text-white/80'
|
||||
}`}
|
||||
>
|
||||
<Calendar className="h-3 w-3" />
|
||||
Период
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Выбор произвольных дат */}
|
||||
{useCustomDates && (
|
||||
<DateRangePicker
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
className="min-w-[280px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основной контент с табами */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Tabs defaultValue="sales" className="h-full flex flex-col">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-white/5 backdrop-blur border border-white/10 rounded-xl flex-shrink-0 h-11">
|
||||
<TabsTrigger
|
||||
value="sales"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 flex items-center gap-2 text-sm rounded-lg"
|
||||
>
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
Продажи
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="advertising"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 flex items-center gap-2 text-sm rounded-lg"
|
||||
>
|
||||
<TrendingUp className="h-4 w-4" />
|
||||
Реклама
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="other"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 flex items-center gap-2 text-sm rounded-lg"
|
||||
>
|
||||
<PieChart className="h-4 w-4" />
|
||||
Иное
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Контент вкладок */}
|
||||
<div className="flex-1 overflow-hidden mt-3">
|
||||
<TabsContent value="sales" className="h-full m-0 overflow-hidden">
|
||||
<SalesTab
|
||||
selectedPeriod={selectedPeriod}
|
||||
useCustomDates={useCustomDates}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="advertising" className="h-full m-0 overflow-hidden">
|
||||
<Card className="glass-card h-full overflow-hidden p-6">
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<TrendingUp className="h-12 w-12 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Статистика рекламы</h3>
|
||||
<p className="text-white/60">Раздел в разработке</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="other" className="h-full m-0 overflow-hidden">
|
||||
<Card className="glass-card h-full overflow-hidden p-6">
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<PieChart className="h-12 w-12 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Прочая статистика</h3>
|
||||
<p className="text-white/60">Раздел в разработке</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user