203 lines
9.3 KiB
JavaScript
203 lines
9.3 KiB
JavaScript
import React from "react";
|
||
import { Navigate, useNavigate, useSearchParams, useLocation } from "react-router-dom";
|
||
import { DriverDeliveryPlanner } from "../components/driver/DriverDeliveryPlanner";
|
||
import { LogisticsReadinessBoard } from "../components/logistics/LogisticsReadinessBoard";
|
||
import { OrdersTable } from "../components/orders/OrdersTable";
|
||
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 { Panel } from "../components/UI/Panel";
|
||
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";
|
||
|
||
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 },
|
||
];
|
||
|
||
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: "Группы доставки по датам и статусам." },
|
||
};
|
||
|
||
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 });
|
||
}
|
||
};
|
||
|
||
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]);
|
||
|
||
const { isInstalled, isInstallAvailable, installApp: onInstallApp } = usePwaStatus();
|
||
|
||
const {
|
||
orderGroups,
|
||
allOrderGroups,
|
||
filteredOrderGroups,
|
||
selectedOrderGroupId,
|
||
setSelectedOrderGroupId,
|
||
filters,
|
||
setFilters,
|
||
statusOptions,
|
||
isLoading,
|
||
loadError,
|
||
} = useOrderGroups();
|
||
|
||
const cities = React.useMemo(() => {
|
||
const set = new Set();
|
||
for (const g of allOrderGroups) {
|
||
if (g.city) set.add(g.city);
|
||
}
|
||
return [...set].sort();
|
||
}, [allOrderGroups]);
|
||
|
||
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 },
|
||
]
|
||
: userRole === "logistician"
|
||
? [
|
||
{ key: "logistics", label: "Логистика", description: "Группы доставки по готовности к уведомлению.", badge: String(allOrderGroups.length || orderGroups.length || 0) },
|
||
]
|
||
: [
|
||
{ key: section.key, label: section.label, description: section.description, badge: String(allOrderGroups.length || orderGroups.length || 0) },
|
||
];
|
||
|
||
const activeSectionMeta = navItems.find((n) => n.key === activeSection) || navItems[0];
|
||
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 <Navigate to={`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`} replace />;
|
||
}
|
||
|
||
if (!ALLOWED_DASHBOARD_ROLES.includes(userRole)) {
|
||
return <Navigate to="/forbidden" replace />;
|
||
}
|
||
|
||
const renderActiveSection = () => {
|
||
|
||
if (activeSection === "analytics") return <div className="space-y-6 xl:space-y-8"><AdminDashboard /></div>;
|
||
if (activeSection === "users") return <div className="space-y-6 xl:space-y-8"><UserManagementPanel /></div>;
|
||
if (activeSection === "stop_words") return <div className="space-y-6 xl:space-y-8"><StopWordsPanel /></div>;
|
||
if (activeSection === "errors") return <div className="space-y-6 xl:space-y-8"><ErrorLogPanel /></div>;
|
||
if (activeSection === "action_log") return <div className="space-y-6 xl:space-y-8"><ActionLogPanel /></div>;
|
||
|
||
if (userRole === "driver") {
|
||
return (
|
||
<div className="space-y-6 xl:space-y-8">
|
||
<DriverDeliveryPlanner orderGroups={allOrderGroups} onOpenOrder={openGroupPage} currentUser={user} />
|
||
</div>
|
||
);
|
||
}
|
||
if (userRole === "logistician") {
|
||
if (activeSection === "orders") {
|
||
return (
|
||
<div className="space-y-6 xl:space-y-8">
|
||
<OrdersTable orderGroups={filteredOrderGroups} selectedOrderGroupId={selectedOrderGroupId} onOpenOrder={openGroupPage} filters={filters} setFilters={setFilters} statusOptions={statusOptions} cities={cities} />
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<div className="space-y-6 xl:space-y-8">
|
||
<LogisticsReadinessBoard orderGroups={allOrderGroups} onSelectSet={openGroupPage} statusOptions={statusOptions} />
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<div className="space-y-6 xl:space-y-8">
|
||
<OrdersTable orderGroups={filteredOrderGroups} selectedOrderGroupId={selectedOrderGroupId} onOpenOrder={openGroupPage} filters={filters} setFilters={setFilters} statusOptions={statusOptions} cities={cities} />
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<AppShell
|
||
user={user}
|
||
onSignOut={signOut}
|
||
onInstallApp={onInstallApp}
|
||
isInstalled={isInstalled}
|
||
isInstallAvailable={isInstallAvailable}
|
||
onOpenGuide={undefined}
|
||
isGuideOpen={false}
|
||
navItems={navItems}
|
||
activeSection={activeSection}
|
||
onSectionChange={setActiveSection}
|
||
sectionMeta={activeSectionMeta}
|
||
notifications={notifications}
|
||
unreadCount={unreadCount}
|
||
onMarkNotificationRead={markNotificationRead}
|
||
onMarkAllNotificationsRead={markAllNotificationsRead}
|
||
>
|
||
{isLoading && (
|
||
<Panel className="border border-dashed border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 text-sm text-[var(--color-text-muted)]">
|
||
Загружаем данные...
|
||
</Panel>
|
||
)}
|
||
{loadError && (
|
||
<Panel className="border border-dashed border-[var(--color-danger)] bg-[var(--color-surface-strong)] p-4 text-sm text-[var(--color-danger)]">
|
||
Не удалось загрузить данные. Обратитесь к администратору.
|
||
</Panel>
|
||
)}
|
||
{renderActiveSection()}
|
||
</AppShell>
|
||
);
|
||
}; |