Обновлен компонент сайдбара: улучшена структура кода, добавлены новые кнопки для навигации, включая "Склад" и "Статистика" для фулфилмент-центров. Оптимизированы запросы GraphQL для получения данных о пользователе и его организации. Улучшена читаемость кода и взаимодействие с пользователем.

This commit is contained in:
Veronika Smirnova
2025-07-22 10:48:06 +03:00
parent a611460168
commit 03ca28e68c
5 changed files with 1395 additions and 154 deletions

View File

@ -0,0 +1,543 @@
"use client";
import { useState } from "react";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Sidebar } from "@/components/dashboard/sidebar";
import { useSidebar } from "@/hooks/useSidebar";
import { StatsCard } from "@/components/supplies/ui/stats-card";
import { StatsGrid } from "@/components/supplies/ui/stats-grid";
import {
BarChart3,
TrendingUp,
AlertTriangle,
Send,
Archive,
Clock,
ShoppingBag,
ChevronDown,
ChevronUp,
Target,
Activity,
Zap,
PieChart,
Calendar,
Package,
DollarSign,
Users,
Truck,
} from "lucide-react";
export function FulfillmentStatisticsDashboard() {
const { getSidebarMargin } = useSidebar();
// Состояния для свёртывания блоков
const [expandedSections, setExpandedSections] = useState({
allTime: true,
marketplaces: true,
analytics: false,
performance: false,
});
// Мок данные для статистики
const statisticsData = {
// Данные за все время
totalProducts: 15678,
totalDefects: 145,
totalSupplies: 2341,
totalRevenue: 45670000,
totalOrders: 8934,
// Отправка на маркетплейсы
sentToWildberries: 8934,
sentToOzon: 4523,
sentToOthers: 1876,
// Аналитика производительности
avgProcessingTime: 2.4,
defectRate: 0.92,
returnRate: 4.3,
customerSatisfaction: 4.8,
// Тренды
revenueTrend: 18,
ordersTrend: 12,
defectsTrend: -8,
satisfactionTrend: 5,
};
const formatNumber = (num: number) => {
return num.toLocaleString("ru-RU");
};
const formatCurrency = (num: number) => {
return new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(num);
};
const toggleSection = (section: keyof typeof expandedSections) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}));
};
// Компонент заголовка секции с кнопкой свёртывания
const SectionHeader = ({
title,
section,
badge,
color = "text-white",
}: {
title: string;
section: keyof typeof expandedSections;
badge?: number | string;
color?: string;
}) => (
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<h2 className={`text-lg font-semibold ${color}`}>{title}</h2>
{badge && (
<span className="px-2 py-1 bg-blue-500/20 text-blue-300 text-xs rounded-full font-medium">
{badge}
</span>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => toggleSection(section)}
className="text-white/60 hover:text-white hover:bg-white/10 p-1 h-8 w-8"
>
{expandedSections[section] ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
);
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main
className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-y-auto transition-all duration-300`}
>
<div className="w-full">
{/* Компактный заголовок с ключевыми показателями */}
<div className="mb-6 flex-shrink-0">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-4">
<div className="p-2 bg-gradient-to-r from-blue-500/20 to-purple-500/20 rounded-xl">
<BarChart3 className="h-6 w-6 text-blue-400" />
</div>
<div>
<div className="flex items-center space-x-3">
<span className="text-sm text-white/60">Общий доход</span>
<span className="text-lg font-bold text-green-400">
{formatCurrency(statisticsData.totalRevenue)}
</span>
</div>
<div className="flex items-center space-x-3">
<span className="text-sm text-white/60">Качество</span>
<span className="text-lg font-bold text-blue-400">
{statisticsData.customerSatisfaction}/5.0
</span>
</div>
</div>
</div>
<div className="text-right">
<div className="text-sm text-white/60">Уровень брака</div>
<div className="text-2xl font-bold text-white">
{statisticsData.defectRate}%
</div>
</div>
</div>
</div>
{/* Блоки статистики */}
<div className="space-y-6">
{/* Накопленная статистика */}
<div>
<SectionHeader
title="Накопленная статистика"
section="allTime"
badge={formatNumber(statisticsData.totalProducts)}
color="text-cyan-400"
/>
{expandedSections.allTime && (
<div className="space-y-4">
<StatsGrid>
<StatsCard
title="Обработано товаров"
value={formatNumber(statisticsData.totalProducts)}
icon={Archive}
iconColor="text-cyan-400"
iconBg="bg-cyan-500/20"
subtitle="Общий объем"
/>
<StatsCard
title="Выявлено брака"
value={formatNumber(statisticsData.totalDefects)}
icon={AlertTriangle}
iconColor="text-red-400"
iconBg="bg-red-500/20"
trend={{
value: Math.abs(statisticsData.defectsTrend),
isPositive: statisticsData.defectsTrend < 0,
}}
subtitle="Всего единиц"
/>
<StatsCard
title="Поставок получено"
value={formatNumber(statisticsData.totalSupplies)}
icon={Clock}
iconColor="text-orange-400"
iconBg="bg-orange-500/20"
subtitle="Входящих поставок"
/>
<StatsCard
title="Общий доход"
value={formatCurrency(statisticsData.totalRevenue)}
icon={DollarSign}
iconColor="text-green-400"
iconBg="bg-green-500/20"
trend={{
value: statisticsData.revenueTrend,
isPositive: true,
}}
subtitle="За все время"
/>
</StatsGrid>
{/* Дополнительные метрики */}
<StatsGrid columns={2}>
<StatsCard
title="Выполнено заказов"
value={formatNumber(statisticsData.totalOrders)}
icon={Package}
iconColor="text-purple-400"
iconBg="bg-purple-500/20"
trend={{
value: statisticsData.ordersTrend,
isPositive: true,
}}
subtitle="Успешных отгрузок"
/>
<StatsCard
title="Удовлетворенность клиентов"
value={`${statisticsData.customerSatisfaction}/5.0`}
icon={Users}
iconColor="text-blue-400"
iconBg="bg-blue-500/20"
trend={{
value: statisticsData.satisfactionTrend,
isPositive: true,
}}
subtitle="Средний рейтинг"
/>
</StatsGrid>
</div>
)}
</div>
{/* Отгрузка на площадки */}
<div>
<SectionHeader
title="Отгрузка на площадки"
section="marketplaces"
badge={formatNumber(
statisticsData.sentToWildberries +
statisticsData.sentToOzon +
statisticsData.sentToOthers
)}
color="text-orange-400"
/>
{expandedSections.marketplaces && (
<div className="space-y-4">
<StatsGrid columns={3}>
<StatsCard
title="Wildberries"
value={formatNumber(statisticsData.sentToWildberries)}
icon={Send}
iconColor="text-purple-400"
iconBg="bg-purple-500/20"
subtitle="Отправлено единиц"
/>
<StatsCard
title="Ozon"
value={formatNumber(statisticsData.sentToOzon)}
icon={Send}
iconColor="text-blue-400"
iconBg="bg-blue-500/20"
subtitle="Отправлено единиц"
/>
<StatsCard
title="Другие маркетплейсы"
value={formatNumber(statisticsData.sentToOthers)}
icon={ShoppingBag}
iconColor="text-green-400"
iconBg="bg-green-500/20"
subtitle="Яндекс.Маркет, Авито"
/>
</StatsGrid>
{/* Диаграмма распределения */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card className="glass-card p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-white/80">
Распределение отгрузок
</h3>
<PieChart className="h-4 w-4 text-white/60" />
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-purple-500 rounded-full"></div>
<span className="text-xs text-white/70">WB</span>
</div>
<span className="text-xs text-white font-medium">
{(
(statisticsData.sentToWildberries /
(statisticsData.sentToWildberries +
statisticsData.sentToOzon +
statisticsData.sentToOthers)) *
100
).toFixed(1)}
%
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
<span className="text-xs text-white/70">Ozon</span>
</div>
<span className="text-xs text-white font-medium">
{(
(statisticsData.sentToOzon /
(statisticsData.sentToWildberries +
statisticsData.sentToOzon +
statisticsData.sentToOthers)) *
100
).toFixed(1)}
%
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<span className="text-xs text-white/70">
Другие
</span>
</div>
<span className="text-xs text-white font-medium">
{(
(statisticsData.sentToOthers /
(statisticsData.sentToWildberries +
statisticsData.sentToOzon +
statisticsData.sentToOthers)) *
100
).toFixed(1)}
%
</span>
</div>
</div>
</Card>
{/* Тренды по площадкам */}
<Card className="glass-card p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-white/80">
Тренды роста
</h3>
<TrendingUp className="h-4 w-4 text-green-400" />
</div>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs text-white/70">
Wildberries
</span>
<div className="flex items-center space-x-2">
<div className="w-16 h-2 bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-purple-500 rounded-full"
style={{ width: "68%" }}
></div>
</div>
<span className="text-xs text-green-400">+12%</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-white/70">Ozon</span>
<div className="flex items-center space-x-2">
<div className="w-16 h-2 bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-blue-500 rounded-full"
style={{ width: "45%" }}
></div>
</div>
<span className="text-xs text-green-400">+8%</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-white/70">Другие</span>
<div className="flex items-center space-x-2">
<div className="w-16 h-2 bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-green-500 rounded-full"
style={{ width: "32%" }}
></div>
</div>
<span className="text-xs text-green-400">+15%</span>
</div>
</div>
</div>
</Card>
</div>
</div>
)}
</div>
{/* Аналитика производительности */}
<div>
<SectionHeader
title="Аналитика производительности"
section="performance"
badge={`${statisticsData.avgProcessingTime}ч`}
color="text-green-400"
/>
{expandedSections.performance && (
<StatsGrid>
<StatsCard
title="Среднее время обработки"
value={`${statisticsData.avgProcessingTime} ч`}
icon={Clock}
iconColor="text-blue-400"
iconBg="bg-blue-500/20"
subtitle="На единицу товара"
/>
<StatsCard
title="Уровень брака"
value={`${statisticsData.defectRate}%`}
icon={AlertTriangle}
iconColor="text-red-400"
iconBg="bg-red-500/20"
trend={{
value: Math.abs(statisticsData.defectsTrend),
isPositive: statisticsData.defectsTrend < 0,
}}
subtitle="От общего объема"
/>
<StatsCard
title="Уровень возвратов"
value={`${statisticsData.returnRate}%`}
icon={Truck}
iconColor="text-yellow-400"
iconBg="bg-yellow-500/20"
subtitle="Возвраты с площадок"
/>
<StatsCard
title="Рейтинг качества"
value={`${statisticsData.customerSatisfaction}/5.0`}
icon={Users}
iconColor="text-green-400"
iconBg="bg-green-500/20"
trend={{
value: statisticsData.satisfactionTrend,
isPositive: true,
}}
subtitle="Отзывы клиентов"
/>
</StatsGrid>
)}
</div>
{/* AI-аналитика и прогнозы */}
<div>
<SectionHeader
title="Умная аналитика"
section="analytics"
color="text-purple-400"
/>
{expandedSections.analytics && (
<Card className="glass-card p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<div className="p-2 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-xl">
<Zap className="h-5 w-5 text-purple-400" />
</div>
<h3 className="text-lg font-semibold text-white">
Прогнозы и рекомендации
</h3>
</div>
<Badge
variant="secondary"
className="bg-purple-500/20 text-purple-300"
>
AI-анализ
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="p-4 bg-white/5 rounded-xl border border-white/10">
<div className="flex items-center space-x-2 mb-2">
<Target className="h-4 w-4 text-green-400" />
<span className="text-sm font-medium text-green-400">
Прогноз роста
</span>
</div>
<p className="text-xs text-white/70">
Ожидается увеличение объемов на 23% в следующем квартале
</p>
</div>
<div className="p-4 bg-white/5 rounded-xl border border-white/10">
<div className="flex items-center space-x-2 mb-2">
<Activity className="h-4 w-4 text-blue-400" />
<span className="text-sm font-medium text-blue-400">
Оптимизация
</span>
</div>
<p className="text-xs text-white/70">
Возможно снижение времени обработки на 18% при
автоматизации
</p>
</div>
<div className="p-4 bg-white/5 rounded-xl border border-white/10">
<div className="flex items-center space-x-2 mb-2">
<Calendar className="h-4 w-4 text-orange-400" />
<span className="text-sm font-medium text-orange-400">
Сезонность
</span>
</div>
<p className="text-xs text-white/70">
Пиковые нагрузки ожидаются в ноябре-декабре (+45%)
</p>
</div>
</div>
</Card>
)}
</div>
</div>
</div>
</main>
</div>
);
}