/** * @file AutocallStatusCard.jsx * @description Блок «Автодозвон» в карточке заказа. * Показывает попытки звонков, слот, кнопки «Позвонить сейчас» / «Не звонить». */ import React, { useState, useEffect, useCallback } from "react"; import { Badge } from "../UI/Badge"; import { supabase } from "../../supabaseClient"; import { resolveAutocallStatus, AUTOCALL_STATUS_META } from "../../services/orderGroupViews"; const OUTCOME_META = { confirmed: { label: "Согласовал", tone: "accent", icon: "✅" }, confirmed_kept_previous:{ label: "Правка не доведена", tone: "warning", icon: "🔁" }, confirmed_silent: { label: "Подтвердил молчанием", tone: "accent", icon: "🤫" }, no_answer: { label: "Не взял трубку", tone: "neutral", icon: "📵" }, no_input_day: { label: "Взял, не нажал (день)", tone: "warning", icon: "🤐" }, no_input_half: { label: "Взял, не нажал (время)", tone: "warning", icon: "🤐" }, operator: { label: "Просил менеджера", tone: "danger", icon: "👤" }, opt_out: { label: "Отказ от звонков", tone: "danger", icon: "🚫" }, invalid_number: { label: "Неверный номер", tone: "danger", icon: "❌" }, error: { label: "Ошибка", tone: "danger", icon: "⚠️" }, }; const DAY_LABEL = { 1: "завтра", 2: "послезавтра" }; const HALF_LABEL = { 1: "первая половина дня", 2: "вторая половина дня" }; const fmtTime = (ts) => { if (!ts) return "—"; try { return new Date(ts).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", }); } catch { return ts; } }; const slotText = (l) => { if (!l) return null; const day = DAY_LABEL[l.dtmf_day]; const half = HALF_LABEL[l.dtmf_half]; if (day && half) return `${day}, ${half}`; if (day) return day; return null; }; export const AutocallStatusCard = ({ order, userRole }) => { const [calls, setCalls] = useState([]); const [loading, setLoading] = useState(false); const [toggling, setToggling] = useState(false); const [queued, setQueued] = useState(false); const canManage = ["mega_admin", "logistician"].includes(userRole); const optOut = order.autocall_opt_out ?? order.autocallOptOut ?? false; const isAutocall = order.delivery_date_source === "autocall" || order.deliveryDateSource === "autocall"; const exclusionReason = order.autocall_exclusion_reason || order.autocallExclusionReason || null; const loadCalls = useCallback(async () => { if (!order?.id) return; setLoading(true); try { const { data, error } = await supabase .from("autocall_log") .select("*") .eq("order_group_id", order.id) .order("created_at", { ascending: false }) .limit(5); if (error) throw error; setCalls(data || []); } catch (e) { // silent } finally { setLoading(false); } }, [order?.id]); useEffect(() => { loadCalls(); }, [loadCalls]); const handleCallNow = async () => { if (!order?.id) return; setQueued(true); try { // Пишем в очередь — планировщик подхватит и создаст call-файл (без ssh из edge fn) const { error } = await supabase.from("autocall_queue").insert({ order_group_id: order.id, phone: order.customer_phone || order.customerPhone || "", scenario: "A", }); if (error) throw error; setTimeout(() => setQueued(false), 3000); } catch (e) { console.error("Queue call error:", e); setQueued(false); } }; const handleToggleOptOut = async () => { if (!order?.id) return; setToggling(true); try { const { error } = await supabase .from("order_groups") .update({ autocall_opt_out: !optOut }) .eq("id", order.id); if (error) throw error; // Обновим локально order.autocall_opt_out = !optOut; setToggling(false); } catch (e) { console.error("Toggle opt-out error:", e); setToggling(false); } }; const lastCall = calls[0]; const lastMeta = lastCall ? OUTCOME_META[lastCall.outcome] : null; const lastSlot = lastCall ? slotText(lastCall) : null; // Единый человеческий статус (по аналогии со статусом SMS) const statusKey = resolveAutocallStatus(calls, { optOut, exclusionReason }); const statusMeta = AUTOCALL_STATUS_META[statusKey] || AUTOCALL_STATUS_META.not_called; return (

📞 Автодозвон

{/* Единый статус — как у SMS */}
{statusMeta.label} {lastSlot && {lastSlot}}
{/* Пояснение исключения */} {exclusionReason && !optOut && (
🚫 Робот не звонит на этот номер по настройке: {exclusionReason}
)} {/* Попытки */} {calls.length > 0 ? (
{calls.map((c) => { const m = OUTCOME_META[c.outcome] || { label: c.outcome, tone: "neutral", icon: "•" }; const slot = slotText(c); return (
{fmtTime(c.created_at)} {m.icon} {m.label} {slot && {slot}} {c.was_test_mode && 🧪 тест}
); })}
) : (
Звонков не было
)} {/* Подсказка о правке */} {lastCall?.changed && (
⚠ Клиент правил выбор в звонке. При сомнении — перезвоните.
)} {/* Кнопки */} {canManage && (
)}
); };