Files
sfera/src/components/fulfillment-statistics/fulfillment-statistics-dashboard.tsx

544 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}