Обновлен компонент сайдбара: улучшена структура кода, добавлены новые кнопки для навигации, включая "Склад" и "Статистика" для фулфилмент-центров. Оптимизированы запросы 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,567 @@
"use client"
import { useState, useEffect } 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 {
Package,
TrendingUp,
AlertTriangle,
RotateCcw,
Wrench,
Users,
ShoppingBag,
ChevronDown,
ChevronUp,
Box,
Zap,
Target,
Activity,
BarChart3,
Eye,
EyeOff,
Warehouse
} from 'lucide-react'
export function FulfillmentWarehouseDashboard() {
const { getSidebarMargin } = useSidebar()
// Состояния для свёртывания блоков
const [expandedSections, setExpandedSections] = useState({
warehouse: true
})
// Состояние для живых изменений продуктов
const [liveChange, setLiveChange] = useState({
value: 12,
isPositive: true,
timestamp: Date.now()
})
// Состояние для модуля товары с дополнительными значениями
const [goodsData, setGoodsData] = useState({
processing: 245, // В обработке (положительное)
rejected: 18, // Отклонено (отрицательное)
efficiency: 87, // Эффективность обработки
isActive: true, // Активность процесса
pulse: 0 // Для анимации пульса
})
// Симуляция живых изменений для продуктов
useEffect(() => {
const interval = setInterval(() => {
const change = Math.floor(Math.random() * 20) - 10 // от -10 до +10
setLiveChange({
value: Math.abs(change),
isPositive: change >= 0,
timestamp: Date.now()
})
}, 3000) // каждые 3 секунды
return () => clearInterval(interval)
}, [])
// Симуляция изменений для товаров
useEffect(() => {
const interval = setInterval(() => {
setGoodsData(prev => ({
...prev,
processing: prev.processing + Math.floor(Math.random() * 10) - 5,
rejected: Math.max(0, prev.rejected + Math.floor(Math.random() * 6) - 3),
efficiency: Math.min(100, Math.max(70, prev.efficiency + Math.floor(Math.random() * 6) - 3)),
pulse: prev.pulse + 1
}))
}, 2500) // каждые 2.5 секунды
return () => clearInterval(interval)
}, [])
// Мок данные для статистики склада фулфилмента
const warehouseStats = {
// Текущие данные
currentProducts: 856, // Готовые продукты
currentGoods: 391, // Товары в процессе
currentDefects: 23,
currentReturns: 156,
currentFulfillmentSupplies: 89,
currentSellerSupplies: 234,
// Тренды (в процентах)
productsTrend: 12,
goodsTrend: 8,
defectsTrend: -5,
returnsTrend: 8,
suppliesTrend: 15,
// Дополнительная аналитика
efficiency: 94.5,
turnover: 2.3,
utilizationRate: 87
}
const formatNumber = (num: number) => {
return num.toLocaleString('ru-RU')
}
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
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">
<SectionHeader
title="Состояние склада"
section="warehouse"
badge={warehouseStats.currentProducts + warehouseStats.currentGoods + warehouseStats.currentFulfillmentSupplies + warehouseStats.currentSellerSupplies}
color="text-blue-400"
/>
{expandedSections.warehouse && (
<div className="grid grid-cols-6 gap-4">
{/* Уникальный модуль "Продукты" */}
<div className="min-w-0 relative group">
<div className="bg-gradient-to-br from-blue-500/10 via-blue-400/5 to-cyan-500/10 backdrop-blur border border-blue-400/30 rounded-2xl p-4 hover:from-blue-500/20 hover:to-cyan-500/20 transition-all duration-500 hover:scale-[1.02] hover:shadow-2xl hover:shadow-blue-500/20">
{/* Живой индикатор изменений */}
<div className="absolute -top-1 -right-1 flex items-center space-x-1">
<div className="relative">
<div className={`w-3 h-3 rounded-full animate-pulse shadow-lg ${
liveChange.isPositive
? 'bg-green-500 shadow-green-500/50'
: 'bg-red-500 shadow-red-500/50'
}`}></div>
<div className={`absolute inset-0 w-3 h-3 rounded-full animate-ping opacity-75 ${
liveChange.isPositive ? 'bg-green-500' : 'bg-red-500'
}`}></div>
</div>
<div className={`backdrop-blur text-white text-[10px] font-bold px-2 py-0.5 rounded-full animate-bounce ${
liveChange.isPositive
? 'bg-green-500/90'
: 'bg-red-500/90'
}`}>
{liveChange.isPositive ? '+' : '-'}{liveChange.value}
</div>
</div>
{/* Заголовок с иконкой */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<div className="relative">
<div className="p-2 bg-blue-500/20 rounded-xl border border-blue-400/30">
<Box className="h-4 w-4 text-blue-400" />
</div>
<div className={`absolute -top-1 -right-1 w-2 h-2 rounded-full animate-pulse ${
liveChange.isPositive ? 'bg-green-400' : 'bg-red-400'
}`}></div>
</div>
<span className="text-blue-100 text-sm font-semibold tracking-wide">ПРОДУКТЫ</span>
</div>
{/* Мини-график тренда */}
<div className="flex items-center space-x-1">
<div className="flex items-end space-x-0.5 h-4">
<div className={`w-1 rounded-full ${liveChange.isPositive ? 'bg-green-400/60' : 'bg-red-400/60'}`} style={{height: '60%'}}></div>
<div className={`w-1 rounded-full ${liveChange.isPositive ? 'bg-green-400/70' : 'bg-red-400/70'}`} style={{height: '80%'}}></div>
<div className={`w-1 rounded-full ${liveChange.isPositive ? 'bg-green-400/80' : 'bg-red-400/80'}`} style={{height: '70%'}}></div>
<div className={`w-1 rounded-full animate-pulse ${liveChange.isPositive ? 'bg-green-400' : 'bg-red-400'}`} style={{height: '100%'}}></div>
</div>
</div>
</div>
{/* Основное значение */}
<div className="mb-2">
<div className="flex items-baseline space-x-2">
<span className="text-2xl font-black text-white tracking-tight">
{formatNumber(warehouseStats.currentProducts)}
</span>
<div className={`flex items-center space-x-1 px-2 py-1 rounded-full ${
liveChange.isPositive ? 'bg-green-500/20' : 'bg-red-500/20'
}`}>
<svg className={`w-3 h-3 ${liveChange.isPositive ? 'text-green-400' : 'text-red-400'}`} fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d={liveChange.isPositive
? "M5.293 7.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L6.707 7.707a1 1 0 01-1.414 0z"
: "M14.707 12.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l2.293-2.293a1 1 0 011.414 0z"
} clipRule="evenodd" />
</svg>
<span className={`text-xs font-bold ${liveChange.isPositive ? 'text-green-400' : 'text-red-400'}`}>
{liveChange.value}%
</span>
</div>
</div>
</div>
{/* Подпись */}
<div className="text-blue-200/70 text-xs">
Готовые к отправке
</div>
{/* Прогресс-бар */}
<div className="mt-3 relative">
<div className="h-1.5 bg-blue-900/30 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-blue-400 to-cyan-400 rounded-full transition-all duration-1000 ease-out relative"
style={{width: '78%'}}
>
<div className="absolute right-0 top-0 h-full w-4 bg-gradient-to-r from-transparent to-white/30 animate-pulse"></div>
</div>
</div>
<div className="flex justify-between text-[10px] text-blue-300/60 mt-1">
<span>0</span>
<span>1.2К</span>
</div>
</div>
{/* Живое изменение значения */}
<div className="absolute bottom-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className={`flex items-center space-x-1 backdrop-blur px-2 py-1 rounded-lg ${
liveChange.isPositive ? 'bg-green-500/90' : 'bg-red-500/90'
}`}>
<div className="w-2 h-2 bg-white rounded-full animate-pulse"></div>
<span className="text-white text-[10px] font-bold">
LIVE {liveChange.isPositive ? '+' : '-'}{liveChange.value}
</span>
</div>
</div>
{/* Декоративные элементы */}
<div className="absolute top-1 left-1 w-8 h-8 bg-gradient-to-br from-blue-400/10 to-transparent rounded-full"></div>
<div className="absolute bottom-1 right-1 w-6 h-6 bg-gradient-to-tl from-cyan-400/10 to-transparent rounded-full"></div>
</div>
</div>
{/* Уникальный модуль "Товары" */}
<div className="min-w-0 relative group">
<div className="bg-gradient-to-br from-cyan-500/10 via-teal-400/5 to-emerald-500/10 backdrop-blur border border-cyan-400/30 rounded-2xl p-4 hover:from-cyan-500/20 hover:to-emerald-500/20 transition-all duration-700 hover:scale-[1.03] hover:shadow-2xl hover:shadow-cyan-500/20 relative overflow-hidden">
{/* Анимированный фон */}
<div className="absolute inset-0 opacity-10">
<div className={`absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-cyan-400 to-transparent transform transition-transform duration-2000 ${
goodsData.pulse % 2 === 0 ? 'translate-x-full' : '-translate-x-full'
}`}></div>
</div>
{/* Статус активности */}
<div className="absolute -top-1 -left-1 flex items-center space-x-1">
<div className="relative">
<div className="w-4 h-4 bg-cyan-500 rounded-full animate-spin shadow-lg shadow-cyan-500/50" style={{
animation: 'spin 2s linear infinite'
}}></div>
<div className="absolute inset-1 w-2 h-2 bg-white rounded-full"></div>
</div>
<div className="bg-cyan-500/90 backdrop-blur text-white text-[9px] font-bold px-2 py-0.5 rounded-full">
ACTIVE
</div>
</div>
{/* Заголовок с двойной иконкой */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<div className="relative">
<div className="p-2 bg-cyan-500/20 rounded-xl border border-cyan-400/30 relative">
<Package className="h-4 w-4 text-cyan-400" />
{/* Мини-индикатор обработки */}
<div className="absolute -bottom-1 -right-1 w-3 h-3 bg-gradient-to-br from-green-400 to-emerald-500 rounded-full flex items-center justify-center">
<div className="w-1 h-1 bg-white rounded-full animate-pulse"></div>
</div>
</div>
</div>
<span className="text-cyan-100 text-sm font-semibold tracking-wide">ТОВАРЫ</span>
</div>
{/* Круговой прогресс эффективности */}
<div className="relative w-8 h-8">
<svg className="w-8 h-8 transform -rotate-90" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="14" stroke="currentColor" strokeWidth="2" fill="none" className="text-cyan-900/30" />
<circle
cx="16" cy="16" r="14"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeDasharray={`${goodsData.efficiency * 0.88} 88`}
className="text-cyan-400 transition-all duration-1000"
/>
</svg>
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-[8px] font-bold text-cyan-300">{goodsData.efficiency}%</span>
</div>
</div>
</div>
{/* Основное значение */}
<div className="mb-3">
<div className="flex items-baseline space-x-2">
<span className="text-2xl font-black text-white tracking-tight">
{formatNumber(warehouseStats.currentGoods)}
</span>
<div className="flex items-center space-x-1 bg-cyan-500/20 px-2 py-1 rounded-full">
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce"></div>
<span className="text-cyan-400 text-xs font-bold">
LIVE
</span>
</div>
</div>
</div>
{/* Дополнительные значения */}
<div className="grid grid-cols-2 gap-2 mb-3">
{/* Положительное значение */}
<div className="bg-green-500/10 border border-green-400/30 rounded-lg p-2">
<div className="flex items-center space-x-1 mb-1">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-green-400 text-[10px] font-semibold">ОБРАБОТКА</span>
</div>
<div className="text-green-300 text-sm font-bold">
+{formatNumber(goodsData.processing)}
</div>
</div>
{/* Отрицательное значение */}
<div className="bg-red-500/10 border border-red-400/30 rounded-lg p-2">
<div className="flex items-center space-x-1 mb-1">
<div className="w-2 h-2 bg-red-400 rounded-full animate-pulse"></div>
<span className="text-red-400 text-[10px] font-semibold">ОТКЛОНЕНО</span>
</div>
<div className="text-red-300 text-sm font-bold">
-{formatNumber(goodsData.rejected)}
</div>
</div>
</div>
{/* Подпись */}
<div className="text-cyan-200/70 text-xs mb-2">
В обработке
</div>
{/* Волновой прогресс */}
<div className="relative h-2 bg-cyan-900/30 rounded-full overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-emerald-400 rounded-full" style={{
width: `${(goodsData.processing / (goodsData.processing + goodsData.rejected)) * 100}%`
}}></div>
{/* Волновая анимация */}
<div className="absolute inset-0 opacity-50">
<div className={`h-full w-full bg-gradient-to-r from-transparent via-white/30 to-transparent transform transition-transform duration-2000 ${
goodsData.pulse % 3 === 0 ? 'translate-x-full' : goodsData.pulse % 3 === 1 ? 'translate-x-0' : '-translate-x-full'
}`}></div>
</div>
</div>
{/* Hover эффект с детальной информацией */}
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/20 to-emerald-500/20 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<div className="text-center">
<div className="text-white text-xs font-bold mb-1">ДЕТАЛИ</div>
<div className="flex space-x-4 text-[10px]">
<div className="text-green-300">
<div className="font-bold">+{goodsData.processing}</div>
<div className="opacity-70">Активных</div>
</div>
<div className="text-red-300">
<div className="font-bold">-{goodsData.rejected}</div>
<div className="opacity-70">Проблем</div>
</div>
<div className="text-cyan-300">
<div className="font-bold">{goodsData.efficiency}%</div>
<div className="opacity-70">Успех</div>
</div>
</div>
</div>
</div>
{/* Декоративные частицы */}
<div className="absolute top-2 right-2 w-1 h-1 bg-cyan-400 rounded-full animate-ping"></div>
<div className="absolute bottom-3 left-3 w-1 h-1 bg-emerald-400 rounded-full animate-ping" style={{animationDelay: '1s'}}></div>
<div className="absolute top-1/2 left-1 w-1 h-1 bg-teal-400 rounded-full animate-ping" style={{animationDelay: '2s'}}></div>
</div>
</div>
<StatsCard
title="Брак"
value={formatNumber(warehouseStats.currentDefects)}
icon={AlertTriangle}
iconColor="text-red-400"
iconBg="bg-red-500/20"
trend={{ value: Math.abs(warehouseStats.defectsTrend), isPositive: warehouseStats.defectsTrend < 0 }}
subtitle="Требует утилизации"
className="min-w-0"
/>
<StatsCard
title="Возвраты с ПВЗ"
value={formatNumber(warehouseStats.currentReturns)}
icon={RotateCcw}
iconColor="text-yellow-400"
iconBg="bg-yellow-500/20"
trend={{ value: warehouseStats.returnsTrend, isPositive: false }}
subtitle="К обработке"
className="min-w-0"
/>
<StatsCard
title="Расходники ФФ"
value={formatNumber(warehouseStats.currentFulfillmentSupplies)}
icon={Wrench}
iconColor="text-purple-400"
iconBg="bg-purple-500/20"
subtitle="Упаковка, этикетки, пленка"
className="min-w-0"
/>
<StatsCard
title="Расходники селлеров"
value={formatNumber(warehouseStats.currentSellerSupplies)}
icon={Users}
iconColor="text-green-400"
iconBg="bg-green-500/20"
subtitle="Материалы клиентов"
className="min-w-0"
/>
</div>
)}
</div>
{/* Компактный заголовок с ключевыми метриками */}
<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">
<Warehouse 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">{warehouseStats.efficiency}%</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">{warehouseStats.turnover}x</span>
</div>
</div>
</div>
<div className="text-right">
<div className="text-sm text-white/60">Загрузка склада</div>
<div className="text-2xl font-bold text-white">{warehouseStats.utilizationRate}%</div>
</div>
</div>
</div>
{/* Нестандартные решения */}
<div className="mt-8 space-y-6">
{/* Интеллектуальные инсайты */}
<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-green-500/20 to-blue-500/20 rounded-xl">
<Zap className="h-5 w-5 text-green-400" />
</div>
<h3 className="text-lg font-semibold text-white">Умные рекомендации</h3>
</div>
<Badge variant="secondary" className="bg-green-500/20 text-green-300">
AI-анализ
</Badge>
</div>
<div className="grid grid-cols-1 md: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-yellow-400" />
<span className="text-sm font-medium text-yellow-400">Оптимизация</span>
</div>
<p className="text-xs text-white/70">
Рекомендуется увеличить запас расходников на 15% для покрытия пикового спроса
</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">
Ожидается рост возвратов на 12% в следующем месяце. Подготовьте дополнительные места
</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">
<BarChart3 className="h-4 w-4 text-purple-400" />
<span className="text-sm font-medium text-purple-400">Тренд</span>
</div>
<p className="text-xs text-white/70">
Эффективность обработки товаров выросла на 8% за последний месяц
</p>
</div>
</div>
</Card>
{/* Быстрые действия */}
<Card className="glass-card p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-white">Быстрые действия</h3>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="border-white/20 text-white/70 hover:bg-white/10">
<Eye className="h-4 w-4 mr-2" />
Обзор
</Button>
<Button size="sm" variant="outline" className="border-white/20 text-white/70 hover:bg-white/10">
<Activity className="h-4 w-4 mr-2" />
Отчеты
</Button>
</div>
</div>
<div className="text-center py-8">
<Package className="h-12 w-12 text-white/40 mx-auto mb-4" />
<p className="text-white/60 text-sm">
Основная функциональность склада будет добавлена на следующем этапе
</p>
</div>
</Card>
</div>
</div>
</main>
</div>
)
}