fix: StatusActionPanel smart hints + driver name lookup + logistician can edit shipment + completed label

1. StatusActionPanel: all buttons clickable (no more manual:false blocking)
   - Click on current status shows actual state: "Водитель уже назначен: Иван"
   - "Назначен водитель" clickable even if already assigned
   - "Согласовано" clickable even if already agreed
2. Status indicators: green badges ✓ Дата согласована / ✓ Водитель: имя
   + warning badges if missing
3. DriverAssignmentPanel: look up driver name from drivers list if
   assignedDriverName is empty (Supabase join failed)
4. DriverShipmentPanel: now visible to logistician/admin too — can edit
   shipment data (fix wrong driver checkboxes)
5. SmsStatusCard: add completed label ("Завершено")
This commit is contained in:
root 2026-07-13 13:16:48 +00:00
parent 17f93a65ae
commit 755385cf0e
4 changed files with 79 additions and 50 deletions

View File

@ -122,7 +122,7 @@ const DriverAssignmentPanel = ({
Водитель назначен
</p>
<p className="mt-1 text-lg font-semibold">
{order.assignedDriverName || "Водитель назначен"}
{order.assignedDriverName || drivers.find((d) => d.id === order.assignedDriverId)?.name || "Водитель назначен"}
</p>
</div>
<Badge tone="accent">Назначен</Badge>

View File

@ -1110,7 +1110,7 @@ export const OrderDetailPanel = ({
/>
) : null}
{userRole === "driver" && order ? (
{["driver", "logistician", "admin", "mega_admin"].includes(userRole) && order ? (
<DriverShipmentPanel
order={order}
onShipmentChange={handleShipmentChange}

View File

@ -23,6 +23,7 @@ const NOTIF_LABELS = {
paid_storage_sent: "Платное хранение: отправлено",
draft: "Черновик",
confirmed: "Клиент согласовал дату",
completed: "Завершено",
};
const NOTIF_TONES = {
@ -38,6 +39,7 @@ const NOTIF_TONES = {
paid_storage_sent: "accent",
draft: "neutral",
confirmed: "accent",
completed: "neutral",
};
// Helpers

View File

@ -4,8 +4,6 @@ import { Button } from "../UI/Button";
import { Panel } from "../UI/Panel";
import { DELIVERY_GROUP_STATUS_LABELS } from "../../services/orderGroupViews";
const STATUS_LABELS = DELIVERY_GROUP_STATUS_LABELS;
const StatusActionPanel = ({
order,
userRole,
@ -20,32 +18,45 @@ const StatusActionPanel = ({
const currentStatus = order.deliveryStatus || order.delivery_status;
const isPickup = (order.deliveryType || order.delivery_type) === "pickup" || currentStatus === "pickup";
// Check if delivery date and half-day are set
// Check delivery schedule
const hasDeliveryDate = !!(order.deliveryDate || order.customerDate);
const hasDeliveryHalfDay = !!(order.deliveryTime || order.deliveryHalfDay || order.delivery_time || order.delivery_half_day);
const hasDeliverySchedule = hasDeliveryDate && hasDeliveryHalfDay;
const hasDriver = !!order.assignedDriverId;
// Smart hints: show actual state instead of generic "do X"
const getHint = (statusValue) => {
if (statusValue === "agreed") {
if (hasDeliverySchedule) return "Дата доставки уже согласована";
return "Согласуйте дату доставки выше";
}
if (statusValue === "driver_assigned") {
if (hasDriver) return `Водитель уже назначен: ${order.assignedDriverName || ""}`.trim();
return "Назначьте водителя из списка выше";
}
return "";
};
// Build status options no "Загружено", "Доставлено"/"Вывезено" require date+half-day
const allStatuses = isPickup
? [
{ value: "pending_confirmation", label: "Ожидает согласования", manual: true },
{ value: "agreed", label: "Согласовано", manual: false, hint: "Согласуйте дату доставки выше" },
{ value: "driver_assigned", label: "Назначен водитель", manual: false, hint: "Назначьте водителя из списка" },
{ value: "picked_up", label: "Вывезено", manual: true, primary: true, requiresSchedule: true },
{ value: "delivered", label: "Доставлено", manual: true, mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес", manual: true },
{ value: "problem", label: "Проблема", manual: true },
{ value: "cancelled", label: "Отменено", manual: true },
{ value: "pending_confirmation", label: "Ожидает согласования" },
{ value: "agreed", label: "Согласовано" },
{ value: "driver_assigned", label: "Назначен водитель" },
{ value: "picked_up", label: "Вывезено", primary: true, requiresSchedule: true },
{ value: "delivered", label: "Доставлено", mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес" },
{ value: "problem", label: "Проблема" },
{ value: "cancelled", label: "Отменено" },
]
: [
{ value: "pending_confirmation", label: "Ожидает согласования", manual: true },
{ value: "agreed", label: "Согласовано", manual: false, hint: "Согласуйте дату доставки выше" },
{ value: "driver_assigned", label: "Назначен водитель", manual: false, hint: "Назначьте водителя из списка" },
{ value: "delivered", label: "Доставлено", manual: true, primary: true, requiresSchedule: true },
{ value: "picked_up", label: "Вывезено", manual: true, mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес", manual: true },
{ value: "problem", label: "Проблема", manual: true },
{ value: "cancelled", label: "Отменено", manual: true },
{ value: "pending_confirmation", label: "Ожидает согласования" },
{ value: "agreed", label: "Согласовано" },
{ value: "driver_assigned", label: "Назначен водитель" },
{ value: "delivered", label: "Доставлено", primary: true, requiresSchedule: true },
{ value: "picked_up", label: "Вывезено", mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес" },
{ value: "problem", label: "Проблема" },
{ value: "cancelled", label: "Отменено" },
];
return (
@ -56,41 +67,57 @@ const StatusActionPanel = ({
Измените статус, если водитель забыл обновить или нужна корректировка.
</p>
</div>
{/* Status indicators: show current state clearly */}
<div className="flex flex-wrap gap-2">
{hasDeliverySchedule && (
<Badge tone="accent"> Дата согласована</Badge>
)}
{hasDriver && (
<Badge tone="accent"> Водитель: {order.assignedDriverName || "назначен"}</Badge>
)}
{!hasDeliverySchedule && (
<Badge tone="warning"> Дата не указана</Badge>
)}
{!hasDriver && (
<Badge tone="warning"> Водитель не назначен</Badge>
)}
</div>
<div className="flex flex-wrap gap-2">
{allStatuses.map((statusOption) => {
const isCurrent = currentStatus === statusOption.value;
const isClickable = (statusOption.manual !== false || currentStatus === 'problem') && !isCurrent;
const blockedBySchedule = statusOption.requiresSchedule && !hasDeliverySchedule;
const hint = getHint(statusOption.value);
return (
<div key={statusOption.value} className="relative group">
<Button
variant={isCurrent ? "primary" : (statusOption.mismatch ? "ghost" : "secondary")}
onClick={() => {
if (!isClickable) {
onConfirmStatus?.({ type: "hint", hint: statusOption.hint || "" });
return;
}
if (blockedBySchedule) {
onConfirmStatus?.({
type: "hint",
hint: "⚠ Нельзя поставить «" + statusOption.label + "» без даты и времени доставки. Сначала согласуйте дату выше.",
});
return;
}
<Button
key={statusOption.value}
variant={isCurrent ? "primary" : (statusOption.mismatch ? "ghost" : "secondary")}
onClick={() => {
if (blockedBySchedule) {
onConfirmStatus?.({
type: "status",
status: statusOption.value,
label: statusOption.label,
mismatch: !!statusOption.mismatch,
deliveryType: isPickup ? "pickup" : "delivery",
type: "hint",
hint: "⚠ Нельзя поставить «" + statusOption.label + "» без даты и времени доставки. Сначала согласуйте дату выше.",
});
}}
disabled={isSavingStatusChange}
className={blockedBySchedule ? "opacity-40 cursor-not-allowed" : ""}
>
{statusOption.label}
</Button>
</div>
return;
}
if (isCurrent && hint) {
onConfirmStatus?.({ type: "hint", hint });
return;
}
onConfirmStatus?.({
type: "status",
status: statusOption.value,
label: statusOption.label,
mismatch: !!statusOption.mismatch,
deliveryType: isPickup ? "pickup" : "delivery",
});
}}
disabled={isSavingStatusChange}
className={blockedBySchedule ? "opacity-40 cursor-not-allowed" : ""}
>
{statusOption.label}
</Button>
);
})}
</div>