Обновлен компонент сайдбара: улучшена структура кода, добавлены новые кнопки для навигации, включая "Склад" и "Статистика" для фулфилмент-центров. Оптимизированы запросы 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,10 @@
import { AuthGuard } from "@/components/auth-guard";
import { FulfillmentStatisticsDashboard } from "@/components/fulfillment-statistics/fulfillment-statistics-dashboard";
export default function FulfillmentStatisticsPage() {
return (
<AuthGuard>
<FulfillmentStatisticsDashboard />
</AuthGuard>
);
}

View File

@ -0,0 +1,10 @@
import { AuthGuard } from "@/components/auth-guard";
import { FulfillmentWarehouseDashboard } from "@/components/fulfillment-warehouse/fulfillment-warehouse-dashboard";
export default function FulfillmentWarehousePage() {
return (
<AuthGuard>
<FulfillmentWarehouseDashboard />
</AuthGuard>
);
}

View File

@ -1,15 +1,15 @@
"use client"
"use client";
import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { useRouter, usePathname } from 'next/navigation'
import { useQuery } from '@apollo/client'
import { GET_CONVERSATIONS, GET_INCOMING_REQUESTS } from '@/graphql/queries'
import {
Settings,
import { useAuth } from "@/hooks/useAuth";
import { useSidebar } from "@/hooks/useSidebar";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { useRouter, usePathname } from "next/navigation";
import { useQuery } from "@apollo/client";
import { GET_CONVERSATIONS, GET_INCOMING_REQUESTS } from "@/graphql/queries";
import {
Settings,
LogOut,
Store,
MessageCircle,
@ -19,132 +19,157 @@ import {
Truck,
Handshake,
ChevronLeft,
ChevronRight
} from 'lucide-react'
ChevronRight,
BarChart3,
} from "lucide-react";
export function Sidebar() {
const { user, logout } = useAuth()
const router = useRouter()
const pathname = usePathname()
const { isCollapsed, toggleSidebar } = useSidebar()
const { user, logout } = useAuth();
const router = useRouter();
const pathname = usePathname();
const { isCollapsed, toggleSidebar } = useSidebar();
// Загружаем список чатов для подсчета непрочитанных сообщений
const { data: conversationsData } = useQuery(GET_CONVERSATIONS, {
pollInterval: 60000, // Обновляем каждую минуту в сайдбаре - этого достаточно
fetchPolicy: 'cache-first',
errorPolicy: 'ignore', // Игнорируем ошибки чтобы не ломать сайдбар
fetchPolicy: "cache-first",
errorPolicy: "ignore", // Игнорируем ошибки чтобы не ломать сайдбар
notifyOnNetworkStatusChange: false, // Плавные обновления без мерцания
})
});
// Загружаем входящие заявки для подсчета новых запросов
const { data: incomingRequestsData } = useQuery(GET_INCOMING_REQUESTS, {
pollInterval: 60000, // Обновляем каждую минуту
fetchPolicy: 'cache-first',
errorPolicy: 'ignore',
fetchPolicy: "cache-first",
errorPolicy: "ignore",
notifyOnNetworkStatusChange: false,
})
});
const conversations = conversationsData?.conversations || []
const incomingRequests = incomingRequestsData?.incomingRequests || []
const totalUnreadCount = conversations.reduce((sum: number, conv: { unreadCount?: number }) => sum + (conv.unreadCount || 0), 0)
const incomingRequestsCount = incomingRequests.length
const conversations = conversationsData?.conversations || [];
const incomingRequests = incomingRequestsData?.incomingRequests || [];
const totalUnreadCount = conversations.reduce(
(sum: number, conv: { unreadCount?: number }) =>
sum + (conv.unreadCount || 0),
0
);
const incomingRequestsCount = incomingRequests.length;
const getInitials = () => {
const orgName = getOrganizationName()
return orgName.charAt(0).toUpperCase()
}
const orgName = getOrganizationName();
return orgName.charAt(0).toUpperCase();
};
const getOrganizationName = () => {
if (user?.organization?.name) {
return user.organization.name
return user.organization.name;
}
if (user?.organization?.fullName) {
return user.organization.fullName
return user.organization.fullName;
}
return 'Организация'
}
return "Организация";
};
const getCabinetType = () => {
if (!user?.organization?.type) return 'Кабинет'
if (!user?.organization?.type) return "Кабинет";
switch (user.organization.type) {
case 'FULFILLMENT':
return 'Фулфилмент'
case 'SELLER':
return 'Селлер'
case 'LOGIST':
return 'Логистика'
case 'WHOLESALE':
return 'Оптовик'
case "FULFILLMENT":
return "Фулфилмент";
case "SELLER":
return "Селлер";
case "LOGIST":
return "Логистика";
case "WHOLESALE":
return "Оптовик";
default:
return 'Кабинет'
return "Кабинет";
}
}
};
const handleSettingsClick = () => {
router.push('/settings')
}
router.push("/settings");
};
const handleMarketClick = () => {
router.push('/market')
}
router.push("/market");
};
const handleMessengerClick = () => {
router.push('/messenger')
}
router.push("/messenger");
};
const handleServicesClick = () => {
router.push('/services')
}
router.push("/services");
};
const handleWarehouseClick = () => {
router.push('/warehouse')
}
router.push("/warehouse");
};
const handleEmployeesClick = () => {
router.push('/employees')
}
router.push("/employees");
};
const handleSuppliesClick = () => {
// Для каждого типа кабинета свой роут
switch (user?.organization?.type) {
case 'FULFILLMENT':
router.push('/fulfillment-supplies')
break
case 'SELLER':
router.push('/supplies')
break
case 'WHOLESALE':
router.push('/supplies')
break
case 'LOGIST':
router.push('/logistics')
break
case "FULFILLMENT":
router.push("/fulfillment-supplies");
break;
case "SELLER":
router.push("/supplies");
break;
case "WHOLESALE":
router.push("/supplies");
break;
case "LOGIST":
router.push("/logistics");
break;
default:
router.push('/supplies')
router.push("/supplies");
}
}
};
const handleFulfillmentWarehouseClick = () => {
router.push("/fulfillment-warehouse");
};
const handleFulfillmentStatisticsClick = () => {
router.push("/fulfillment-statistics");
};
const handlePartnersClick = () => {
router.push('/partners')
}
router.push("/partners");
};
const isSettingsActive = pathname === '/settings'
const isMarketActive = pathname.startsWith('/market')
const isMessengerActive = pathname.startsWith('/messenger')
const isServicesActive = pathname.startsWith('/services')
const isWarehouseActive = pathname.startsWith('/warehouse')
const isEmployeesActive = pathname.startsWith('/employees')
const isSuppliesActive = pathname.startsWith('/supplies') || pathname.startsWith('/fulfillment-supplies') || pathname.startsWith('/logistics')
const isPartnersActive = pathname.startsWith('/partners')
const isSettingsActive = pathname === "/settings";
const isMarketActive = pathname.startsWith("/market");
const isMessengerActive = pathname.startsWith("/messenger");
const isServicesActive = pathname.startsWith("/services");
const isWarehouseActive = pathname.startsWith("/warehouse");
const isFulfillmentWarehouseActive = pathname.startsWith(
"/fulfillment-warehouse"
);
const isFulfillmentStatisticsActive = pathname.startsWith(
"/fulfillment-statistics"
);
const isEmployeesActive = pathname.startsWith("/employees");
const isSuppliesActive =
pathname.startsWith("/supplies") ||
pathname.startsWith("/fulfillment-supplies") ||
pathname.startsWith("/logistics");
const isPartnersActive = pathname.startsWith("/partners");
return (
<div className="relative">
{/* Основной сайдбар */}
<div className={`fixed left-4 top-4 bottom-4 ${isCollapsed ? 'w-16' : 'w-56'} bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl ${isCollapsed ? 'p-2' : 'p-3'} transition-all duration-300 ease-in-out z-50`}>
<div
className={`fixed left-4 top-4 bottom-4 ${
isCollapsed ? "w-16" : "w-56"
} bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl ${
isCollapsed ? "p-2" : "p-3"
} transition-all duration-300 ease-in-out z-50`}
>
{/* ОХУЕННАЯ кнопка сворачивания - на правом краю сайдбара */}
<div className="absolute -right-6 top-1/2 -translate-y-1/2 z-[999]">
<div className="relative group">
@ -164,11 +189,11 @@ export function Sidebar() {
<ChevronLeft className="h-6 w-6 text-white/80 group-hover:text-white transition-colors duration-300" />
)}
</div>
{/* Простое свечение при наведении */}
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-purple-500/0 to-blue-500/0 group-hover:from-purple-500/10 group-hover:to-blue-500/10 transition-all duration-500"></div>
</Button>
{/* Подсказка только в свернутом состоянии */}
{isCollapsed && (
<div className="absolute left-full ml-3 top-1/2 -translate-y-1/2 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300">
@ -192,9 +217,9 @@ export function Sidebar() {
<div className="relative flex-shrink-0">
<Avatar className="h-8 w-8 ring-2 ring-white/40">
{user?.avatar ? (
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
className="w-full h-full object-cover"
/>
) : null}
@ -205,7 +230,10 @@ export function Sidebar() {
<div className="absolute -bottom-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full border-2 border-white/40"></div>
</div>
<div className="flex-1 min-w-0">
<p className="text-white text-xs font-medium mb-0.5 break-words" title={getOrganizationName()}>
<p
className="text-white text-xs font-medium mb-0.5 break-words"
title={getOrganizationName()}
>
{getOrganizationName()}
</p>
<div className="flex items-center space-x-1">
@ -219,12 +247,15 @@ export function Sidebar() {
) : (
// Свернутое состояние - только аватар
<div className="flex justify-center">
<div className="relative" title={`${getOrganizationName()} - ${getCabinetType()}`}>
<div
className="relative"
title={`${getOrganizationName()} - ${getCabinetType()}`}
>
<Avatar className="h-7 w-7 ring-2 ring-white/40">
{user?.avatar ? (
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
className="w-full h-full object-cover"
/>
) : null}
@ -242,24 +273,32 @@ export function Sidebar() {
<div className="space-y-1 mb-3 flex-1">
<Button
variant={isMarketActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isMarketActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleMarketClick}
title={isCollapsed ? "Маркет" : ""}
>
<Store className={`${isCollapsed ? 'h-4 w-4' : 'h-4 w-4'} flex-shrink-0`} />
<Store
className={`${
isCollapsed ? "h-4 w-4" : "h-4 w-4"
} flex-shrink-0`}
/>
{!isCollapsed && <span className="ml-3">Маркет</span>}
</Button>
<Button
variant={isMessengerActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs relative ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs relative ${
isMessengerActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleMessengerClick}
title={isCollapsed ? "Мессенджер" : ""}
@ -268,22 +307,30 @@ export function Sidebar() {
{!isCollapsed && <span className="ml-3">Мессенджер</span>}
{/* Индикатор непрочитанных сообщений */}
{totalUnreadCount > 0 && (
<div className={`absolute ${
isCollapsed
? 'top-1 right-1 w-3 h-3'
: 'top-2 right-2 w-4 h-4'
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}>
{isCollapsed ? '' : (totalUnreadCount > 99 ? '99+' : totalUnreadCount)}
<div
className={`absolute ${
isCollapsed
? "top-1 right-1 w-3 h-3"
: "top-2 right-2 w-4 h-4"
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}
>
{isCollapsed
? ""
: totalUnreadCount > 99
? "99+"
: totalUnreadCount}
</div>
)}
</Button>
<Button
variant={isPartnersActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs relative ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs relative ${
isPartnersActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handlePartnersClick}
title={isCollapsed ? "Партнёры" : ""}
@ -292,24 +339,32 @@ export function Sidebar() {
{!isCollapsed && <span className="ml-3">Партнёры</span>}
{/* Индикатор входящих заявок */}
{incomingRequestsCount > 0 && (
<div className={`absolute ${
isCollapsed
? 'top-1 right-1 w-3 h-3'
: 'top-2 right-2 w-4 h-4'
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}>
{isCollapsed ? '' : (incomingRequestsCount > 99 ? '99+' : incomingRequestsCount)}
<div
className={`absolute ${
isCollapsed
? "top-1 right-1 w-3 h-3"
: "top-2 right-2 w-4 h-4"
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}
>
{isCollapsed
? ""
: incomingRequestsCount > 99
? "99+"
: incomingRequestsCount}
</div>
)}
</Button>
{/* Услуги - только для фулфилмент центров */}
{user?.organization?.type === 'FULFILLMENT' && (
{user?.organization?.type === "FULFILLMENT" && (
<Button
variant={isServicesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isServicesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleServicesClick}
title={isCollapsed ? "Услуги" : ""}
@ -320,13 +375,15 @@ export function Sidebar() {
)}
{/* Сотрудники - только для фулфилмент центров */}
{user?.organization?.type === 'FULFILLMENT' && (
{user?.organization?.type === "FULFILLMENT" && (
<Button
variant={isEmployeesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isEmployeesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleEmployeesClick}
title={isCollapsed ? "Сотрудники" : ""}
@ -337,13 +394,15 @@ export function Sidebar() {
)}
{/* Мои поставки - для селлеров */}
{user?.organization?.type === 'SELLER' && (
{user?.organization?.type === "SELLER" && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Мои поставки" : ""}
@ -354,30 +413,74 @@ export function Sidebar() {
)}
{/* Входящие поставки - для фулфилмент */}
{user?.organization?.type === 'FULFILLMENT' && (
{user?.organization?.type === "FULFILLMENT" && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Входящие поставки" : ""}
>
<Truck className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Входящие поставки</span>}
{!isCollapsed && (
<span className="ml-3">Входящие поставки</span>
)}
</Button>
)}
{/* Склад - для фулфилмент */}
{user?.organization?.type === "FULFILLMENT" && (
<Button
variant={isFulfillmentWarehouseActive ? "secondary" : "ghost"}
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isFulfillmentWarehouseActive
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleFulfillmentWarehouseClick}
title={isCollapsed ? "Склад" : ""}
>
<Warehouse className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Склад</span>}
</Button>
)}
{/* Статистика - для фулфилмент */}
{user?.organization?.type === "FULFILLMENT" && (
<Button
variant={isFulfillmentStatisticsActive ? "secondary" : "ghost"}
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isFulfillmentStatisticsActive
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleFulfillmentStatisticsClick}
title={isCollapsed ? "Статистика" : ""}
>
<BarChart3 className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Статистика</span>}
</Button>
)}
{/* Отгрузки - для оптовиков */}
{user?.organization?.type === 'WHOLESALE' && (
{user?.organization?.type === "WHOLESALE" && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Отгрузки" : ""}
@ -388,13 +491,15 @@ export function Sidebar() {
)}
{/* Перевозки - для логистов */}
{user?.organization?.type === 'LOGIST' && (
{user?.organization?.type === "LOGIST" && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Перевозки" : ""}
@ -405,13 +510,15 @@ export function Sidebar() {
)}
{/* Склад - только для оптовиков */}
{user?.organization?.type === 'WHOLESALE' && (
{user?.organization?.type === "WHOLESALE" && (
<Button
variant={isWarehouseActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isWarehouseActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleWarehouseClick}
title={isCollapsed ? "Склад" : ""}
@ -420,13 +527,15 @@ export function Sidebar() {
{!isCollapsed && <span className="ml-3">Склад</span>}
</Button>
)}
<Button
variant={isSettingsActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-left transition-all duration-200 text-xs ${
isSettingsActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
? "bg-white/20 text-white hover:bg-white/30"
: "text-white/80 hover:bg-white/10 hover:text-white"
} cursor-pointer`}
onClick={handleSettingsClick}
title={isCollapsed ? "Настройки профиля" : ""}
@ -440,7 +549,9 @@ export function Sidebar() {
<div>
<Button
variant="ghost"
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer text-xs transition-all duration-200`}
className={`w-full ${
isCollapsed ? "justify-center px-2 h-9" : "justify-start h-10"
} text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer text-xs transition-all duration-200`}
onClick={logout}
title={isCollapsed ? "Выйти" : ""}
>
@ -451,5 +562,5 @@ export function Sidebar() {
</div>
</div>
</div>
)
}
);
}

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

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