202 lines
7.9 KiB
JavaScript
202 lines
7.9 KiB
JavaScript
import React, { useState } from "react";
|
||
import { getAvailableTransitionsByRole, getOrderStatusComment, getStatusTone } from "../../constants/deliveryWorkflow";
|
||
import { getDeliveryCity, getDeliveryDay, getDeliveryHalfDay } from "../../services/driverDeliveries";
|
||
import { Badge } from "../UI/Badge";
|
||
import { Button } from "../UI/Button";
|
||
import { Panel } from "../UI/Panel";
|
||
|
||
const PROBLEM_REASONS = [
|
||
{ value: "client_absent", label: "Клиент не принял", description: "Клиент отказался или не вышел на связь" },
|
||
{ value: "damage", label: "Повреждение заказа", description: "Товар повреждён при транспортировке" },
|
||
{ value: "wrong_address", label: "Неверный адрес", description: "Адрес доставки указан неверно" },
|
||
{ value: "other", label: "Другое", description: "Иная причина проблемы доставки" },
|
||
];
|
||
|
||
const ProblemReasonModal = ({ onSelect, onCancel }) => (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={onCancel}>
|
||
<Panel className="mx-4 w-full max-w-md space-y-4 p-6" onClick={(e) => e.stopPropagation()}>
|
||
<h3 className="text-lg font-semibold">Причина проблемы</h3>
|
||
<p className="text-sm text-[var(--color-text-muted)]">Укажите причину возникшей проблемы с доставкой.</p>
|
||
<div className="space-y-2">
|
||
{PROBLEM_REASONS.map((reason) => (
|
||
<Button
|
||
key={reason.value}
|
||
variant="secondary"
|
||
className="w-full rounded-[16px] p-3 text-left"
|
||
onClick={() => onSelect(reason.value, reason.label)}
|
||
>
|
||
<span className="font-medium">{reason.label}</span>
|
||
<p className="mt-0.5 text-xs text-[var(--color-text-muted)]">{reason.description}</p>
|
||
</Button>
|
||
))}
|
||
</div>
|
||
<div className="flex justify-end">
|
||
<Button variant="ghost" onClick={onCancel}>Отмена</Button>
|
||
</div>
|
||
</Panel>
|
||
</div>
|
||
);
|
||
|
||
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 DriverDeliveryDetail = ({ order, onStatusChange }) => {
|
||
const [showProblemModal, setShowProblemModal] = useState(false);
|
||
|
||
if (!order) {
|
||
return null;
|
||
}
|
||
|
||
const availableTransitions = getAvailableTransitionsByRole({
|
||
status: order.status,
|
||
role: "driver",
|
||
});
|
||
const orderItems = Array.isArray(order.items) ? order.items.map(splitItem) : [];
|
||
|
||
const currentStatus = order.status;
|
||
|
||
let actionButtons = [];
|
||
if (currentStatus === "Назначен водитель") {
|
||
actionButtons = [
|
||
{ value: "Доставлен", label: "Доставлено" },
|
||
{ value: "Проблема доставки", label: "Проблема" },
|
||
];
|
||
} else if (currentStatus === "Доставлен") {
|
||
actionButtons = [
|
||
{ value: "Назначен водитель", label: "Вернуть в работу" },
|
||
];
|
||
} else if (currentStatus === "Проблема доставки" || currentStatus === "Закрыт" || currentStatus === "Отменён") {
|
||
actionButtons = [];
|
||
} else {
|
||
actionButtons = availableTransitions.map((status) => ({
|
||
value: status,
|
||
label: status === "Проблема доставки" ? "Проблема" : status,
|
||
}));
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4 fs-zone-card">
|
||
{showProblemModal && (
|
||
<ProblemReasonModal
|
||
onSelect={(reasonValue, reasonLabel) => {
|
||
setShowProblemModal(false);
|
||
onStatusChange?.("Проблема доставки", { reason: reasonValue, reasonLabel });
|
||
}}
|
||
onCancel={() => setShowProblemModal(false)}
|
||
/>
|
||
)}
|
||
|
||
<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.customer.address}</h2>
|
||
<p className="mt-2 text-sm text-[var(--color-text-muted)]">
|
||
{order.orderNumber} · {order.customer.name}
|
||
</p>
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Badge tone={getStatusTone(order.status)}>{order.status}</Badge>
|
||
</div>
|
||
</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">{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">{getDeliveryCity(order)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-xs text-[var(--color-text-muted)]">Интервал доставки</p>
|
||
<p className="mt-1 font-medium">
|
||
{getDeliveryDay(order)} · {getDeliveryHalfDay(order)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Panel>
|
||
|
||
<Panel className="space-y-4 p-6">
|
||
<h3 className="text-lg font-semibold">Что везти</h3>
|
||
<div className="space-y-3">
|
||
{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>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
|
||
<Panel className="space-y-4 p-6">
|
||
<h3 className="text-lg font-semibold">Комментарии для доставки</h3>
|
||
<div className="space-y-3 text-sm text-[var(--color-text)]">
|
||
<div className="rounded-[20px] bg-[var(--color-surface)] p-4">
|
||
{order.orderNotes?.[0]?.text || "Дополнительных комментариев нет."}
|
||
</div>
|
||
{order.comments?.length ? (
|
||
<div className="rounded-[20px] bg-[var(--color-surface)] p-4">
|
||
{order.comments.join(". ")}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
</Panel>
|
||
|
||
{actionButtons.length > 0 && (
|
||
<Panel className="space-y-4 p-6">
|
||
<h3 className="text-lg font-semibold">Быстрые действия</h3>
|
||
<div className="flex flex-wrap gap-2">
|
||
{actionButtons.map((btn) => (
|
||
<Button
|
||
key={btn.value}
|
||
variant={btn.value === "Проблема доставки" ? "ghost" : "secondary"}
|
||
onClick={() => {
|
||
if (btn.value === "Проблема доставки") {
|
||
setShowProblemModal(true);
|
||
return;
|
||
}
|
||
onStatusChange?.(btn.value);
|
||
}}
|
||
>
|
||
{btn.label}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
)}
|
||
</div>
|
||
);
|
||
}; |