import React from "react";
import { Navigate } from "react-router-dom";
import { ORDER_STATUSES } from "../constants/orderStatuses";
import { ROLE_LABELS, ROLE_PERMISSIONS } from "../constants/roles";
import {
DRIVER_STATUSES,
getOrderStatusComment,
getStatusStageKey,
LOGISTICS_STATUSES,
PRODUCTION_STATUSES,
} from "../constants/deliveryWorkflow";
import { AuditPanel } from "../components/admin/AuditPanel";
import { UserDirectoryPanel } from "../components/admin/UserDirectoryPanel";
import { UserOnboardingPanel } from "../components/admin/UserOnboardingPanel";
import { DeliverySetDetailPanel } from "../components/logistics/DeliverySetDetailPanel";
import { DriverDeliveryDetail } from "../components/driver/DriverDeliveryDetail";
import { DriverDeliveryPlanner } from "../components/driver/DriverDeliveryPlanner";
import { KpiCard } from "../components/dashboard/KpiCard";
import { LogisticsReadinessBoard } from "../components/logistics/LogisticsReadinessBoard";
import { ProductionQueuePanel } from "../components/dashboard/ProductionQueuePanel";
import { RoleWorkspacePanel } from "../components/dashboard/RoleWorkspacePanel";
import { BotControlPanel } from "../components/logistics/BotControlPanel";
import { OrdersCalendarView } from "../components/orders/OrdersCalendarView";
import { OrderDetailPanel } from "../components/orders/OrderDetailPanel";
import { OrderFilters } from "../components/orders/OrderFilters";
import { OrdersKanbanBoard } from "../components/orders/OrdersKanbanBoard";
import { OrdersTable } from "../components/orders/OrdersTable";
import { Badge } from "../components/UI/Badge";
import { Button } from "../components/UI/Button";
import { Modal } from "../components/UI/Modal";
import { Panel } from "../components/UI/Panel";
import { SegmentedTabs } from "../components/UI/SegmentedTabs";
import { Select } from "../components/UI/Select";
import { useAuth } from "../context/AuthContext";
import { useOrders } from "../hooks/useOrders";
import { AppShell } from "../layouts/AppShell";
import {
filterDriverDeliveries,
getDeliveryDay,
} from "../services/driverDeliveries";
import {
buildKanbanColumns,
filterArchiveOrders,
filterKanbanColumnsByStage,
filterRegistryOrders,
} from "../services/orderViews";
import { getKanbanDropResolution } from "../services/orderService";
import { formatDateTime } from "../utils/formatters";
import { resolveDraggedOrderId } from "../components/orders/ordersKanbanDrag";
export const DashboardPage = () => {
const { user, signOut } = useAuth();
const userRole = user?.role;
const [activeSection, setActiveSection] = React.useState("overview");
const [overviewTab, setOverviewTab] = React.useState("pulse");
const [ordersViewTab, setOrdersViewTab] = React.useState("registry");
const [isOrderModalOpen, setIsOrderModalOpen] = React.useState(false);
const [isOrderWorkspaceExpanded, setIsOrderWorkspaceExpanded] = React.useState(false);
const [dragOrderId, setDragOrderId] = React.useState(null);
const [dropColumnKey, setDropColumnKey] = React.useState(null);
const [kanbanNotice, setKanbanNotice] = React.useState(null);
const [kanbanMode, setKanbanMode] = React.useState("by_stage");
const [kanbanDepartmentFilter, setKanbanDepartmentFilter] = React.useState("all");
const [kanbanSort, setKanbanSort] = React.useState("updated_desc");
const [showCompletedInRegistry, setShowCompletedInRegistry] = React.useState(false);
const [showCompletedInKanban, setShowCompletedInKanban] = React.useState(false);
const [selectedDeliverySet, setSelectedDeliverySet] = React.useState(null);
const [driverFilters, setDriverFilters] = React.useState({
dateFrom: "",
dateTo: "",
city: "all",
timeSlot: "all",
viewMode: "active",
showCompleted: false,
});
const {
orders,
allOrders,
selectedOrder,
selectedOrderId,
setSelectedOrderId,
filters,
setFilters,
notifications,
pushNotification,
updateStatus,
addChatMessage,
addInternalMessage,
addOrderNote,
assignDriver,
reassignDelivery,
autoAssignLogisticians,
saveDriverRouteOrder,
metrics,
agingAlerts,
agingSummary,
deliverySetBuckets,
users,
isSupabaseBacked,
isLoading,
loadError,
} = useOrders(user);
const canManageLogistics = userRole === "logistician" || userRole === "admin";
const productionOrders = allOrders.filter((order) => PRODUCTION_STATUSES.includes(order.status));
const logisticsOrders = allOrders.filter((order) => LOGISTICS_STATUSES.includes(order.status));
const driverOrders =
userRole === "driver"
? allOrders
: allOrders.filter((order) => DRIVER_STATUSES.includes(order.status));
const selectedLogisticsOrder =
logisticsOrders.find((order) => order.id === selectedOrderId) || logisticsOrders[0] || null;
const registryOrders = React.useMemo(
() => filterRegistryOrders(orders, { includeCompleted: showCompletedInRegistry }),
[orders, showCompletedInRegistry],
);
const archiveOrders = React.useMemo(() => filterArchiveOrders(orders), [orders]);
const driverPlannedOrders = React.useMemo(
() => filterDriverDeliveries(driverOrders, driverFilters),
[driverFilters, driverOrders],
);
const todayKey = React.useMemo(() => new Date().toISOString().slice(0, 10), []);
const tomorrowKey = React.useMemo(() => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().slice(0, 10);
}, []);
const driverTodayOrders = React.useMemo(
() => driverOrders.filter((order) => getDeliveryDay(order) === todayKey),
[driverOrders, todayKey],
);
const driverTomorrowOrders = React.useMemo(
() => driverOrders.filter((order) => getDeliveryDay(order) === tomorrowKey),
[driverOrders, tomorrowKey],
);
const driverCompletedOrders = React.useMemo(
() => driverOrders.filter((order) => ["Доставлен", "Закрыт"].includes(order.status)),
[driverOrders],
);
const navItems = React.useMemo(() => {
if (userRole === "driver") {
return [
{
key: "overview",
label: "Обзор",
description: "Краткая сводка по вашим текущим и ближайшим доставкам.",
},
{
key: "deliveries",
label: "Мои доставки",
description: "План маршрута, фильтры по дням и быстрые действия по доставкам.",
badge: String(driverOrders.length),
},
];
}
const base = [
{
key: "overview",
label: "Обзор",
description: "Сводка по загрузке, событиям и проблемным точкам.",
},
{
key: "orders",
label: "Заказы",
description: "Реестр, календарь доставок, канбан, история и архив заказов.",
badge: String(allOrders.length),
},
];
if (userRole === "production_lead" || userRole === "admin") {
base.push({
key: "production",
label: "Производство",
description: "Очередь производства, приоритеты и контроль готовности.",
badge: String(productionOrders.length),
});
}
if (userRole === "logistician" || userRole === "admin") {
base.push({
key: "logistics",
label: "Логистика",
description: "Слоты доставки, чатботы и ручная обработка исключений.",
badge: String(logisticsOrders.length),
});
}
if (userRole === "admin") {
base.push({
key: "admin",
label: "Администрирование",
description: "Пользователи, аудит, ошибки интеграций и контроль доступа.",
badge: String(notifications.length),
});
}
base.push({
key: "references",
label: "Справочники",
description: "Статусы заказа, роли и правила маршрутизации по процессу.",
badge: String(ORDER_STATUSES.length),
});
return base;
}, [
allOrders.length,
driverOrders.length,
logisticsOrders.length,
notifications.length,
productionOrders.length,
userRole,
]);
React.useEffect(() => {
if (!navItems.some((item) => item.key === activeSection)) {
setActiveSection(navItems[0]?.key || "overview");
}
}, [activeSection, navItems]);
React.useEffect(() => {
setIsOrderWorkspaceExpanded(false);
}, [activeSection]);
React.useEffect(() => {
if (activeSection !== "orders") {
setOrdersViewTab("registry");
}
}, [activeSection]);
const sectionMeta = navItems.find((item) => item.key === activeSection) || navItems[0];
const openOrderModal = (orderId) => {
setSelectedOrderId(orderId);
setIsOrderModalOpen(true);
};
const handleKanbanDrop = (event, column) => {
const droppedOrderId = resolveDraggedOrderId(event, dragOrderId);
if (!droppedOrderId) {
return;
}
const draggedOrder = allOrders.find((order) => order.id === droppedOrderId);
if (!draggedOrder) {
setDragOrderId(null);
setDropColumnKey(null);
return;
}
const isSameColumn =
(kanbanMode === "by_stage" && getStatusStageKey(draggedOrder.status) === column.key) ||
(kanbanMode === "by_status" && draggedOrder.status === column.key);
if (isSameColumn) {
setDragOrderId(null);
setDropColumnKey(null);
return;
}
const { nextStatus, reason } = getKanbanDropResolution({
order: draggedOrder,
column,
role: user.role,
});
if (!nextStatus || nextStatus === draggedOrder.status) {
setKanbanNotice({
tone: "warning",
title: "Перенос недоступен",
description: `${draggedOrder.orderNumber}: ${reason || `для роли ${ROLE_LABELS[user.role]} этот переход сейчас недоступен`}`,
});
pushNotification({
id: `notification-${Date.now()}`,
type: "warning",
title: "Перенос недоступен",
description: `${draggedOrder.orderNumber}: ${reason || `для роли ${ROLE_LABELS[user.role]} этот переход сейчас недоступен`}`,
});
setDragOrderId(null);
setDropColumnKey(null);
return;
}
setKanbanNotice(null);
updateStatus(droppedOrderId, nextStatus, user.name);
setDragOrderId(null);
setDropColumnKey(null);
};
const overviewTabs = [
{ key: "pulse", label: "Пульс" },
{ key: "events", label: "События" },
{ key: "exceptions", label: "Исключения" },
];
const ordersTabs = [
{ key: "registry", label: "Реестр" },
{ key: "calendar", label: "Календарь" },
{ key: "kanban", label: "Канбан" },
{ key: "history", label: "История" },
{ key: "archive", label: "Архив" },
];
const orderHistoryFeed = allOrders
.flatMap((order) =>
order.history.map((entry) => ({
...entry,
orderNumber: order.orderNumber,
customerName: order.customer.name,
})),
)
.sort((left, right) => new Date(right.at) - new Date(left.at));
const sortedKanbanOrders = React.useMemo(() => {
const sortableOrders = [...orders];
const sorters = {
updated_desc: (left, right) => new Date(right.updatedAt) - new Date(left.updatedAt),
created_desc: (left, right) => new Date(right.createdAt) - new Date(left.createdAt),
delivery_asc: (left, right) =>
new Date(left.scheduledDelivery) - new Date(right.scheduledDelivery),
client_asc: (left, right) => left.customer.name.localeCompare(right.customer.name),
order_asc: (left, right) => left.orderNumber.localeCompare(right.orderNumber),
};
return sortableOrders.sort(sorters[kanbanSort] || sorters.updated_desc);
}, [kanbanSort, orders]);
const kanbanColumns = React.useMemo(
() =>
buildKanbanColumns(sortedKanbanOrders, {
includeCompleted: showCompletedInKanban,
mode: kanbanMode,
}),
[kanbanMode, showCompletedInKanban, sortedKanbanOrders],
);
const visibleKanbanColumns = React.useMemo(
() => filterKanbanColumnsByStage(kanbanColumns, kanbanDepartmentFilter),
[kanbanColumns, kanbanDepartmentFilter],
);
const eventFeed = React.useMemo(() => {
const agingEvents = agingAlerts.slice(0, 4).map((alert) => ({
id: `aging-${alert.order.id}`,
title:
alert.agingState === "critical"
? `Просрочен ${alert.order.orderNumber}`
: `Требует внимания ${alert.order.orderNumber}`,
description: `${alert.order.customer.name}: ${alert.order.status} · ${alert.statusAgeLabel}`,
}));
return [...agingEvents, ...notifications].slice(0, 8);
}, [agingAlerts, notifications]);
if (!user) {
return Выберите заказ из таблицы.
{order.customer.name} · {order.customer.address}
1. Откройте «Мои доставки» и отфильтруйте день, город и половину дня.
2. Перетащите карточки внутри дня, чтобы выстроить удобный порядок точек.
3. Откройте карточку доставки и отметьте: загружен, в пути, доставлен или проблема.
Смотрите на заказы, которые слишком долго находятся в одном статусе.
Основная таблица для ежедневной работы. Данные сюда поступают только из 1С.
Канбан показывает только отфильтрованные заказы. Можно переключать вид по этапам и по статусам, а цвет карточки показывает, чья сейчас зона ответственности.
Лента показывает, кто и по какому заказу менял статус или выполнял действие.
Завершённые заказы вынесены отдельно, чтобы не перегружать реестр и канбан.
Единый интеграционный слой сохраняет входящие и исходящие события в историю заказа, а ответы клиента автоматически меняют статус согласования доставки.
СМС и электронная почта: быстрый старт для первого подтверждения доставки.
ВКонтакте: обратные вызовы и кнопки с привязкой к идентификатору заказа.
Макс: преобразование входящих событий в единую модель чата.
Отдельные заказы из наборов доставки, где нужно согласование, перенос или ручная реакция на исключения.
Только нужные водителю данные: адрес, клиент, состав заказа и быстрые действия.
Просмотр без потери контекста списка. При необходимости можно раскрыть в рабочую область.