From 04f78f73672acaa6c59ffbf35f589bcc4b1f9b67 Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 9 Sep 2026 14:49:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(filters):=20=D0=B4=D0=B2=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D1=82=D1=80=D0=B0=20=C2=AB=D0=A1=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=81=20SMS=C2=BB=20=D0=B8=20=C2=AB=D0=A1=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D1=83=D1=81=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B4=D0=BE=D0=B7?= =?UTF-8?q?=D0=B2=D0=BE=D0=BD=D0=B0=C2=BB=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=20=C2=AB=D0=9F=D1=80=D0=BE=D0=B7=D0=B2=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logistics/LogisticsReadinessBoard.jsx | 4 +- src/components/orders/OrderFilters.jsx | 36 +++++++------ src/services/orderGroupViews.js | 51 ++++++++++++------- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/components/logistics/LogisticsReadinessBoard.jsx b/src/components/logistics/LogisticsReadinessBoard.jsx index 2dfecfa..60553ca 100644 --- a/src/components/logistics/LogisticsReadinessBoard.jsx +++ b/src/components/logistics/LogisticsReadinessBoard.jsx @@ -299,7 +299,7 @@ const isFilterActive = (filters) => { if (filters.deliveryStatus && filters.deliveryStatus !== "all") return true; if (filters.deliveryHalfDay && filters.deliveryHalfDay !== "all") return true; if (filters.smsStatus && filters.smsStatus !== "all") return true; - if (filters.callStatus && filters.callStatus !== "all") return true; + if (filters.autocallStatus && filters.autocallStatus !== "all") return true; if (filters.dateFrom) return true; if (filters.dateTo) return true; return false; @@ -405,7 +405,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], totalGroups: totalGr // Reset to page 1 when filters change React.useEffect(() => { setPage(1); - }, [filters.query, filters.displayStatus, filters.city, filters.deliveryStatus, filters.deliveryHalfDay, filters.smsStatus, filters.callStatus, filters.dateFrom, filters.dateTo]); + }, [filters.query, filters.displayStatus, filters.city, filters.deliveryStatus, filters.deliveryHalfDay, filters.smsStatus, filters.autocallStatus, filters.dateFrom, filters.dateTo]); const totalGroups = rankedGroups.length; const totalPages = Math.max(1, Math.ceil(totalGroups / PAGE_SIZE)); diff --git a/src/components/orders/OrderFilters.jsx b/src/components/orders/OrderFilters.jsx index b5cb003..738fb3c 100644 --- a/src/components/orders/OrderFilters.jsx +++ b/src/components/orders/OrderFilters.jsx @@ -121,17 +121,23 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities = { value: "pickup", label: "Самовывоз" }, ]; - const smsProblemOptions = [ - { value: "all", label: "Проблемы SMS" }, - { value: "problem_not_sent", label: "SMS не отправлено" }, - { value: "problem_not_delivered", label: "SMS не доставлено" }, + const smsStatusOptions = [ + { value: "all", label: "Статус SMS" }, + { value: "not_sent", label: "SMS: не отправлено" }, + { value: "sent", label: "SMS: отправлено" }, + { value: "delivered", label: "SMS: доставлено" }, + { value: "error", label: "SMS: ошибка" }, ]; - const callStatusOptions = [ - { value: "all", label: "Прозвонить" }, - { value: "need_call", label: "Нужно прозвонить" }, - { value: "sms_problem", label: "Проблема с SMS" }, - { value: "autocall_problem", label: "Проблема с автодозвоном" }, + const autocallStatusOptions = [ + { value: "all", label: "Статус автодозвона" }, + { value: "not_called", label: "Автодозвон: не звонили" }, + { value: "confirmed", label: "Автодозвон: согласовано" }, + { value: "no_answer", label: "Автодозвон: не дозвонились" }, + { value: "no_input", label: "Автодозвон: не нажал в меню" }, + { value: "manual", label: "Автодозвон: требует ручного управления" }, + { value: "opt_out", label: "Автодозвон: отказ от звонков" }, + { value: "excluded", label: "Автодозвон: не звоним (исключение)" }, ]; return ( @@ -235,16 +241,16 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities = updateFilter("smsStatus", v)} - placeholder="Проблемы SMS" + placeholder="Статус SMS" /> updateFilter("callStatus", v)} - placeholder="Прозвонить" + value={filters.autocallStatus || "all"} + options={autocallStatusOptions} + onChange={(v) => updateFilter("autocallStatus", v)} + placeholder="Статус автодозвона" /> {typeof onToggleHideCompleted === "function" && ( diff --git a/src/services/orderGroupViews.js b/src/services/orderGroupViews.js index 8977c6d..13b81f7 100644 --- a/src/services/orderGroupViews.js +++ b/src/services/orderGroupViews.js @@ -245,22 +245,38 @@ export const parseGroupDate = (value) => { }; /** - * SMS delivery problem tone used for both table highlighting and filtering. - * Returns "ok" | "not_sent" | "not_delivered". + * SMS status used for both table highlighting and filtering. + * Returns "not_sent" | "sent" | "delivered" | "error". */ -export const getSmsProblemTone = (group) => { - if (!group) return "ok"; +export const getSmsStatus = (group) => { + if (!group) return "not_sent"; const firstSms = group.firstSmsSentAt || group.first_sms_sent_at; const secondSms = group.secondSmsSentAt || group.second_sms_sent_at; const lastError = group.lastSmsError; - // Sent an SMS (1st or 2nd) and it has a recorded delivery error → problem with delivery. - if ((firstSms || secondSms) && lastError) return "not_delivered"; + // Sent an SMS (1st or 2nd) and it has a recorded delivery error → error. + if ((firstSms || secondSms) && lastError) return "error"; // SMS should be expected but not sent at all yet. if (!firstSms && !secondSms) return "not_sent"; // Sent but no error recorded — assume delivered / in progress. + return "delivered"; +}; + +// Для обратной совместимости +export const getSmsProblemTone = (group) => { + const s = getSmsStatus(group); + if (s === "error") return "not_delivered"; + if (s === "not_sent") return "not_sent"; return "ok"; }; +// ─── Статус SMS: человеческие значения для фильтра ───────────────── +export const SMS_STATUS_META = { + not_sent: { label: "Не отправлено", tone: "neutral" }, + sent: { label: "Отправлено", tone: "info" }, + delivered: { label: "Доставлено", tone: "accent" }, + error: { label: "Ошибка", tone: "danger" }, +}; + // ─── Автодозвон: единый человеческий статус (по аналогии со статусом SMS) ── // Принимает массив autocall_log (отсортированный по created_at desc) или null. export const AUTOCALL_STATUS_META = { @@ -294,7 +310,7 @@ export const filterOrderGroups = (groups, filters = {}) => { const dateTo = normalizeDate(filters.dateTo); const deliveryHalfDay = normalizeDate(filters.deliveryHalfDay || filters.timeSlot || "all"); const smsStatus = filters.smsStatus || "all"; - const callStatus = filters.callStatus || "all"; + const autocallStatus = filters.autocallStatus || "all"; const isWithinDateRange = (group) => { const deliveryDate = parseGroupDate(getDeliveryDate(group)); @@ -387,23 +403,20 @@ export const filterOrderGroups = (groups, filters = {}) => { } if (smsStatus !== "all") { - const tone = getSmsProblemTone(group); - if (smsStatus === "problem_not_sent" && tone !== "not_sent") return false; - if (smsStatus === "problem_not_delivered" && tone !== "not_delivered") return false; + const status = getSmsStatus(group); + if (smsStatus === "not_sent" && status !== "not_sent") return false; + if (smsStatus === "sent" && status !== "sent") return false; + if (smsStatus === "delivered" && status !== "delivered") return false; + if (smsStatus === "error" && status !== "error") return false; } - // Фильтр «прозвонить»: SMS + автодозвон - if (callStatus !== "all") { - const smsTone = getSmsProblemTone(group); - const autocallStatus = group.autocall_status || group.autocallStatus || resolveAutocallStatus([], { + // Фильтр «Статус автодозвона» + if (autocallStatus !== "all") { + const acStatus = group.autocall_status || group.autocallStatus || resolveAutocallStatus([], { optOut: group.autocall_opt_out, exclusionReason: group.autocall_exclusion_reason, }); - const smsNotDone = smsTone === "not_sent" || smsTone === "not_delivered"; - const callNotDone = ["not_called", "no_answer", "no_input", "manual"].includes(autocallStatus); - if (callStatus === "need_call" && !(smsNotDone || callNotDone)) return false; - if (callStatus === "sms_problem" && !smsNotDone) return false; - if (callStatus === "autocall_problem" && !callNotDone) return false; + if (acStatus !== autocallStatus) return false; } if (!query) {