supersam/src/components/orders/OrderDetailPanel.jsx

240 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from "react";
import { getAvailableTransitionsByRole, getDeliveryAgreementComment, getOrderStatusComment, getStatusTone } from "../../constants/deliveryWorkflow";
import { demoUsers } from "../../data/mockAppData";
import { formatDateTime } from "../../utils/formatters";
import { Badge } from "../UI/Badge";
import { Button } from "../UI/Button";
import { Panel } from "../UI/Panel";
const getUsers = (users) => (Array.isArray(users) && users.length ? users : demoUsers);
const resolveUserName = (users, userId) => getUsers(users).find((user) => user.id === userId)?.name || "Не назначен";
const splitItem = (item) => {
if (!item) {
return { name: "Позиция", quantity: "" };
}
if (typeof item === "string") {
const [name, quantity] = item.split("|").map((part) => part.trim());
return {
name: name || item,
quantity: quantity || "",
};
}
if (typeof item === "object") {
return {
name: item.name || item.label || "Позиция",
quantity: typeof item.quantity === "number" ? String(item.quantity) : item.quantity || "",
};
}
return { name: "Позиция", quantity: "" };
};
export const OrderDetailPanel = ({ order, users, currentUser, onStatusChange, onAssignDriver }) => {
if (!order) {
return (
<Panel className="flex min-h-[460px] items-center justify-center">
<p className="text-sm text-[var(--color-text-muted)]">Выберите заказ для просмотра деталей.</p>
</Panel>
);
}
const orderItems = Array.isArray(order.items) ? order.items.map(splitItem) : [];
const orderHistory = Array.isArray(order.history) ? order.history : [];
const role = currentUser?.role;
const availableTransitions = role ? getAvailableTransitionsByRole({ status: order.status, role }) : [];
const drivers = (Array.isArray(users) && users.length ? users : demoUsers).filter((u) => u.role === "driver");
const canAssignDriver = role === "logistician" || role === "admin";
return (
<div className="space-y-5">
<Panel className="space-y-5 p-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<p className="text-sm uppercase tracking-[0.2em] text-[var(--color-text-muted)]">Карточка заказа</p>
<h2 className="mt-2 text-2xl font-semibold">{order.orderNumber}</h2>
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
{order.customer.name} · {order.customer.address}
</p>
</div>
<Badge tone={getStatusTone(order.status)}>{order.status}</Badge>
</div>
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
{getOrderStatusComment(order.status)}
</p>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div>
<p className="text-xs text-[var(--color-text-muted)]">Менеджер</p>
<p className="mt-1 font-medium">{resolveUserName(users, order.managerId)}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Логист</p>
<p className="mt-1 font-medium">{resolveUserName(users, order.logisticianIds?.[0])}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Водитель</p>
<p className="mt-1 font-medium">{resolveUserName(users, order.assignedDriverId)}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Дата создания</p>
<p className="mt-1 font-medium">{formatDateTime(order.createdAt)}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">План доставки</p>
<p className="mt-1 font-medium">{formatDateTime(order.scheduledDelivery)}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Канал связи</p>
<p className="mt-1 font-medium">{order.customer.messenger}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Согласование доставки</p>
<p className="mt-1 font-medium">{order.deliveryAgreementStatus}</p>
</div>
</div>
</Panel>
<Panel className="space-y-4 p-5">
<div className="flex flex-wrap items-center justify-between gap-3">
<strong>Данные клиента</strong>
<Badge tone={getStatusTone(order.status)}>{order.status}</Badge>
</div>
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
{getDeliveryAgreementComment(order.deliveryAgreementStatus)}
</p>
<div className="grid gap-4 md:grid-cols-2">
<div>
<p className="text-xs text-[var(--color-text-muted)]">Клиент</p>
<p className="mt-1 font-medium">{order.customer.name}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Телефон</p>
<p className="mt-1 font-medium">{order.customer.phone}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Адрес</p>
<p className="mt-1 font-medium">{order.customer.address}</p>
</div>
<div>
<p className="text-xs text-[var(--color-text-muted)]">Дата доставки</p>
<p className="mt-1 font-medium">{formatDateTime(order.scheduledDelivery)}</p>
</div>
</div>
</Panel>
<Panel className="space-y-4 p-5">
<strong>Состав заказа</strong>
<div className="space-y-3">
{orderItems.length ? (
orderItems.map((item) => (
<div
key={`${item.name}-${item.quantity || "item"}`}
className="flex items-center justify-between gap-3 rounded-[20px] border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3 text-sm"
>
<span>{item.name}</span>
{item.quantity ? <Badge tone="neutral">{item.quantity}</Badge> : null}
</div>
))
) : (
<p className="text-sm text-[var(--color-text-muted)]">Состав заказа не указан.</p>
)}
</div>
</Panel>
{order.orderNotes?.length ? (
<Panel className="space-y-3 p-5">
<strong>Комментарии</strong>
<div className="space-y-2">
{order.orderNotes.map((note) => (
<div
key={note.id}
className="rounded-[20px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4 text-sm leading-6"
>
{note.text}
</div>
))}
</div>
</Panel>
) : null}
{order.comments?.length ? (
<Panel className="space-y-3 p-5">
<strong>Дополнительные комментарии</strong>
<div className="space-y-2 text-sm leading-6 text-[var(--color-text-muted)]">
{order.comments.map((comment, index) => (
<div key={`${comment}-${index}`} className="rounded-[20px] bg-[var(--color-surface)] p-4">
{comment}
</div>
))}
</div>
</Panel>
) : null}
{availableTransitions.length ? (
<Panel className="space-y-4 p-5">
<strong>Действия</strong>
<div className="flex flex-wrap gap-2">
{availableTransitions.map((status) => (
<Button
key={status}
variant={status === "Проблема доставки" || status === "Платное хранение" || status === "Отменён" ? "ghost" : "secondary"}
onClick={() => onStatusChange?.(status)}
>
{status}
</Button>
))}
</div>
</Panel>
) : null}
{canAssignDriver ? (
<Panel className="space-y-4 p-5">
<strong>Назначить водителя</strong>
<div className="flex flex-wrap gap-2">
{drivers.map((driver) => (
<Button
key={driver.id}
variant={order.assignedDriverId === driver.id ? "primary" : "secondary"}
onClick={() => onAssignDriver?.({ orderId: order.id, driverId: driver.id, actorName: currentUser.name })}
>
{driver.name}
</Button>
))}
{order.assignedDriverId ? (
<Button variant="ghost" onClick={() => onAssignDriver?.({ orderId: order.id, driverId: null, actorName: currentUser.name })}>
Снять водителя
</Button>
) : null}
</div>
</Panel>
) : null}
{orderHistory.length ? (
<Panel className="space-y-3 p-5">
<strong>История</strong>
<div className="space-y-2">
{orderHistory.map((entry) => (
<div
key={entry.id}
className="rounded-[20px] border border-[var(--color-border)] bg-[var(--color-surface)] p-4 text-sm leading-6"
>
<div className="flex flex-wrap items-center justify-between gap-3">
<span className="font-medium">{entry.action}</span>
<span className="text-[var(--color-text-muted)]">{formatDateTime(entry.at)}</span>
</div>
<div className="mt-2 text-[var(--color-text-muted)]">
{entry.oldStatus || "Начало"} {entry.newStatus}
</div>
</div>
))}
</div>
</Panel>
) : null}
</div>
);
};