feat: driver filters + status fixes + logistics problem indicator
DriverDeliveryPlanner: - Text search (name, address, phone, order numbers) - Half-day filter (morning/afternoon/unknown) - 'Reset all filters' button - tel: link on phone in delivery cards - Order numbers badges in cards - Partial delivery warning badge - Problems-first sorting saveShipmentData fix: - Partial shipment with problems → status 'problem' (not 'delivered') - Full shipment → 'delivered' (unchanged) DriverShipmentPanel: - Reset button: confirmation dialog when status is final - Calls onResetStatus to revert delivery status to 'loaded' OrderDetailPanel (driver status section): - 'Return to work' button for delivered/problem/picked_up statuses - Passes onResetStatus + isSavingStatusChange to DriverShipmentPanel LogisticsReadinessBoard: - Warning badge for has_delivery_problem groups in table rows - Shows delivery_problem_note as tooltip
This commit is contained in:
parent
c1e5f109be
commit
ced5958eac
|
|
@ -8,6 +8,7 @@ import {
|
||||||
isOrderGroupVisibleToDriver,
|
isOrderGroupVisibleToDriver,
|
||||||
groupOrderGroupsByDate,
|
groupOrderGroupsByDate,
|
||||||
parseGroupDate,
|
parseGroupDate,
|
||||||
|
ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS,
|
||||||
} from "../../services/orderGroupViews";
|
} from "../../services/orderGroupViews";
|
||||||
import { Badge } from "../UI/Badge";
|
import { Badge } from "../UI/Badge";
|
||||||
import { Button } from "../UI/Button";
|
import { Button } from "../UI/Button";
|
||||||
|
|
@ -110,13 +111,29 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
selectedDate: "",
|
selectedDate: "",
|
||||||
deliveryStatus: "all",
|
deliveryStatus: "all",
|
||||||
selectedCity: "",
|
selectedCity: "",
|
||||||
|
searchQuery: "",
|
||||||
|
halfDay: "all",
|
||||||
});
|
});
|
||||||
const [collapsedDates, setCollapsedDates] = React.useState({});
|
const [collapsedDates, setCollapsedDates] = React.useState({});
|
||||||
|
|
||||||
|
const hasActiveFilters = filters.selectedDate || filters.deliveryStatus !== "all" || filters.selectedCity || filters.searchQuery || filters.halfDay !== "all";
|
||||||
|
|
||||||
|
const resetAllFilters = () => {
|
||||||
|
setFilters({ selectedDate: "", deliveryStatus: "all", selectedCity: "", searchQuery: "", halfDay: "all" });
|
||||||
|
};
|
||||||
|
|
||||||
const toggleDate = (date) => {
|
const toggleDate = (date) => {
|
||||||
setCollapsedDates((prev) => ({ ...prev, [date]: !prev[date] }));
|
setCollapsedDates((prev) => ({ ...prev, [date]: !prev[date] }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 driverOrderGroups = React.useMemo(
|
const driverOrderGroups = React.useMemo(
|
||||||
() => orderGroups.filter((group) => {
|
() => orderGroups.filter((group) => {
|
||||||
const isVisible = isOrderGroupVisibleToDriver(group);
|
const isVisible = isOrderGroupVisibleToDriver(group);
|
||||||
|
|
@ -158,6 +175,19 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
});
|
});
|
||||||
}, [cityDeliveryMap]);
|
}, [cityDeliveryMap]);
|
||||||
|
|
||||||
|
const getSearchHaystack = (group) => {
|
||||||
|
return [
|
||||||
|
group.groupKey,
|
||||||
|
group.displayTitle,
|
||||||
|
group.customerName,
|
||||||
|
group.customerPhone,
|
||||||
|
group.customerDate,
|
||||||
|
Array.isArray(group.orderNumbers) ? group.orderNumbers.join(" ") : "",
|
||||||
|
group.deliveryAddress || group.delivery_address || "",
|
||||||
|
group.city || "",
|
||||||
|
].filter(Boolean).join(" ").toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
const filteredOrderGroups = React.useMemo(() => {
|
const filteredOrderGroups = React.useMemo(() => {
|
||||||
let result = [...driverOrderGroups];
|
let result = [...driverOrderGroups];
|
||||||
if (filters.selectedDate) {
|
if (filters.selectedDate) {
|
||||||
|
|
@ -172,8 +202,39 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
return city === filters.selectedCity;
|
return city === filters.selectedCity;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (filters.halfDay !== "all") {
|
||||||
|
result = result.filter((group) => {
|
||||||
|
const groupHalfDay = getOrderGroupDeliveryHalfDay(group);
|
||||||
|
if (filters.halfDay === "unknown") {
|
||||||
|
return !groupHalfDay;
|
||||||
|
}
|
||||||
|
const labelMap = { morning: "Первая половина дня", afternoon: "Вторая половина дня" };
|
||||||
|
return groupHalfDay === labelMap[filters.halfDay];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const query = (filters.searchQuery || "").trim().toLowerCase();
|
||||||
|
if (query) {
|
||||||
|
result = result.filter((group) => getSearchHaystack(group).includes(query));
|
||||||
|
}
|
||||||
|
// Sort: problems first, then by status priority
|
||||||
|
const statusPriority = ["problem", "on_route", "loaded", "driver_assigned", "agreed", "delivered", "picked_up", "paid_storage"];
|
||||||
|
result.sort((a, b) => {
|
||||||
|
// has_delivery_problem always first
|
||||||
|
const aProblem = a.hasDeliveryProblem || a.has_delivery_problem;
|
||||||
|
const bProblem = b.hasDeliveryProblem || b.has_delivery_problem;
|
||||||
|
if (aProblem && !bProblem) return -1;
|
||||||
|
if (!aProblem && bProblem) return 1;
|
||||||
|
const sa = a.deliveryStatus || a.delivery_status || "unknown";
|
||||||
|
const sb = b.deliveryStatus || b.delivery_status || "unknown";
|
||||||
|
const ia = statusPriority.indexOf(sa);
|
||||||
|
const ib = statusPriority.indexOf(sb);
|
||||||
|
if (ia === -1 && ib === -1) return 0;
|
||||||
|
if (ia === -1) return 1;
|
||||||
|
if (ib === -1) return -1;
|
||||||
|
return ia - ib;
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
}, [driverOrderGroups, filters.selectedDate, filters.deliveryStatus, filters.selectedCity]);
|
}, [driverOrderGroups, filters.selectedDate, filters.deliveryStatus, filters.selectedCity, filters.searchQuery, filters.halfDay]);
|
||||||
|
|
||||||
const groupedOrderGroups = React.useMemo(
|
const groupedOrderGroups = React.useMemo(
|
||||||
() => groupOrderGroupsByDate(filteredOrderGroups),
|
() => groupOrderGroupsByDate(filteredOrderGroups),
|
||||||
|
|
@ -245,6 +306,49 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
|
||||||
|
<label className="flex min-w-0 flex-col gap-2">
|
||||||
|
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||||
|
Поиск
|
||||||
|
</span>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Имя, адрес, телефон, номер заказа..."
|
||||||
|
value={filters.searchQuery}
|
||||||
|
onChange={(event) => setFilters((current) => ({ ...current, searchQuery: event.target.value }))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className="flex min-w-0 flex-col gap-2">
|
||||||
|
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||||
|
Время дня
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={filters.halfDay}
|
||||||
|
onChange={(event) => setFilters((current) => ({ ...current, halfDay: event.target.value }))}
|
||||||
|
>
|
||||||
|
{ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<div className="pt-1">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={resetAllFilters}
|
||||||
|
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-danger)]"
|
||||||
|
>
|
||||||
|
✕ Сбросить все фильтры
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Date pills */}
|
{/* Date pills */}
|
||||||
{sortedDeliveryDates.length > 0 && (
|
{sortedDeliveryDates.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2 pt-2">
|
<div className="flex flex-wrap gap-2 pt-2">
|
||||||
|
|
@ -402,7 +506,11 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
<span className="text-xs text-[var(--color-text-muted)]">{items.length} {pluralGroups(items.length)}</span>
|
<span className="text-xs text-[var(--color-text-muted)]">{items.length} {pluralGroups(items.length)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
{items.map((item) => (
|
{items.map((item) => {
|
||||||
|
const hasProblem = item.hasDeliveryProblem || item.has_delivery_problem;
|
||||||
|
const phoneTel = normalizePhoneForTel(item.customerPhone);
|
||||||
|
const halfDayLabel = getOrderGroupDeliveryHalfDay(item);
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|
@ -410,14 +518,43 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
onClick={() => onOpenOrder?.(item.id)}
|
onClick={() => onOpenOrder?.(item.id)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="font-medium text-[var(--color-text)]">
|
<div className="flex items-center gap-2">
|
||||||
{item.displayTitle || item.customerName || item.groupKey}
|
<span className="font-medium text-[var(--color-text)]">
|
||||||
|
{item.displayTitle || item.customerName || item.groupKey}
|
||||||
|
</span>
|
||||||
|
{hasProblem && (
|
||||||
|
<Badge tone="danger">⚠ Частично</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-sm text-[var(--color-text-muted)]">
|
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-[var(--color-text-muted)]">
|
||||||
{item.customerDate} · {item.customerPhone}
|
{item.customerDate && <span>{item.customerDate}</span>}
|
||||||
{getOrderGroupDeliveryHalfDay(item) ? ` · ${getOrderGroupDeliveryHalfDay(item)}` : ""}
|
{item.customerDate && (halfDayLabel || item.customerPhone) && <span>·</span>}
|
||||||
|
{halfDayLabel && <span>{halfDayLabel}</span>}
|
||||||
|
{halfDayLabel && item.customerPhone && <span>·</span>}
|
||||||
|
{item.customerPhone && phoneTel && (
|
||||||
|
<a
|
||||||
|
href={`tel:${phoneTel}`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="text-[var(--color-accent)] hover:underline"
|
||||||
|
>
|
||||||
|
{item.customerPhone}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{item.customerPhone && !phoneTel && <span>{item.customerPhone}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
{Array.isArray(item.orderNumbers) && item.orderNumbers.length > 0 && (
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
|
{item.orderNumbers.map((num, idx) => (
|
||||||
|
<span
|
||||||
|
key={idx}
|
||||||
|
className="rounded-full bg-[var(--color-accent-soft)] px-1.5 py-0.5 text-[10px] font-semibold text-[var(--color-accent)]"
|
||||||
|
>
|
||||||
|
{num}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -425,7 +562,8 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
|
||||||
{item.deliveryAddress || item.delivery_address || "Адрес не указан"}
|
{item.deliveryAddress || item.delivery_address || "Адрес не указан"}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,12 @@ const parseOrderItems = (order) => {
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DriverShipmentPanel = ({ order, onShipmentChange, onSaveShipment, isSavingShipment }) => {
|
export const DriverShipmentPanel = ({ order, onShipmentChange, onSaveShipment, isSavingShipment, onResetStatus, isSavingStatusChange }) => {
|
||||||
const [stopWords, setStopWords] = React.useState([]);
|
const [stopWords, setStopWords] = React.useState([]);
|
||||||
const [scopeActive, setScopeActive] = React.useState(true);
|
const [scopeActive, setScopeActive] = React.useState(true);
|
||||||
const [savedShipment, setSavedShipment] = React.useState([]);
|
const [savedShipment, setSavedShipment] = React.useState([]);
|
||||||
const [justSaved, setJustSaved] = React.useState(false);
|
const [justSaved, setJustSaved] = React.useState(false);
|
||||||
|
const [showResetConfirm, setShowResetConfirm] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!supabase) return;
|
if (!supabase) return;
|
||||||
|
|
@ -168,11 +169,27 @@ export const DriverShipmentPanel = ({ order, onShipmentChange, onSaveShipment, i
|
||||||
setComments({});
|
setComments({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const currentDeliveryStatus = order?.deliveryStatus || order?.delivery_status;
|
||||||
|
const isStatusFinal = ["delivered", "problem", "picked_up"].includes(currentDeliveryStatus);
|
||||||
|
|
||||||
const unshipAll = () => {
|
const unshipAll = () => {
|
||||||
|
if (isStatusFinal && onResetStatus) {
|
||||||
|
setShowResetConfirm(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setShippedItems(new Set());
|
setShippedItems(new Set());
|
||||||
setComments({});
|
setComments({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const confirmResetAll = () => {
|
||||||
|
setShippedItems(new Set());
|
||||||
|
setComments({});
|
||||||
|
setShowResetConfirm(false);
|
||||||
|
if (onResetStatus) {
|
||||||
|
onResetStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const shippedCount = items.filter((i) => shippedItems.has(i.id)).length;
|
const shippedCount = items.filter((i) => shippedItems.has(i.id)).length;
|
||||||
const unshippedCount = items.length - shippedCount;
|
const unshippedCount = items.length - shippedCount;
|
||||||
const allShipped = items.length > 0 && shippedCount === items.length;
|
const allShipped = items.length > 0 && shippedCount === items.length;
|
||||||
|
|
@ -232,11 +249,36 @@ export const DriverShipmentPanel = ({ order, onShipmentChange, onSaveShipment, i
|
||||||
<Button variant="secondary" size="sm" onClick={shipAll} disabled={allShipped}>
|
<Button variant="secondary" size="sm" onClick={shipAll} disabled={allShipped}>
|
||||||
Отгрузить всё
|
Отгрузить всё
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" onClick={unshipAll} disabled={shippedCount === 0}>
|
<Button variant="ghost" size="sm" onClick={unshipAll} disabled={shippedCount === 0 && !isStatusFinal}>
|
||||||
Сбросить
|
Сбросить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showResetConfirm && (
|
||||||
|
<div className="rounded-xl border border-[var(--color-warning)] bg-[var(--color-warning-soft)] p-4 space-y-3">
|
||||||
|
<p className="text-sm font-medium text-[var(--color-text)]">
|
||||||
|
Сбросить отгрузку и вернуть статус?
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-[var(--color-text-muted)]">
|
||||||
|
Текущий статус («{currentDeliveryStatus === "delivered" ? "Доставлено" : currentDeliveryStatus === "problem" ? "Проблема" : "Вывезено"}») будет сброшен.
|
||||||
|
Отгрузка будет очищена, логист увидит что доставка требует доработки.
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={confirmResetAll}
|
||||||
|
disabled={isSavingStatusChange}
|
||||||
|
>
|
||||||
|
{isSavingStatusChange ? "Сохраняем..." : "Да, сбросить"}
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => setShowResetConfirm(false)}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const isShipped = shippedItems.has(item.id);
|
const isShipped = shippedItems.has(item.id);
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,17 @@ const renderRow = (group, onSelectSet) => (
|
||||||
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]">—</span>}
|
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]">—</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<Badge tone={getOrderGroupStatusTone(group)}>{getOrderGroupDisplayStatusLabel(group)}</Badge>
|
<div className="flex items-center gap-1.5">
|
||||||
|
<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)]"
|
||||||
|
>
|
||||||
|
⚠ Проблема
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-1.5 text-xs text-[var(--color-text-muted)]">
|
<div className="px-3 py-1.5 text-xs text-[var(--color-text-muted)]">
|
||||||
{formatDateTime(group.updatedAt)}
|
{formatDateTime(group.updatedAt)}
|
||||||
|
|
|
||||||
|
|
@ -1088,7 +1088,27 @@ export const OrderDetailPanel = ({
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{userRole === "driver" && order ? (
|
{userRole === "driver" && order ? (
|
||||||
<DriverShipmentPanel order={order} onShipmentChange={handleShipmentChange} onSaveShipment={handleSaveShipment} isSavingShipment={isSavingShipment} />
|
<DriverShipmentPanel
|
||||||
|
order={order}
|
||||||
|
onShipmentChange={handleShipmentChange}
|
||||||
|
onSaveShipment={handleSaveShipment}
|
||||||
|
isSavingShipment={isSavingShipment}
|
||||||
|
onResetStatus={() => {
|
||||||
|
if (onChangeDeliveryStatus) {
|
||||||
|
onChangeDeliveryStatus({
|
||||||
|
orderGroupId: order.id,
|
||||||
|
status: "loaded",
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.success) {
|
||||||
|
setFormMessage(response.error || "Не удалось сбросить статус");
|
||||||
|
} else {
|
||||||
|
setFormMessage("Статус сброшен, отгрузка очищена");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{userRole === "driver" && order && onChangeDeliveryStatus ? (
|
{userRole === "driver" && order && onChangeDeliveryStatus ? (
|
||||||
|
|
@ -1135,7 +1155,10 @@ export const OrderDetailPanel = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusOptions.length === 0) return null;
|
// "Return to work" button for final statuses
|
||||||
|
const canReturn = ["delivered", "picked_up", "problem"].includes(currentStatus);
|
||||||
|
|
||||||
|
if (statusOptions.length === 0 && !canReturn) return null;
|
||||||
|
|
||||||
return statusOptions.map((statusOption) => {
|
return statusOptions.map((statusOption) => {
|
||||||
const isSelected = pendingStatus?.value === statusOption.value;
|
const isSelected = pendingStatus?.value === statusOption.value;
|
||||||
|
|
@ -1163,6 +1186,28 @@ export const OrderDetailPanel = ({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
})()}
|
})()}
|
||||||
|
{(() => {
|
||||||
|
const currentStatus = order.deliveryStatus || order.delivery_status;
|
||||||
|
const canReturn = ["delivered", "picked_up", "problem"].includes(currentStatus);
|
||||||
|
if (!canReturn) return null;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
disabled={isSavingStatusChange}
|
||||||
|
onClick={() => {
|
||||||
|
setPendingStatus({
|
||||||
|
value: "loaded",
|
||||||
|
label: "Вернуть в работу",
|
||||||
|
mismatch: false,
|
||||||
|
deliveryType: "delivery",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-warning)]"
|
||||||
|
>
|
||||||
|
↩ Вернуть в работу
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
{pendingStatus ? (
|
{pendingStatus ? (
|
||||||
<div className="flex items-center gap-3 mt-2">
|
<div className="flex items-center gap-3 mt-2">
|
||||||
|
|
|
||||||
|
|
@ -409,8 +409,16 @@ export const saveShipmentData = async ({ orderGroupId, shipmentData }) => {
|
||||||
? shipmentData.filter((i) => !i.shipped).map((i) => `${i.name}${i.quantity ? ` (${i.quantity}${i.unit ? ` ${i.unit}` : ""})` : ""}${i.comment ? ` — ${i.comment}` : ""}`).join("; ")
|
? shipmentData.filter((i) => !i.shipped).map((i) => `${i.name}${i.quantity ? ` (${i.quantity}${i.unit ? ` ${i.unit}` : ""})` : ""}${i.comment ? ` — ${i.comment}` : ""}`).join("; ")
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// Any shipment data saved = delivered status. Problems go into has_delivery_problem/delivery_problem_note columns.
|
// If all shipped → delivered. If partial (some shipped, some not) → problem.
|
||||||
const newDeliveryStatus = hasAnyShipped ? "delivered" : undefined;
|
// If nothing shipped → keep current status (driver just clearing checkboxes).
|
||||||
|
let newDeliveryStatus;
|
||||||
|
if (hasAnyShipped && !hasProblem) {
|
||||||
|
newDeliveryStatus = "delivered";
|
||||||
|
} else if (hasAnyShipped && hasProblem) {
|
||||||
|
newDeliveryStatus = "problem";
|
||||||
|
} else {
|
||||||
|
newDeliveryStatus = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
driver_shipment_data: shipmentData,
|
driver_shipment_data: shipmentData,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue