diff --git a/src/components/driver/DriverDeliveryPlanner.jsx b/src/components/driver/DriverDeliveryPlanner.jsx
index 4fe743f..3435289 100644
--- a/src/components/driver/DriverDeliveryPlanner.jsx
+++ b/src/components/driver/DriverDeliveryPlanner.jsx
@@ -8,6 +8,7 @@ import {
isOrderGroupVisibleToDriver,
groupOrderGroupsByDate,
parseGroupDate,
+ ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS,
} from "../../services/orderGroupViews";
import { Badge } from "../UI/Badge";
import { Button } from "../UI/Button";
@@ -110,13 +111,29 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
selectedDate: "",
deliveryStatus: "all",
selectedCity: "",
+ searchQuery: "",
+ halfDay: "all",
});
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) => {
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(
() => orderGroups.filter((group) => {
const isVisible = isOrderGroupVisibleToDriver(group);
@@ -158,6 +175,19 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
});
}, [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(() => {
let result = [...driverOrderGroups];
if (filters.selectedDate) {
@@ -172,8 +202,39 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
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;
- }, [driverOrderGroups, filters.selectedDate, filters.deliveryStatus, filters.selectedCity]);
+ }, [driverOrderGroups, filters.selectedDate, filters.deliveryStatus, filters.selectedCity, filters.searchQuery, filters.halfDay]);
const groupedOrderGroups = React.useMemo(
() => groupOrderGroupsByDate(filteredOrderGroups),
@@ -245,6 +306,49 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
+
+
+
+
+
+ {hasActiveFilters && (
+
+
+
+ )}
+
{/* Date pills */}
{sortedDeliveryDates.length > 0 && (
@@ -402,7 +506,11 @@ export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUs
{items.length} {pluralGroups(items.length)}
- {items.map((item) => (
+ {items.map((item) => {
+ const hasProblem = item.hasDeliveryProblem || item.has_delivery_problem;
+ const phoneTel = normalizePhoneForTel(item.customerPhone);
+ const halfDayLabel = getOrderGroupDeliveryHalfDay(item);
+ return (
+ {showResetConfirm && (
+
+
+ Сбросить отгрузку и вернуть статус?
+
+
+ Текущий статус («{currentDeliveryStatus === "delivered" ? "Доставлено" : currentDeliveryStatus === "problem" ? "Проблема" : "Вывезено"}») будет сброшен.
+ Отгрузка будет очищена, логист увидит что доставка требует доработки.
+
+
+
+ {isSavingStatusChange ? "Сохраняем..." : "Да, сбросить"}
+
+ setShowResetConfirm(false)}>
+ Отмена
+
+
+
+ )}
+
{items.map((item) => {
const isShipped = shippedItems.has(item.id);
diff --git a/src/components/logistics/LogisticsReadinessBoard.jsx b/src/components/logistics/LogisticsReadinessBoard.jsx
index eb5e035..14c4d65 100644
--- a/src/components/logistics/LogisticsReadinessBoard.jsx
+++ b/src/components/logistics/LogisticsReadinessBoard.jsx
@@ -158,7 +158,17 @@ const renderRow = (group, onSelectSet) => (
{group.assignedDriverName || —}
-
{getOrderGroupDisplayStatusLabel(group)}
+
+ {getOrderGroupDisplayStatusLabel(group)}
+ {(group.hasDeliveryProblem || group.has_delivery_problem) && (
+
+ ⚠ Проблема
+
+ )}
+
{formatDateTime(group.updatedAt)}
diff --git a/src/components/orders/OrderDetailPanel.jsx b/src/components/orders/OrderDetailPanel.jsx
index 7996a99..b0b3cc0 100644
--- a/src/components/orders/OrderDetailPanel.jsx
+++ b/src/components/orders/OrderDetailPanel.jsx
@@ -1088,7 +1088,27 @@ export const OrderDetailPanel = ({
) : null}
{userRole === "driver" && order ? (
-
+ {
+ if (onChangeDeliveryStatus) {
+ onChangeDeliveryStatus({
+ orderGroupId: order.id,
+ status: "loaded",
+ }).then((response) => {
+ if (!response.success) {
+ setFormMessage(response.error || "Не удалось сбросить статус");
+ } else {
+ setFormMessage("Статус сброшен, отгрузка очищена");
+ }
+ });
+ }
+ }}
+ isSavingStatusChange={isSavingStatusChange}
+ />
) : null}
{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) => {
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 (
+ {
+ setPendingStatus({
+ value: "loaded",
+ label: "Вернуть в работу",
+ mismatch: false,
+ deliveryType: "delivery",
+ });
+ }}
+ className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-warning)]"
+ >
+ ↩ Вернуть в работу
+
+ );
+ })()}
{pendingStatus ? (
diff --git a/src/services/supabase/orderGroupRepository.js b/src/services/supabase/orderGroupRepository.js
index 68e0001..2131df0 100644
--- a/src/services/supabase/orderGroupRepository.js
+++ b/src/services/supabase/orderGroupRepository.js
@@ -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("; ")
: null;
- // Any shipment data saved = delivered status. Problems go into has_delivery_problem/delivery_problem_note columns.
- const newDeliveryStatus = hasAnyShipped ? "delivered" : undefined;
+ // If all shipped → delivered. If partial (some shipped, some not) → problem.
+ // 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 = {
driver_shipment_data: shipmentData,