/** * @file DashboardPage.jsx * @description Main dashboard page. Dispatches to role-specific sections * (analytics, orders, logistics, driver deliveries, admin panels). * Manages navigation, notifications, PWA status, and order-group state. */ import React from "react"; import { Navigate, useNavigate, useSearchParams, useLocation } from "react-router-dom"; import { DriverDeliveryPlanner } from "../components/driver/DriverDeliveryPlanner"; import { isOrderGroupVisibleToDriver } from "../services/orderGroupViews"; import { LogisticsReadinessBoard } from "../components/logistics/LogisticsReadinessBoard"; import { AdminDashboard } from "../components/admin/AdminDashboard"; import UserManagementPanel from "../components/admin/UserManagementPanel"; import ErrorLogPanel from "../components/admin/ErrorLogPanel"; import { StopWordsPanel } from "../components/admin/StopWordsPanel"; import { ActionLogPanel } from "../components/admin/ActionLogPanel"; import { SuggestionsPanel } from "../components/admin/SuggestionsPanel"; import { SmsCampaignPanel } from "../components/admin/SmsCampaignPanel"; import { Panel } from "../components/UI/Panel"; import { SkeletonPage, SkeletonTable } from "../components/UI/Loading"; import { useAuth } from "../context/AuthContext"; import { useNotifications } from "../hooks/useNotifications"; import { usePushNotifications } from "../hooks/usePushNotifications"; import { usePwaStatus } from "../hooks/usePwaStatus"; import { useOrderGroups } from "../hooks/useOrderGroups"; import { AppShell } from "../layouts/AppShell"; // ── Navigation Config ───────────────────────────────────────────────────── const MEGA_ADMIN_NAV = [ { key: "analytics", label: "Аналитика", description: "Статистика доставки, графики и показатели.", badge: null }, { key: "orders", label: "Группы", description: "Реестр групп доставки.", badge: null }, { key: "users", label: "Пользователи", description: "Управление пользователями и ролями.", badge: null }, { key: "errors", label: "Ошибки", description: "Журнал ошибок приложения.", badge: null }, { key: "stop_words", label: "Стоп-слова", description: "Слова, исключаемые из клиентской карточки.", badge: null }, { key: "action_log", label: "Журнал", description: "Журнал действий сотрудников.", badge: null }, { key: "suggestions", label: "Предложения", description: "Предложения сотрудников по улучшению.", badge: null }, { key: "sms_campaign", label: "SMS-кампании", description: "Логи и настройки SMS-рассылок.", badge: null }, ]; // ── Role → Default Section Map ───────────────────────────────────────────── const ROLE_SECTION = { mega_admin: { key: "analytics", label: "Аналитика" }, admin: { key: "analytics", label: "Аналитика", description: "Статистика доставки." }, manager: { key: "orders", label: "Группы", description: "Реестр групп доставки, поиск и просмотр карточки." }, logistician: { key: "logistics", label: "Логистика", description: "Группы доставки по готовности к уведомлению." }, driver: { key: "deliveries", label: "Мои доставки", description: "Группы доставки по датам и статусам." }, }; // ── Dashboard Component ──────────────────────────────────────────────────── export const DashboardPage = () => { const { user, signOut, isSessionLoading } = useAuth(); const location = useLocation(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const userRole = user?.role; const isMegaAdmin = userRole === "mega_admin"; const isAdmin = userRole === "admin" || isMegaAdmin; const section = ROLE_SECTION[userRole] || ROLE_SECTION.manager; // Active section from URL, fallback to role default const activeSection = searchParams.get("tab") || section.key; const setActiveSection = (key) => { if (key === section.key) { setSearchParams({}, { replace: true }); // default tab → clean URL } else { setSearchParams({ tab: key }, { replace: true }); } }; // ── Notifications ───────────────────────────────────────────────────────── const { notifications, unreadCount, isLoading: notifLoading, markAsRead: markNotificationRead, markAllAsRead: markAllNotificationsRead, } = useNotifications(user?.id); const { isSupported, isSubscribed, subscribe } = usePushNotifications(user?.id); React.useEffect(() => { if (isSupported && !isSubscribed && user?.id && Notification.permission === "granted") { subscribe(); } }, [isSupported, isSubscribed, user?.id, subscribe]); // ── PWA ──────────────────────────────────────────────────────────────────── const { isInstalled, isInstallAvailable, installApp: onInstallApp } = usePwaStatus(); // ── Order Groups ───────────────────────────────────────────────────────── const { orderGroups, allOrderGroups, filteredOrderGroups, selectedOrderGroupId, setSelectedOrderGroupId, filters, setFilters, statusOptions, isLoading, loadError, } = useOrderGroups(); // ── Derived City List ───────────────────────────────────────────────────── const cities = React.useMemo(() => { const set = new Set(); for (const g of allOrderGroups) { if (g.city) set.add(g.city); } return [...set].sort(); }, [allOrderGroups]); // ── Driver order count (for badge) ──────────────────────────────────────── const driverOrderCount = React.useMemo(() => { if (userRole !== "driver" || !user) return 0; return allOrderGroups.filter((g) => { const isVisible = isOrderGroupVisibleToDriver(g); const isAssignedToMe = g.assignedDriverId === user.id; return isVisible && isAssignedToMe; }).length; }, [allOrderGroups, user, userRole]); // ── Navigation Builder ──────────────────────────────────────────────────── const openGroupPage = React.useCallback((groupId) => { navigate("/dashboard/group/" + groupId); }, [navigate]); const navItems = isMegaAdmin ? MEGA_ADMIN_NAV : userRole === "admin" ? [ { key: "analytics", label: "Аналитика", description: "Статистика доставки.", badge: null }, { key: "orders", label: "Группы", description: "Реестр групп доставки.", badge: String(allOrderGroups.length || orderGroups.length || 0) }, { key: "users", label: "Пользователи", description: "Управление пользователями.", badge: null }, { key: "stop_words", label: "Стоп-слова", description: "Слова, исключаемые из карточки.", badge: null }, { key: "errors", label: "Ошибки", description: "Журнал ошибок приложения.", badge: null }, { key: "action_log", label: "Журнал", description: "Журнал действий сотрудников.", badge: null }, { key: "suggestions", label: "Предложения", description: "Предложения сотрудников по улучшению.", badge: null }, ] : userRole === "logistician" ? [ { key: "logistics", label: "Логистика", description: "Группы доставки по готовности к уведомлению.", badge: String(allOrderGroups.length || orderGroups.length || 0) }, ] : [ { key: section.key, label: section.label, description: section.description, badge: String(userRole === "driver" ? driverOrderCount : (allOrderGroups.length || orderGroups.length || 0)) }, { key: "suggestions", label: "Предложения", description: "Предложить улучшение.", badge: null }, ]; const activeSectionMeta = navItems.find((n) => n.key === activeSection) || navItems[0]; // ── Auth Guard ──────────────────────────────────────────────────────────── const isGuideOpen = false; const ALLOWED_DASHBOARD_ROLES = ["admin", "mega_admin", "manager", "logistician", "driver"]; // Wait for session restore before deciding redirect if (isSessionLoading) { return null; } if (!user) { return ; } if (!ALLOWED_DASHBOARD_ROLES.includes(userRole)) { return ; } // ── Section Renderer ────────────────────────────────────────────────────── const renderActiveSection = () => { if (activeSection === "analytics") return
; if (activeSection === "users") return
; if (activeSection === "stop_words") return
; if (activeSection === "errors") return
; if (activeSection === "action_log") return
; if (activeSection === "suggestions") return
; if (activeSection === "sms_campaign") return
; if (isLoading) { if (userRole === "driver") { return ; } return ; } if (userRole === "driver") { return (
); } // All roles (logistician, admin, mega_admin, manager) — same grouped board return (
); }; // ── Layout ──────────────────────────────────────────────────────────────── return ( {loadError && ( Не удалось загрузить данные. Обратитесь к администратору. )} {renderActiveSection()} ); };