refactor: Phase 1 stabilization — hooks ordering, deduplicate utils, extract shared constants
- P0-1: Move all hooks (useState, useCallback, useMemo) before early return to fix Rules of Hooks violation - P0-3: Replace 7 inline isPickupOrder checks with single computed variable - P1: Extract getErrorMessage, normalizeNom to shared utils/deliveryUtils.js - P1: Replace duplicate STATUS_LABELS with DELIVERY_GROUP_STATUS_LABELS import - P1: Add requires_address to DELIVERY_GROUP_STATUS_LABELS - Remove duplicate getErrorMessage from useOrderGroups.js and OrderDetailPanel.jsx - Remove duplicate normalizeNom from OrderDetailPanel.jsx and orderGroupRepository.js
This commit is contained in:
parent
1a665b5165
commit
4dde64ff5a
|
|
@ -50,11 +50,13 @@ import {
|
|||
getOrderGroupDeliveryStatusLabel,
|
||||
getOrderGroupDisplayStatusLabel,
|
||||
getOrderGroupStatusTone,
|
||||
DELIVERY_GROUP_STATUS_LABELS,
|
||||
} from "../../services/orderGroupViews";
|
||||
import { getErrorMessage, normalizeNom } from "../../utils/deliveryUtils";
|
||||
|
||||
const DELIVERY_TIME_OPTIONS = ["Первая половина дня", "Вторая половина дня"];
|
||||
const WEEK_DAY_LABELS = ["ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ", "ВС"];
|
||||
const STATUS_LABELS = { pending_confirmation: 'Ожидает согласования', agreed: 'Согласовано', driver_assigned: 'Назначен водитель', loaded: 'Загружено', on_route: 'В пути', delivered: 'Доставлено', pickup: 'Самовывоз', requires_address: 'Требуется адрес', problem: 'Проблема', cancelled: 'Отменено' };
|
||||
const STATUS_LABELS = DELIVERY_GROUP_STATUS_LABELS;
|
||||
|
||||
const ConfirmModal = ({ open, title, message, onConfirm, onCancel }) => {
|
||||
if (!open) return null;
|
||||
|
|
@ -125,12 +127,6 @@ const renderList = (values) => {
|
|||
|
||||
const renderValue = (value) => value || "Нет данных";
|
||||
|
||||
const normalizeNom = (nom) => {
|
||||
if (!nom) return '';
|
||||
// 1C escapes backslashes: "СФ Т\\ЕА-33584" → normalize for comparison
|
||||
return String(nom).replace(/\\\\/g, '\\').trim();
|
||||
};
|
||||
|
||||
const getAllBillNumbers = (order) => {
|
||||
const orders = parseOrderList(order);
|
||||
if (!orders.length) return order.orderNumbers || [];
|
||||
|
|
@ -194,22 +190,6 @@ const parseOrderList = (order) => {
|
|||
return [];
|
||||
};
|
||||
|
||||
const getErrorMessage = (error, fallbackMessage) => {
|
||||
if (!error) {
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message || fallbackMessage;
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error || fallbackMessage;
|
||||
}
|
||||
|
||||
return error?.message || fallbackMessage;
|
||||
};
|
||||
|
||||
const normalizeDeliveryTimeChoice = (value) => {
|
||||
const normalized = value ? String(value).trim() : "";
|
||||
const deliveryTime = DELIVERY_TIME_ALIASES[normalized] || normalized;
|
||||
|
|
@ -604,6 +584,10 @@ export const OrderDetailPanel = ({
|
|||
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);
|
||||
|
|
@ -655,7 +639,6 @@ export const OrderDetailPanel = ({
|
|||
|
||||
const isDeliveryAgreed = ["agreed", "driver_assigned", "loaded", "on_route", "delivered"].includes(order.deliveryStatus || order.delivery_status);
|
||||
const isPickupOrder = order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup";
|
||||
const [isEditingDate, setIsEditingDate] = React.useState(false);
|
||||
// Show "agreed" banner only when selected tab matches the already-agreed type
|
||||
const agreedTypeMatchesTab = isDeliveryAgreed && !isEditingDate && (
|
||||
(deliveryType === "pickup" && isPickupOrder)
|
||||
|
|
@ -667,10 +650,6 @@ export const OrderDetailPanel = ({
|
|||
order.deliveryTime || order.deliveryHalfDay,
|
||||
].filter((value) => value && value !== "Нет данных").join(" · ");
|
||||
|
||||
const handleShipmentChange = React.useCallback((state) => {
|
||||
setShipmentState(state);
|
||||
}, []);
|
||||
|
||||
const handleSaveDeliveryChoice = async () => {
|
||||
const effectiveDate = deliveryType === "pickup" ? pickupDate : deliveryDate;
|
||||
const effectiveTime = deliveryType === "pickup" ? pickupTimeSlot : deliveryTime;
|
||||
|
|
@ -735,7 +714,7 @@ export const OrderDetailPanel = ({
|
|||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm uppercase tracking-[0.2em] text-[var(--color-text-muted)]">
|
||||
{(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "Карточка группы самовывоза" : "Карточка группы доставки"}
|
||||
{isPickupOrder ? "Карточка группы самовывоза" : "Карточка группы доставки"}
|
||||
</p>
|
||||
<h2 className="mt-2 text-2xl font-semibold">
|
||||
{order.displayTitle || order.customerName || order.groupKey}
|
||||
|
|
@ -754,7 +733,7 @@ export const OrderDetailPanel = ({
|
|||
</div>
|
||||
|
||||
{(() => {
|
||||
const isPickup = order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup";
|
||||
const isPickup = isPickupOrder;
|
||||
const deliveryTypeLabel = isPickup
|
||||
? "Самовывоз"
|
||||
: (order.deliveryStatus === "requires_address" || order.delivery_status === "requires_address")
|
||||
|
|
@ -882,7 +861,7 @@ export const OrderDetailPanel = ({
|
|||
<p className="font-medium !text-[var(--color-text)]">{formatDateTime(order.updatedAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">{(order.deliveryType === "pickup" || order.deliveryStatus === "pickup" || order.delivery_status === "pickup") ? "Статус самовывоза" : "Статус доставки"}</p>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">{isPickupOrder ? "Статус самовывоза" : "Статус доставки"}</p>
|
||||
<p className="font-medium !text-[var(--color-text)]">{getOrderGroupDeliveryStatusLabel(order.deliveryStatus || order.delivery_status)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1173,7 +1152,7 @@ export const OrderDetailPanel = ({
|
|||
) : null}
|
||||
|
||||
|
||||
{canManageDelivery && ["manager", "logistician", "admin", "mega_admin"].includes(userRole) && (order.deliveryType !== "pickup" && order.deliveryStatus !== "pickup" && order.delivery_status !== "pickup") ? (
|
||||
{canManageDelivery && ["manager", "logistician", "admin", "mega_admin"].includes(userRole) && !isPickupOrder ? (
|
||||
<Panel className="space-y-4 p-5">
|
||||
<div>
|
||||
<strong>Назначение водителя</strong>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,7 @@ import {
|
|||
ORDER_GROUP_DISPLAY_STATUS_OPTIONS,
|
||||
getOrderGroupDisplayStatusValue,
|
||||
} from "../services/orderGroupViews";
|
||||
|
||||
const getErrorMessage = (error, fallbackMessage) => {
|
||||
if (!error) {
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message || fallbackMessage;
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error || fallbackMessage;
|
||||
}
|
||||
|
||||
return error?.message || fallbackMessage;
|
||||
};
|
||||
import { getErrorMessage } from "../utils/deliveryUtils";
|
||||
|
||||
export const useOrderGroups = () => {
|
||||
const [orderGroups, setOrderGroups] = React.useState(() => []);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const getDeliveryDate = (group) => normalizeDate(group.deliveryDate || group.cus
|
|||
export const DELIVERY_GROUP_STATUS_LABELS = {
|
||||
pending_confirmation: "Ожидает согласования",
|
||||
manual_confirmation_required: "Взят в ручное управление",
|
||||
requires_address: "Требуется адрес",
|
||||
agreed: "Согласовано",
|
||||
driver_assigned: "Назначен водитель",
|
||||
loaded: "Загружено",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
getOrderGroupDeliveryStatusLabel,
|
||||
getOrderGroupStatusLabel,
|
||||
} from "../orderGroupViews";
|
||||
import { normalizeNom } from "../../utils/deliveryUtils";
|
||||
|
||||
const requireSupabase = () => {
|
||||
if (!hasSupabaseConfig || !supabase) {
|
||||
|
|
@ -68,7 +69,6 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
if (!Array.isArray(srcOrders) || !srcOrders.length) return orderNumbers;
|
||||
const seen = new Set();
|
||||
const result = [];
|
||||
const normalizeNom = (nom) => String(nom || '').replace(/\\\\/g, '\\').trim();
|
||||
for (const src of srcOrders) {
|
||||
if (src && Array.isArray(src.orderList)) {
|
||||
for (const ol of src.orderList) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Shared delivery/delivery-group utilities.
|
||||
* Single source of truth for isPickupOrder, getErrorMessage, normalizeNom, and STATUS_LABELS.
|
||||
*/
|
||||
|
||||
// ── Pickup detection ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns true if the order group is a pickup (self-pickup) order.
|
||||
* Uses canonical deliveryStatus field (mapper normalizes both delivery_status variants).
|
||||
*/
|
||||
export const isPickupOrder = (order) =>
|
||||
order?.deliveryType === "pickup" ||
|
||||
order?.deliveryStatus === "pickup" ||
|
||||
order?.delivery_status === "pickup";
|
||||
|
||||
// ── Error messages ────────────────────────────────────────────────────────
|
||||
|
||||
export const getErrorMessage = (error, fallbackMessage) => {
|
||||
if (!error) {
|
||||
return fallbackMessage;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message || fallbackMessage;
|
||||
}
|
||||
if (typeof error === "string") {
|
||||
return error || fallbackMessage;
|
||||
}
|
||||
return error?.message || fallbackMessage;
|
||||
};
|
||||
|
||||
// ── Nom normalisation ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 1C escapes backslashes: "СФ Т\\ЕА-33584" → normalise for comparison.
|
||||
*/
|
||||
export const normalizeNom = (nom) => {
|
||||
if (!nom) return "";
|
||||
return String(nom).replace(/\\\\/g, "\\").trim();
|
||||
};
|
||||
|
||||
// ── Status labels ─────────────────────────────────────────────────────────
|
||||
|
||||
export const DELIVERY_STATUS_LABELS = {
|
||||
pending_confirmation: "Ожидает подтверждения",
|
||||
requires_address: "Требуется адрес",
|
||||
agreed: "Согласовано",
|
||||
driver_assigned: "Водитель назначен",
|
||||
loaded: "Загружен",
|
||||
on_route: "В пути",
|
||||
delivered: "Доставлено",
|
||||
problem: "Проблема",
|
||||
cancelled: "Отменено",
|
||||
pickup: "Самовывоз",
|
||||
};
|
||||
|
||||
export const getOrderGroupDeliveryStatusLabel = (status) =>
|
||||
DELIVERY_STATUS_LABELS[status] || status || "Ожидает подтверждения";
|
||||
Loading…
Reference in New Issue