Обновлен компонент сайдбара: улучшена структура кода, добавлены новые кнопки для навигации, включая "Склад" и "Статистика" для фулфилмент-центров. Оптимизированы запросы GraphQL для получения данных о пользователе и его организации. Улучшена читаемость кода и взаимодействие с пользователем.
This commit is contained in:
10
src/app/fulfillment-statistics/page.tsx
Normal file
10
src/app/fulfillment-statistics/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
10
src/app/fulfillment-warehouse/page.tsx
Normal file
10
src/app/fulfillment-warehouse/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
"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 { 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,
|
||||
@ -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">
|
||||
@ -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,7 +247,10 @@ 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
|
||||
@ -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 ? "Склад" : ""}
|
||||
@ -423,10 +530,12 @@ export function Sidebar() {
|
||||
|
||||
<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>
|
||||
)
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user