diff --git a/src/app/fulfillment-statistics/page.tsx b/src/app/fulfillment-statistics/page.tsx
new file mode 100644
index 0000000..10bae07
--- /dev/null
+++ b/src/app/fulfillment-statistics/page.tsx
@@ -0,0 +1,10 @@
+import { AuthGuard } from "@/components/auth-guard";
+import { FulfillmentStatisticsDashboard } from "@/components/fulfillment-statistics/fulfillment-statistics-dashboard";
+
+export default function FulfillmentStatisticsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/fulfillment-warehouse/page.tsx b/src/app/fulfillment-warehouse/page.tsx
new file mode 100644
index 0000000..fc5f880
--- /dev/null
+++ b/src/app/fulfillment-warehouse/page.tsx
@@ -0,0 +1,10 @@
+import { AuthGuard } from "@/components/auth-guard";
+import { FulfillmentWarehouseDashboard } from "@/components/fulfillment-warehouse/fulfillment-warehouse-dashboard";
+
+export default function FulfillmentWarehousePage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/dashboard/sidebar.tsx b/src/components/dashboard/sidebar.tsx
index f28b2ec..9b880cb 100644
--- a/src/components/dashboard/sidebar.tsx
+++ b/src/components/dashboard/sidebar.tsx
@@ -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 (
{/* Основной сайдбар */}
-
+
{/* ОХУЕННАЯ кнопка сворачивания - на правом краю сайдбара */}
@@ -164,11 +189,11 @@ export function Sidebar() {
)}
-
+
{/* Простое свечение при наведении */}
-
+
{/* Подсказка только в свернутом состоянии */}
{isCollapsed && (
@@ -192,9 +217,9 @@ export function Sidebar() {
{user?.avatar ? (
-
) : null}
@@ -205,7 +230,10 @@ export function Sidebar() {
-
+
{getOrganizationName()}
@@ -219,12 +247,15 @@ export function Sidebar() {
) : (
// Свернутое состояние - только аватар
-
+
{user?.avatar ? (
-
) : null}
@@ -242,24 +273,32 @@ export function Sidebar() {
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/components/fulfillment-statistics/fulfillment-statistics-dashboard.tsx b/src/components/fulfillment-statistics/fulfillment-statistics-dashboard.tsx
new file mode 100644
index 0000000..2035b87
--- /dev/null
+++ b/src/components/fulfillment-statistics/fulfillment-statistics-dashboard.tsx
@@ -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;
+ }) => (
+
+
+
{title}
+ {badge && (
+
+ {badge}
+
+ )}
+
+
toggleSection(section)}
+ className="text-white/60 hover:text-white hover:bg-white/10 p-1 h-8 w-8"
+ >
+ {expandedSections[section] ? (
+
+ ) : (
+
+ )}
+
+
+ );
+
+ return (
+
+
+
+
+ {/* Компактный заголовок с ключевыми показателями */}
+
+
+
+
+
+
+
+
+ Общий доход
+
+ {formatCurrency(statisticsData.totalRevenue)}
+
+
+
+ Качество
+
+ {statisticsData.customerSatisfaction}/5.0
+
+
+
+
+
+
Уровень брака
+
+ {statisticsData.defectRate}%
+
+
+
+
+
+ {/* Блоки статистики */}
+
+ {/* Накопленная статистика */}
+
+
+ {expandedSections.allTime && (
+
+
+
+
+
+
+
+
+
+
+
+ {/* Дополнительные метрики */}
+
+
+
+
+
+
+ )}
+
+
+ {/* Отгрузка на площадки */}
+
+
+ {expandedSections.marketplaces && (
+
+
+
+
+
+
+
+
+
+ {/* Диаграмма распределения */}
+
+
+
+
+ Распределение отгрузок
+
+
+
+
+
+
+
+ {(
+ (statisticsData.sentToWildberries /
+ (statisticsData.sentToWildberries +
+ statisticsData.sentToOzon +
+ statisticsData.sentToOthers)) *
+ 100
+ ).toFixed(1)}
+ %
+
+
+
+
+
+ {(
+ (statisticsData.sentToOzon /
+ (statisticsData.sentToWildberries +
+ statisticsData.sentToOzon +
+ statisticsData.sentToOthers)) *
+ 100
+ ).toFixed(1)}
+ %
+
+
+
+
+
+ {(
+ (statisticsData.sentToOthers /
+ (statisticsData.sentToWildberries +
+ statisticsData.sentToOzon +
+ statisticsData.sentToOthers)) *
+ 100
+ ).toFixed(1)}
+ %
+
+
+
+
+
+ {/* Тренды по площадкам */}
+
+
+
+ Тренды роста
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Аналитика производительности */}
+
+
+ {expandedSections.performance && (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {/* AI-аналитика и прогнозы */}
+
+
+ {expandedSections.analytics && (
+
+
+
+
+
+
+
+ Прогнозы и рекомендации
+
+
+
+ AI-анализ
+
+
+
+
+
+
+
+
+ Прогноз роста
+
+
+
+ Ожидается увеличение объемов на 23% в следующем квартале
+
+
+
+
+
+
+ Возможно снижение времени обработки на 18% при
+ автоматизации
+
+
+
+
+
+
+
+ Сезонность
+
+
+
+ Пиковые нагрузки ожидаются в ноябре-декабре (+45%)
+
+
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx b/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx
new file mode 100644
index 0000000..c15ac0e
--- /dev/null
+++ b/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx
@@ -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
+ }) => (
+
+
+
{title}
+ {badge && (
+
+ {badge}
+
+ )}
+
+
toggleSection(section)}
+ className="text-white/60 hover:text-white hover:bg-white/10 p-1 h-8 w-8"
+ >
+ {expandedSections[section] ? (
+
+ ) : (
+
+ )}
+
+
+ )
+
+ return (
+
+
+
+
+ {/* Объединенный блок склада - ПЕРВЫЙ СВЕРХУ */}
+
+
+ {expandedSections.warehouse && (
+
+ {/* Уникальный модуль "Продукты" */}
+
+
+ {/* Живой индикатор изменений */}
+
+
+
+ {liveChange.isPositive ? '+' : '-'}{liveChange.value}
+
+
+
+ {/* Заголовок с иконкой */}
+
+
+
+ {/* Мини-график тренда */}
+
+
+
+ {/* Основное значение */}
+
+
+
+ {formatNumber(warehouseStats.currentProducts)}
+
+
+
+
+ {liveChange.value}%
+
+
+
+
+
+ {/* Подпись */}
+
+ Готовые к отправке
+
+
+ {/* Прогресс-бар */}
+
+
+ {/* Живое изменение значения */}
+
+
+
+
+ LIVE {liveChange.isPositive ? '+' : '-'}{liveChange.value}
+
+
+
+
+ {/* Декоративные элементы */}
+
+
+
+
+
+ {/* Уникальный модуль "Товары" */}
+
+
+
+ {/* Анимированный фон */}
+
+
+ {/* Статус активности */}
+
+
+ {/* Заголовок с двойной иконкой */}
+
+
+
+
+
+ {/* Мини-индикатор обработки */}
+
+
+
+
ТОВАРЫ
+
+
+ {/* Круговой прогресс эффективности */}
+
+
+
+ {goodsData.efficiency}%
+
+
+
+
+ {/* Основное значение */}
+
+
+
+ {formatNumber(warehouseStats.currentGoods)}
+
+
+
+
+
+ {/* Дополнительные значения */}
+
+ {/* Положительное значение */}
+
+
+
+ +{formatNumber(goodsData.processing)}
+
+
+
+ {/* Отрицательное значение */}
+
+
+
+ -{formatNumber(goodsData.rejected)}
+
+
+
+
+ {/* Подпись */}
+
+ В обработке
+
+
+ {/* Волновой прогресс */}
+
+
+ {/* Волновая анимация */}
+
+
+
+ {/* Hover эффект с детальной информацией */}
+
+
+
ДЕТАЛИ
+
+
+
+{goodsData.processing}
+
Активных
+
+
+
-{goodsData.rejected}
+
Проблем
+
+
+
{goodsData.efficiency}%
+
Успех
+
+
+
+
+
+ {/* Декоративные частицы */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Компактный заголовок с ключевыми метриками */}
+
+
+
+
+
+
+
+
+ Эффективность
+ {warehouseStats.efficiency}%
+
+
+ Оборачиваемость
+ {warehouseStats.turnover}x
+
+
+
+
+
Загрузка склада
+
{warehouseStats.utilizationRate}%
+
+
+
+
+ {/* Нестандартные решения */}
+
+ {/* Интеллектуальные инсайты */}
+
+
+
+
+
+
+
Умные рекомендации
+
+
+ AI-анализ
+
+
+
+
+
+
+
+ Оптимизация
+
+
+ Рекомендуется увеличить запас расходников на 15% для покрытия пикового спроса
+
+
+
+
+
+
+ Ожидается рост возвратов на 12% в следующем месяце. Подготовьте дополнительные места
+
+
+
+
+
+
+ Тренд
+
+
+ Эффективность обработки товаров выросла на 8% за последний месяц
+
+
+
+
+
+ {/* Быстрые действия */}
+
+
+
Быстрые действия
+
+
+
+ Обзор
+
+
+
+ Отчеты
+
+
+
+
+
+
+
+ Основная функциональность склада будет добавлена на следующем этапе
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file