feat(filters): два отдельных фильтра «Статус SMS» и «Статус автодозвона» вместо «Прозвонить»

This commit is contained in:
Hermes 2026-09-09 14:49:31 +02:00
parent 587e12f562
commit 04f78f7367
3 changed files with 55 additions and 36 deletions

View File

@ -299,7 +299,7 @@ const isFilterActive = (filters) => {
if (filters.deliveryStatus && filters.deliveryStatus !== "all") return true; if (filters.deliveryStatus && filters.deliveryStatus !== "all") return true;
if (filters.deliveryHalfDay && filters.deliveryHalfDay !== "all") return true; if (filters.deliveryHalfDay && filters.deliveryHalfDay !== "all") return true;
if (filters.smsStatus && filters.smsStatus !== "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.dateFrom) return true;
if (filters.dateTo) return true; if (filters.dateTo) return true;
return false; return false;
@ -405,7 +405,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], totalGroups: totalGr
// Reset to page 1 when filters change // Reset to page 1 when filters change
React.useEffect(() => { React.useEffect(() => {
setPage(1); 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 totalGroups = rankedGroups.length;
const totalPages = Math.max(1, Math.ceil(totalGroups / PAGE_SIZE)); const totalPages = Math.max(1, Math.ceil(totalGroups / PAGE_SIZE));

View File

@ -121,17 +121,23 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities =
{ value: "pickup", label: "Самовывоз" }, { value: "pickup", label: "Самовывоз" },
]; ];
const smsProblemOptions = [ const smsStatusOptions = [
{ value: "all", label: "Проблемы SMS" }, { value: "all", label: "Статус SMS" },
{ value: "problem_not_sent", label: "SMS не отправлено" }, { value: "not_sent", label: "SMS: не отправлено" },
{ value: "problem_not_delivered", label: "SMS не доставлено" }, { value: "sent", label: "SMS: отправлено" },
{ value: "delivered", label: "SMS: доставлено" },
{ value: "error", label: "SMS: ошибка" },
]; ];
const callStatusOptions = [ const autocallStatusOptions = [
{ value: "all", label: "Прозвонить" }, { value: "all", label: "Статус автодозвона" },
{ value: "need_call", label: "Нужно прозвонить" }, { value: "not_called", label: "Автодозвон: не звонили" },
{ value: "sms_problem", label: "Проблема с SMS" }, { value: "confirmed", label: "Автодозвон: согласовано" },
{ value: "autocall_problem", label: "Проблема с автодозвоном" }, { value: "no_answer", label: "Автодозвон: не дозвонились" },
{ value: "no_input", label: "Автодозвон: не нажал в меню" },
{ value: "manual", label: "Автодозвон: требует ручного управления" },
{ value: "opt_out", label: "Автодозвон: отказ от звонков" },
{ value: "excluded", label: "Автодозвон: не звоним (исключение)" },
]; ];
return ( return (
@ -235,16 +241,16 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities =
<DropdownSelect <DropdownSelect
value={filters.smsStatus || "all"} value={filters.smsStatus || "all"}
options={smsProblemOptions} options={smsStatusOptions}
onChange={(v) => updateFilter("smsStatus", v)} onChange={(v) => updateFilter("smsStatus", v)}
placeholder="Проблемы SMS" placeholder="Статус SMS"
/> />
<DropdownSelect <DropdownSelect
value={filters.callStatus || "all"} value={filters.autocallStatus || "all"}
options={callStatusOptions} options={autocallStatusOptions}
onChange={(v) => updateFilter("callStatus", v)} onChange={(v) => updateFilter("autocallStatus", v)}
placeholder="Прозвонить" placeholder="Статус автодозвона"
/> />
{typeof onToggleHideCompleted === "function" && ( {typeof onToggleHideCompleted === "function" && (

View File

@ -245,22 +245,38 @@ export const parseGroupDate = (value) => {
}; };
/** /**
* SMS delivery problem tone used for both table highlighting and filtering. * SMS status used for both table highlighting and filtering.
* Returns "ok" | "not_sent" | "not_delivered". * Returns "not_sent" | "sent" | "delivered" | "error".
*/ */
export const getSmsProblemTone = (group) => { export const getSmsStatus = (group) => {
if (!group) return "ok"; if (!group) return "not_sent";
const firstSms = group.firstSmsSentAt || group.first_sms_sent_at; const firstSms = group.firstSmsSentAt || group.first_sms_sent_at;
const secondSms = group.secondSmsSentAt || group.second_sms_sent_at; const secondSms = group.secondSmsSentAt || group.second_sms_sent_at;
const lastError = group.lastSmsError; const lastError = group.lastSmsError;
// Sent an SMS (1st or 2nd) and it has a recorded delivery error → problem with delivery. // Sent an SMS (1st or 2nd) and it has a recorded delivery error → error.
if ((firstSms || secondSms) && lastError) return "not_delivered"; if ((firstSms || secondSms) && lastError) return "error";
// SMS should be expected but not sent at all yet. // SMS should be expected but not sent at all yet.
if (!firstSms && !secondSms) return "not_sent"; if (!firstSms && !secondSms) return "not_sent";
// Sent but no error recorded — assume delivered / in progress. // 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"; 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) ── // ─── Автодозвон: единый человеческий статус (по аналогии со статусом SMS) ──
// Принимает массив autocall_log (отсортированный по created_at desc) или null. // Принимает массив autocall_log (отсортированный по created_at desc) или null.
export const AUTOCALL_STATUS_META = { export const AUTOCALL_STATUS_META = {
@ -294,7 +310,7 @@ export const filterOrderGroups = (groups, filters = {}) => {
const dateTo = normalizeDate(filters.dateTo); const dateTo = normalizeDate(filters.dateTo);
const deliveryHalfDay = normalizeDate(filters.deliveryHalfDay || filters.timeSlot || "all"); const deliveryHalfDay = normalizeDate(filters.deliveryHalfDay || filters.timeSlot || "all");
const smsStatus = filters.smsStatus || "all"; const smsStatus = filters.smsStatus || "all";
const callStatus = filters.callStatus || "all"; const autocallStatus = filters.autocallStatus || "all";
const isWithinDateRange = (group) => { const isWithinDateRange = (group) => {
const deliveryDate = parseGroupDate(getDeliveryDate(group)); const deliveryDate = parseGroupDate(getDeliveryDate(group));
@ -387,23 +403,20 @@ export const filterOrderGroups = (groups, filters = {}) => {
} }
if (smsStatus !== "all") { if (smsStatus !== "all") {
const tone = getSmsProblemTone(group); const status = getSmsStatus(group);
if (smsStatus === "problem_not_sent" && tone !== "not_sent") return false; if (smsStatus === "not_sent" && status !== "not_sent") return false;
if (smsStatus === "problem_not_delivered" && tone !== "not_delivered") 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") { if (autocallStatus !== "all") {
const smsTone = getSmsProblemTone(group); const acStatus = group.autocall_status || group.autocallStatus || resolveAutocallStatus([], {
const autocallStatus = group.autocall_status || group.autocallStatus || resolveAutocallStatus([], {
optOut: group.autocall_opt_out, optOut: group.autocall_opt_out,
exclusionReason: group.autocall_exclusion_reason, exclusionReason: group.autocall_exclusion_reason,
}); });
const smsNotDone = smsTone === "not_sent" || smsTone === "not_delivered"; if (acStatus !== autocallStatus) return false;
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 (!query) { if (!query) {