const DriverShipmentReport = ({ shipmentData }) => {
if (!Array.isArray(shipmentData) || shipmentData.length === 0) return null;
return (
Проблемы с доставкой позиций
Не доставлено {shipmentData.length} {shipmentData.length === 1 ? "позиция" : shipmentData.length < 5 ? "позиции" : "позиций"}. Остальное — доставлено.
{shipmentData.map((item) => (
{item.name}
{item.quantity || item.unit ? (
{[item.quantity, item.unit].filter(Boolean).join(" ")}
) : null}
{item.comment ? (
Причина: {item.comment}
) : (
Причина не указана
)}
))}
);
};
import React from "react";
import { formatDateTime } from "../../utils/formatters";
import { Badge } from "../UI/Badge";
import { Button } from "../UI/Button";
import { Panel } from "../UI/Panel";
import { DriverShipmentPanel } from "../driver/DriverShipmentPanel";
import { CalendarWidget } from "./CalendarWidget";
import { StatusActionPanel } from "./StatusActionPanel";
import { DriverAssignmentPanel } from "./DriverAssignmentPanel";
import { matchesStopWord, useStopWords } from "../../hooks/useStopWords";
import {
getOrderGroupDeliveryStatusLabel,
getOrderGroupDisplayStatusLabel,
getOrderGroupStatusTone,
DELIVERY_GROUP_STATUS_LABELS,
} from "../../services/orderGroupViews";
import { getErrorMessage, normalizeNom } from "../../utils/deliveryUtils";
const DELIVERY_TIME_OPTIONS = ["Первая половина дня", "Вторая половина дня"];
const STATUS_LABELS = DELIVERY_GROUP_STATUS_LABELS;
const ConfirmModal = ({ open, title, message, onConfirm, onCancel }) => {
if (!open) return null;
return (
e.stopPropagation()}>
{title &&
{title}
}
{message &&
{message}
}
);
};
const DELIVERY_TIME_ALIASES = {
"До обеда": "Первая половина дня",
"После обеда": "Вторая половина дня",
};
const CollapsibleChips = ({ label, items }) => {
const [open, setOpen] = React.useState(false);
if (!Array.isArray(items) || items.length === 0) return null;
return (
{open && (
{items.map((item, idx) => (
{ navigator.clipboard?.writeText(item); }}
>{item}
))}
)}
);
};
const renderList = (values) => {
if (!Array.isArray(values) || !values.length) {
return Нет данных
;
}
return (
{values.map((value, index) => (
{value}
))}
);
};
const renderValue = (value) => value || "Нет данных";
const getAllBillNumbers = (order) => {
const orders = parseOrderList(order);
if (!orders.length) return order.orderNumbers || [];
return orders.map((o) => o.nom || o.name || '').filter(Boolean);
};
const parseOrderList = (order) => {
if (!order) return [];
// Try orderList first (Supabase JSONB array of positions)
if (order.orderList) {
let parsed = order.orderList;
if (typeof parsed === 'string') {
try { parsed = JSON.parse(parsed); } catch { /* ignore */ }
}
if (Array.isArray(parsed)) return parsed;
}
// Fallback: orderListStructured (JSONB with { orders: [...] })
if (order.orderListStructured) {
let parsed = order.orderListStructured;
if (typeof parsed === 'string') {
try { parsed = JSON.parse(parsed); } catch { /* ignore */ }
}
if (parsed && Array.isArray(parsed.orders)) return parsed.orders;
}
// Fallback: sourceOrders (1C exchange data)
// 1C sends the FULL order composition (main + associated bills) in EVERY source order's orderList.
// We must deduplicate by nom to avoid showing the same items multiple times.
if (order.sourceOrders) {
let parsed = order.sourceOrders;
if (typeof parsed === 'string') {
try { parsed = JSON.parse(parsed); } catch { /* ignore */ }
}
if (Array.isArray(parsed) && parsed.length > 0) {
const seen = new Set();
const allItems = [];
for (const src of parsed) {
if (src && Array.isArray(src.orderList)) {
for (const ol of src.orderList) {
if (ol && (ol.items || ol.nom || ol.name)) {
const normalizedNom = normalizeNom(ol.nom || ol.name || '');
// Deduplicate by nom — 1C repeats same orderList in every source order
if (seen.has(normalizedNom)) continue;
seen.add(normalizedNom);
allItems.push(ol);
}
}
}
}
if (allItems.length > 0) return allItems;
// Legacy: return whole array if no orderList structure
if (parsed[0].orderList && Array.isArray(parsed[0].orderList)) {
return parsed[0].orderList;
}
return parsed;
}
}
return [];
};
const normalizeDeliveryTimeChoice = (value) => {
const normalized = value ? String(value).trim() : "";
const deliveryTime = DELIVERY_TIME_ALIASES[normalized] || normalized;
return DELIVERY_TIME_OPTIONS.includes(deliveryTime) ? deliveryTime : DELIVERY_TIME_OPTIONS[0];
};
const toDateKey = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const fromDateKey = (value) => {
const normalized = normalizeDateForInput(value);
if (!normalized) {
return null;
}
const [year, month, day] = normalized.split("-").map(Number);
return new Date(year, month - 1, day);
};
const addDays = (date, amount) => {
const nextDate = new Date(date);
nextDate.setDate(nextDate.getDate() + amount);
return nextDate;
};
const isWeekendDate = (date) => {
const day = date.getDay();
return day === 0 || day === 6;
};
export const getNextSelectableDateKey = (referenceDate = new Date()) => {
let current = addDays(referenceDate, 1);
while (isWeekendDate(current)) {
current = addDays(current, 1);
}
return toDateKey(current);
};
const normalizePhoneForTel = (phone) => {
const cleaned = String(phone || "").trim();
if (!cleaned) return "";
if (cleaned.startsWith("+7")) return cleaned;
if (cleaned.startsWith("8")) return "+7" + cleaned.slice(1);
return "+7" + cleaned;
};
const isFutureDeliveryDate = (value) => {
const parsedDate = fromDateKey(value);
if (!parsedDate) {
return false;
}
return !isWeekendDate(parsedDate) && toDateKey(parsedDate) >= getNextSelectableDateKey();
};
const formatDateForDisplay = (value) => {
if (!value) {
return "Выберите дату";
}
const [year, month, day] = value.split("-").map(Number);
if (!year || !month || !day) {
return value;
}
return new Date(year, month - 1, day).toLocaleDateString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
};
const formatDeliveryDateDisplay = (value) => {
const normalized = normalizeDateForInput(value);
if (!normalized) {
return renderValue(value);
}
return formatDateForDisplay(normalized);
};
const startOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1);
const addMonths = (date, amount) => new Date(date.getFullYear(), date.getMonth() + amount, 1);
const buildCalendarDays = (currentMonth) => {
const firstDay = startOfMonth(currentMonth);
const lastDay = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0);
const firstWeekDay = (firstDay.getDay() + 6) % 7;
const totalDays = lastDay.getDate();
const cells = [];
for (let index = 0; index < firstWeekDay; index += 1) {
cells.push(null);
}
for (let day = 1; day <= totalDays; day += 1) {
cells.push(new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day));
}
while (cells.length % 7 !== 0) {
cells.push(null);
}
return cells;
};
const normalizeDateForInput = (value) => {
if (!value) {
return "";
}
const normalized = String(value).trim();
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
return normalized;
}
const shortDateMatch = normalized.match(/^(\d{2})\.(\d{2})\.(\d{2})$/);
if (shortDateMatch) {
const [, day, month, year] = shortDateMatch;
return `20${year}-${month}-${day}`;
}
return "";
};
const CollapsibleOrderComposition = ({ order }) => {
const [isExpanded, setIsExpanded] = React.useState(false);
const { stopWords, active } = useStopWords();
const orders = parseOrderList(order);
const allPositions = orders.reduce((sum, o) => sum + (o.items?.length || 0), 0);
const filteredPositions = active ? orders.reduce((sum, o) => {
if (!o.items) return sum;
return sum + o.items.filter((item) => {
const name = String(item.product_name || item.name || item.title || "");
return !matchesStopWord(name, stopWords);
}).length;
}, 0) : allPositions;
return (
{isExpanded && (
{!orders.length ? (
Позиции не указаны
) : (
orders.map((orderItem, idx) => (
{orderItem.nom || orderItem.name || `Заказ ${idx + 1}`}
{(() => {
const filtered = (orderItem.items || []).filter((item) => {
const name = String(item.product_name || item.name || item.title || "");
return active ? !matchesStopWord(name, stopWords) : true;
});
if (filtered.length === 0 && active && (orderItem.items || []).length > 0) {
return
Только услуги — скрыты стоп-словами
;
}
if (filtered.length === 0) {
return
Позиции не указаны
;
}
return (
{filtered.map((item, itemIdx) => (
{item.product_name || item.name || item.title || ''}
{item.product_quantity || item.quantity || item.count || item.amount || ""} {item.product_ed || item.unit || ""}
))}
);
})()}
))
)}
)}
);
};
const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingStatusChange, setFormMessage }) => {
const [showConfirm, setShowConfirm] = React.useState(false);
const isPaidStorage = (order.deliveryStatus || order.delivery_status) === "paid_storage";
if (isPaidStorage) {
return (
Платное хранение
{order.paidStorageAt && (
Переведено: {formatDateTime(order.paidStorageAt)}
)}
);
}
return (
Платное хранение
Переведите заказ в статус платного хранения, если клиент не забрал товар в срок.
{showConfirm ? (
Перевести заказ в платное хранение? Клиент получит уведомление.
) : (
)}
);
};
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 }) => (
e.stopPropagation()}>
Причина проблемы
Укажите причину возникшей проблемы с доставкой.
{PROBLEM_REASONS.map((reason) => (
))}
);
export const OrderDetailPanel = ({
order,
canManageDelivery = false,
onSaveManualDeliveryChoice,
isSavingDeliveryChoice = false,
isSavingDriverAssignment = false,
isSavingStatusChange = false,
drivers = [],
onAssignDriver,
onChangeDeliveryStatus,
userRole,
}) => {
const [problemReason, setProblemReason] = React.useState(null);
const [pendingStatus, setPendingStatus] = React.useState(null);
const [deliveryDate, setDeliveryDate] = React.useState("");
const [deliveryTime, setDeliveryTime] = React.useState(DELIVERY_TIME_OPTIONS[0]);
const [formMessage, setFormMessage] = React.useState("");
const [shipmentState, setShipmentState] = React.useState(null);
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [driverMessage, setDriverMessage] = React.useState("");
const [selectedDriverId, setSelectedDriverId] = React.useState(order?.assignedDriverId || "");
const [deliveryType, setDeliveryType] = React.useState(order?.deliveryType || "delivery");
const [pickupDate, setPickupDate] = React.useState(order?.pickupDate || "");
const [pickupTimeSlot, setPickupTimeSlot] = React.useState(DELIVERY_TIME_OPTIONS[0]);
const [deliveryAddress, setDeliveryAddress] = React.useState(order?.originalDeliveryAddress || order?.deliveryAddress || order?.customerAddress || "");
const [confirmAction, setConfirmAction] = React.useState(null);
const [isEditingDate, setIsEditingDate] = React.useState(false);
const handleShipmentChange = React.useCallback((state) => {
setShipmentState(state);
}, []);
const minSelectableDateKey = React.useMemo(() => getNextSelectableDateKey(), []);
const [currentMonth, setCurrentMonth] = React.useState(() => {
const existingDeliveryDate = fromDateKey(order?.deliveryDate);
const fallbackDate = fromDateKey(minSelectableDateKey) || new Date();
const sourceDate = existingDeliveryDate && isFutureDeliveryDate(toDateKey(existingDeliveryDate))
? existingDeliveryDate
: fallbackDate;
return startOfMonth(sourceDate);
});
const calendarDays = React.useMemo(() => buildCalendarDays(currentMonth), [currentMonth]);
const monthLabel = React.useMemo(
() =>
currentMonth.toLocaleDateString("ru-RU", {
month: "long",
year: "numeric",
}),
[currentMonth],
);
const canGoBack = toDateKey(currentMonth) > toDateKey(startOfMonth(fromDateKey(minSelectableDateKey) || new Date()));
React.useEffect(() => {
setSelectedDriverId(order?.assignedDriverId || "");
setIsEditingDate(false);
}, [order?.id, order?.assignedDriverId]);
React.useEffect(() => {
const normalizedDeliveryDate = normalizeDateForInput(order?.deliveryDate);
const nextSelectableDateKey = getNextSelectableDateKey();
const selectedDateKey = isFutureDeliveryDate(normalizedDeliveryDate) ? normalizedDeliveryDate : nextSelectableDateKey;
setDeliveryDate(selectedDateKey);
const selectedDate = fromDateKey(selectedDateKey) || new Date();
setCurrentMonth(startOfMonth(selectedDate));
setDeliveryTime(normalizeDeliveryTimeChoice(order?.deliveryTime || order?.deliveryHalfDay));
setDeliveryType(order?.deliveryType || "delivery");
setPickupDate(order?.pickupDate || "");
setPickupTimeSlot(normalizeDeliveryTimeChoice(order?.pickupTimeSlot || order?.deliveryTime || order?.deliveryHalfDay));
setDeliveryAddress(order?.originalDeliveryAddress || order?.deliveryAddress || order?.customerAddress || "");
setFormMessage("");
}, [order?.id, order?.deliveryDate, order?.deliveryHalfDay, order?.deliveryTime, order?.deliveryType, order?.pickupDate, order?.pickupTimeSlot]);
if (!order) {
return (
Выберите группу для просмотра деталей.
);
}
const isDeliveryAgreed = ["agreed", "driver_assigned", "loaded", "on_route", "delivered", "picked_up"].includes(order.deliveryStatus || order.delivery_status);
const isPickupOrder = order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup";
// Show "agreed" banner only when selected tab matches the already-agreed type
const agreedTypeMatchesTab = isDeliveryAgreed && !isEditingDate && (
(deliveryType === "pickup" && isPickupOrder)
|| (deliveryType === "delivery" && !isPickupOrder)
);
const canEditDelivery = canManageDelivery && ["admin", "mega_admin", "logistician"].includes(userRole);
const agreedDeliveryLabel = [
formatDeliveryDateDisplay(order.deliveryDate),
order.deliveryTime || order.deliveryHalfDay,
].filter((value) => value && value !== "Нет данных").join(" · ");
const handleSaveDeliveryChoice = async () => {
const effectiveDate = deliveryType === "pickup" ? pickupDate : deliveryDate;
const effectiveTime = deliveryType === "pickup" ? pickupTimeSlot : deliveryTime;
if (!effectiveDate || !effectiveTime) {
setFormMessage(deliveryType === "pickup" ? "Укажите дату и время самовывоза." : "Укажите дату и половину дня доставки.");
return;
}
if (!isFutureDeliveryDate(effectiveDate)) {
setFormMessage(deliveryType === "pickup" ? "Выберите дату самовывоза позже сегодняшнего дня." : "Выберите дату доставки позже сегодняшнего дня.");
return;
}
try {
const result = await onSaveManualDeliveryChoice?.({
orderGroupId: order.id,
deliveryDate: deliveryType === "pickup" ? pickupDate : deliveryDate,
deliveryTime: deliveryType === "pickup" ? pickupTimeSlot : deliveryTime,
deliveryType,
...(deliveryType === "pickup" ? { pickupDate, pickupTimeSlot } : {}),
...(deliveryType === "delivery" ? { deliveryAddress: deliveryAddress.trim() } : {}),
});
if (result?.success) {
setFormMessage(deliveryType === "pickup" ? "Самовывоз согласован вручную." : "Доставка согласована вручную.");
return;
}
setFormMessage(getErrorMessage(result?.error, "Не удалось сохранить согласование доставки."));
} catch (error) {
setFormMessage(getErrorMessage(error, "Не удалось сохранить согласование доставки."));
}
};
const handleAssignDriver = async () => {
if (!selectedDriverId) {
setDriverMessage("Выберите водителя");
return;
}
if (!order.deliveryDate) {
setDriverMessage("Сначала укажите дату и время доставки.");
return;
}
setDriverMessage("");
const response = await onAssignDriver({
orderGroupId: order.id,
driverId: selectedDriverId,
});
if (!response.success) {
setDriverMessage(response.error || "Не удалось назначить водителя");
} else {
setDriverMessage("Водитель назначен");
}
};
return (
{isPickupOrder ? "Карточка группы самовывоза" : "Карточка группы доставки"}
{order.displayTitle || order.customerName || order.groupKey}
{(() => {
const parts = [];
if (order.orderNumbers && order.orderNumbers.length > 0) parts.push(order.orderNumbers.join(", "));
const sub = order.displaySubtitle || [order.customerPhone, order.customerDate].filter(Boolean).join(" · ");
if (sub) parts.push(sub);
return parts.join(" · ") || "Не указано";
})()}
{getOrderGroupDisplayStatusLabel(order)}
{(() => {
const isPickup = isPickupOrder;
const effectiveAddress = isPickup
? (order.customerAddress || "")
: (order.deliveryAddress || "");
const requiresAddress = (order.deliveryStatus === "requires_address" || order.delivery_status === "requires_address") && !effectiveAddress;
const deliveryTypeLabel = isPickup
? "Самовывоз"
: requiresAddress
? "Доставка (требуется адрес)"
: "Доставка";
const dateLabel = isPickup ? "Дата самовывоза" : "Дата доставки";
const timeLabel = isPickup ? "Время самовывоза" : "Время доставки";
const addressLabel = isPickup ? "Адрес клиента" : "Адрес доставки";
return (
{dateLabel}
{formatDeliveryDateDisplay(order.deliveryDate)}
{timeLabel}
{renderValue(order.deliveryTime || order.deliveryHalfDay)}
Тип
{deliveryTypeLabel}
{requiresAddress && (
📍
Адрес доставки не указан
Клиент выбрал доставку, но адрес отсутствует. Уточните адрес у клиента и заполните поле ниже.
)}
Водитель
{order.assignedDriverId ? renderValue(order.assignedDriverName) : (isPickup ? "Не нужен" : "Не назначен")}
{!isPickup || effectiveAddress ? (
{addressLabel}
{renderValue(effectiveAddress)}
) : null}
);
})()}
Заказ
{(() => {
const mainNumbers = order.orderNumbers || [];
const allNumbers = order.allBillNumbers || [];
const mainSet = new Set(mainNumbers.map(String));
const extraNumbers = allNumbers.filter((n) => !mainSet.has(String(n)));
if (mainNumbers.length > 0) {
return (
{mainNumbers.map((num, idx) => (
{ navigator.clipboard?.writeText(num); }}
>{num}
))}
{extraNumbers.length > 0 && (
)}
);
}
return renderValue(order.orderNumberSummary);
})()}
Клиент
{renderValue(order.customerName)}
Дата счёта
{renderValue(order.customerDate)}
Всего заказов
{order.ordersCount ?? 0}
Готово
{order.readyCount ?? 0}
{(order.notReadyCount ?? 0) > 0 ? (
Не готово
{order.notReadyCount}
) : null}
Обновлена
{formatDateTime(order.updatedAt)}
{isPickupOrder ? "Статус самовывоза" : "Статус доставки"}
{getOrderGroupDeliveryStatusLabel(order.deliveryStatus || order.delivery_status)}
{canManageDelivery ? (
Ручное согласование
{isDeliveryAgreed
? "Дата и время уже зафиксированы."
: "Если клиент согласовал доставку или самовывоз по телефону, сохраните дату и время здесь."}
{/* Delivery type tabs */}
{deliveryType === "pickup" && (
ℹ️ Условия хранения
Бесплатное хранение — 2 рабочих дня с даты готовности.
Начиная с 3-го рабочего дня — 300 ₽/день платного хранения.
)}
{deliveryType === "delivery" && (
setDeliveryAddress(e.target.value)}
placeholder="Введите адрес доставки"
className="w-full rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3 text-sm !text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-accent)] focus:outline-none"
/>
)}
{agreedTypeMatchesTab ? (
{deliveryType === "pickup" ? "Самовывоз согласован" : "Доставка согласована"}
{agreedDeliveryLabel || "Дата и время сохранены"}
Согласовано
{canEditDelivery ? (
) : null}
) : deliveryType === "delivery" ? (
{ setDeliveryDate(dateKey); setFormMessage(""); }}
minDateKey={minSelectableDateKey}
isCalendarOpen={isCalendarOpen}
setIsCalendarOpen={setIsCalendarOpen}
currentMonth={currentMonth}
setCurrentMonth={setCurrentMonth}
calendarDays={calendarDays}
monthLabel={monthLabel}
canGoBack={canGoBack}
timeOptions={DELIVERY_TIME_OPTIONS}
selectedTime={deliveryTime}
onTimeChange={(option) => { setDeliveryTime(option); setFormMessage(""); }}
layoutClassName="flex flex-col gap-3 md:flex-row md:items-start md:relative md:z-10"
calendarClassName="relative space-y-3 md:min-w-0 md:flex-1 md:pr-4"
timeClassName="grid gap-2 sm:grid-cols-2 md:w-[320px] md:flex-none"
/>
) : (
{ setPickupDate(dateKey); setFormMessage(""); }}
minDateKey={minSelectableDateKey}
isCalendarOpen={isCalendarOpen}
setIsCalendarOpen={setIsCalendarOpen}
currentMonth={currentMonth}
setCurrentMonth={setCurrentMonth}
calendarDays={calendarDays}
monthLabel={monthLabel}
canGoBack={canGoBack}
timeOptions={DELIVERY_TIME_OPTIONS}
selectedTime={pickupTimeSlot}
onTimeChange={(option) => { setPickupTimeSlot(option); setFormMessage(""); }}
layoutClassName="space-y-3"
calendarClassName="relative"
timeClassName="grid gap-2 sm:grid-cols-2"
/>
)}
{formMessage ? (
{formMessage}
) : null}
) : null}
{deliveryType === "delivery" && (
{ setSelectedDriverId(id); setDriverMessage(""); }}
onConfirmDriver={() => setConfirmAction({ type: 'driver' })}
driverMessage={driverMessage}
drivers={drivers}
/>
)}
{
if (action.type === "hint") {
setFormMessage(action.hint);
} else if (action.type === "status") {
setConfirmAction({ type: "status", status: action.status });
}
}}
/>
{formMessage && ["manager", "logistician", "admin", "mega_admin"].includes(userRole) && order && onChangeDeliveryStatus ? (
{formMessage}
) : null}
{["manager", "logistician", "admin", "mega_admin"].includes(userRole) && order && onChangeDeliveryStatus ? (
) : null}
{userRole === "driver" && order ? (
) : null}
{userRole === "driver" && order && onChangeDeliveryStatus ? (
Статус доставки
Выберите статус и нажмите «Сохранить».
{problemReason !== null ? (
{
setPendingStatus({ value: "problem", reason: reasonValue, reasonLabel });
setProblemReason(null);
}}
onCancel={() => setProblemReason(null)}
/>
) : null}
{(() => {
const currentStatus = order.deliveryStatus || order.delivery_status;
const IN_TRANSIT_STATUSES = ["loaded", "on_route"];
const isOnRoute = IN_TRANSIT_STATUSES.includes(currentStatus);
let statusOptions = [];
if (currentStatus === "delivered" || currentStatus === "picked_up" || currentStatus === "problem" || currentStatus === "cancelled" || currentStatus === "paid_storage") {
statusOptions = [];
} else {
statusOptions = [
{ value: "delivered", label: "Доставлено" },
{ value: "picked_up", label: "Вывезено" },
{ value: "problem", label: "Проблема" },
];
}
if (statusOptions.length === 0) return null;
return statusOptions.map((statusOption) => {
const isSelected = pendingStatus?.value === statusOption.value;
const isDeliveredBtn = statusOption.value === "delivered";
const deliveryBlocked = isDeliveredBtn && shipmentState && !shipmentState.canMarkDelivered;
return (
);
});
})()}
{pendingStatus ? (
) : null}
{formMessage ? (
{formMessage}
) : null}
) : null}
Счета
{(() => {
const mainNumbers = order.orderNumbers || [];
const allNumbers = getAllBillNumbers(order);
const mainSet = new Set(mainNumbers.map(String));
const extraNumbers = allNumbers.filter((n) => !mainSet.has(String(n)));
return (
{mainNumbers.length > 0 && (
Основной счёт
{mainNumbers.map((num, idx) => (
{num}
))}
)}
{extraNumbers.length > 0 && (
{mainNumbers.length > 0 ? "Составные заказы" : "Все счета"}
{extraNumbers.map((num, idx) => (
{num}
))}
)}
{mainNumbers.length === 0 && extraNumbers.length === 0 && (
Нет данных
)}
);
})()}
{userRole !== "driver" && (order?.driver_shipment_data || order?.driverShipmentData) ? (
) : null}
{userRole !== "driver" ? (
Дополнительные данные
{order.firstSmsSentAt ? (
1-е SMS отправлено
{formatDateTime(order.firstSmsSentAt)}
) : null}
{order.secondSmsSentAt ? (
2-е SMS отправлено
{formatDateTime(order.secondSmsSentAt)}
) : null}
{!order.firstSmsSentAt && !order.secondSmsSentAt ? (
) : null}
Ручное согласование выполнено
{order.manualConfirmationAt ? formatDateTime(order.manualConfirmationAt) : "Нет"}
Платное хранение
{order.paidStorageAt ? formatDateTime(order.paidStorageAt) : "Нет"}
{order.createdFromExchangeAt ? (
Создано из обмена
{formatDateTime(order.createdFromExchangeAt)}
) : null}
) : null}
{ setConfirmAction(null); handleSaveDeliveryChoice(); }}
onCancel={() => setConfirmAction(null)}
/>
d.id === selectedDriverId)?.name || 'выбранного водителя'} на эту группу?`}
onConfirm={() => { setConfirmAction(null); handleAssignDriver(); }}
onCancel={() => setConfirmAction(null)}
/>
{
const status = confirmAction.status;
setConfirmAction(null);
onChangeDeliveryStatus({ orderGroupId: order.id, status }).then((response) => {
if (!response.success) setFormMessage(response.error || 'Не удалось обновить статус');
else setFormMessage('');
});
}}
onCancel={() => setConfirmAction(null)}
/>
);
};