fix: logistics UI — problem badge overlap, driver name, SMS labels, shipment report

1. LogisticsReadinessBoard: problem badge on separate line (no overlap)
2. DriverAssignmentPanel: show "Водитель назначен" instead of "Неизвестно"
   when driver name not resolved from join
3. SmsStatusCard: "confirmed" label = "Клиент согласовал дату" (not "Согласовано")
4. StatusActionPanel: when currentStatus=problem, all status buttons clickable
   (logistician can resolve problem by setting any status)
5. DriverShipmentReport: show ALL items — green check for delivered,
   red cross for not delivered, with reason comments
This commit is contained in:
root 2026-07-13 13:07:03 +00:00
parent 1ffd8bacc5
commit 17f93a65ae
5 changed files with 38 additions and 20 deletions

View File

@ -158,12 +158,12 @@ const renderRow = (group, onSelectSet) => (
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]"></span>}
</div>
<div className="px-3 py-1.5">
<div className="flex items-center gap-1.5">
<div className="flex flex-col gap-1">
<Badge tone={getOrderGroupStatusTone(group)}>{getOrderGroupDisplayStatusLabel(group)}</Badge>
{(group.hasDeliveryProblem || group.has_delivery_problem) && (
<span
title={group.deliveryProblemNote || group.delivery_problem_note || "Есть проблемы с отгрузкой позиций"}
className="inline-flex items-center gap-0.5 rounded-full bg-[rgba(239,68,68,0.12)] px-1.5 py-0.5 text-[10px] font-bold text-[var(--color-danger)]"
className="inline-flex w-fit items-center gap-0.5 rounded-full bg-[rgba(239,68,68,0.12)] px-1.5 py-0.5 text-[10px] font-bold text-[var(--color-danger)]"
>
Проблема
</span>

View File

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

View File

@ -2,42 +2,60 @@
const DriverShipmentReport = ({ shipmentData }) => {
if (!Array.isArray(shipmentData) || shipmentData.length === 0) return null;
const deliveredItems = shipmentData.filter((i) => i.shipped);
const notDeliveredItems = shipmentData.filter((i) => !i.shipped);
return (
<Panel className="space-y-4 p-5 border-[var(--color-warning)]">
<Panel className="space-y-4 p-5">
<div className="flex items-center gap-2">
<svg className="h-5 w-5 text-[var(--color-warning)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<strong className="text-[var(--color-warning)]">Проблемы с доставкой позиций</strong>
<span className="text-lg">📋</span>
<strong>Отчёт об отгрузке</strong>
<Badge tone={notDeliveredItems.length > 0 ? "warning" : "accent"}>
{deliveredItems.length}/{shipmentData.length} доставлено
</Badge>
</div>
<p className="text-sm text-[var(--color-text-muted)]">
Не доставлено {shipmentData.length} {shipmentData.length === 1 ? "позиция" : shipmentData.length < 5 ? "позиции" : "позиций"}. Остальное доставлено.
</p>
{notDeliveredItems.length > 0 && (
<div className="rounded-xl border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-3 py-2 text-sm text-[var(--color-warning)]">
Не доставлено {notDeliveredItems.length} {notDeliveredItems.length === 1 ? "позиция" : notDeliveredItems.length < 5 ? "позиции" : "позиций"}
</div>
)}
<div className="space-y-2">
{shipmentData.map((item) => (
<div
key={item.id || item.name}
className="rounded-[18px] border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-4 py-3 text-sm"
className={[
"rounded-[18px] border px-4 py-3 text-sm",
item.shipped
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)]"
: "border-[var(--color-warning)] bg-[var(--color-warning-soft)]"
].join(" ")}
>
<div className="flex items-center justify-between gap-2">
<span className="text-[var(--color-text)]">{item.name}</span>
<div className="flex items-center gap-2">
<span className={item.shipped ? "text-[var(--color-accent)]" : "text-[var(--color-warning)]"}>
{item.shipped ? "✓" : "✗"}
</span>
<span className={item.shipped ? "text-[var(--color-text-muted)] line-through" : "text-[var(--color-text)] font-medium"}>
{item.name}
</span>
</div>
{item.quantity || item.unit ? (
<Badge tone="neutral">{[item.quantity, item.unit].filter(Boolean).join(" ")}</Badge>
) : null}
</div>
{item.comment ? (
<p className="mt-1 text-xs text-[var(--color-text-muted)]">Причина: {item.comment}</p>
) : (
<p className="mt-1 text-xs text-[var(--color-text-muted)] italic">Причина не указана</p>
{!item.shipped && item.comment && (
<p className="mt-1 ml-6 text-xs text-[var(--color-warning)]">Причина: {item.comment}</p>
)}
</div>
))}
</div>
</Panel>
);
};
import React from "react";
import { formatDateTime } from "../../utils/formatters";
import { Badge } from "../UI/Badge";

View File

@ -22,7 +22,7 @@ const NOTIF_LABELS = {
paid_storage_sending: "Отправляется…",
paid_storage_sent: "Платное хранение: отправлено",
draft: "Черновик",
confirmed: "Согласовано",
confirmed: "Клиент согласовал дату",
};
const NOTIF_TONES = {

View File

@ -59,7 +59,7 @@ const StatusActionPanel = ({
<div className="flex flex-wrap gap-2">
{allStatuses.map((statusOption) => {
const isCurrent = currentStatus === statusOption.value;
const isClickable = statusOption.manual !== false && !isCurrent;
const isClickable = (statusOption.manual !== false || currentStatus === 'problem') && !isCurrent;
const blockedBySchedule = statusOption.requiresSchedule && !hasDeliverySchedule;
return (
<div key={statusOption.value} className="relative group">