diff --git a/src/components/admin/SmsCampaignPanel.jsx b/src/components/admin/SmsCampaignPanel.jsx index c7aea9f..8f0a1b8 100644 --- a/src/components/admin/SmsCampaignPanel.jsx +++ b/src/components/admin/SmsCampaignPanel.jsx @@ -109,6 +109,7 @@ export const SmsCampaignPanel = () => { const navigate = useNavigate(); const [activeCampaign, setActiveCampaign] = useState("first_sms"); const [logs, setLogs] = useState([]); + const [manualGroups, setManualGroups] = useState([]); const [settings, setSettings] = useState(null); const [allSettings, setAllSettings] = useState({}); const [campaignCounts, setCampaignCounts] = useState({}); @@ -152,19 +153,43 @@ export const SmsCampaignPanel = () => { if (r.status === "sent" || r.status === "checking") counts[r.campaign_type].sent++; if (["send_failed", "error", "limit_exceeded"].includes(r.status)) counts[r.campaign_type].errors++; }); + + // Also count manual_required groups for the manual card + const { count: manualCount, error: manualCountErr } = await supabase + .from("order_groups") + .select("id", { count: "exact", head: true }) + .eq("notification_status", "manual_required"); + if (!manualCountErr && manualCount != null) { + counts["manual"] = counts["manual"] || { total: 0, delivered: 0, sent: 0, errors: 0 }; + counts["manual"].total = manualCount; + } setCampaignCounts(counts); - // Load logs for active campaign - let query = supabase - .from("sms_campaign_log") - .select("*") - .eq("campaign_type", activeCampaign) - .order("created_at", { ascending: false }) - .limit(200); - if (filter !== "all") query = query.eq("status", filter); - const { data: logData, error: logError } = await query; - if (logError) throw logError; - setLogs(logData || []); + // For "manual" campaign: load order_groups with manual_required status + if (activeCampaign === "manual") { + const { data: manualData, error: manualErr } = await supabase + .from("order_groups") + .select("id, customer_name, customer_phone, notification_status, first_sms_sent_at, second_sms_sent_at, next_notification_check_at, sms_attempts, last_sms_error, delivery_status, delivery_link, created_at, updated_at") + .eq("notification_status", "manual_required") + .order("updated_at", { ascending: false }) + .limit(200); + if (manualErr) throw manualErr; + setManualGroups(manualData || []); + setLogs([]); // No SMS logs for manual campaign + } else { + setManualGroups([]); + // Load logs for active campaign + let query = supabase + .from("sms_campaign_log") + .select("*") + .eq("campaign_type", activeCampaign) + .order("created_at", { ascending: false }) + .limit(200); + if (filter !== "all") query = query.eq("status", filter); + const { data: logData, error: logError } = await query; + if (logError) throw logError; + setLogs(logData || []); + } // Load settings for active campaign if (HAS_SETTINGS.includes(activeCampaign)) { @@ -550,6 +575,58 @@ export const SmsCampaignPanel = () => { )} + {/* ── Manual campaign: groups table ─────────────────────────────────── */} + {isManualCampaign ? ( + <> + +
+ В ручном управлении: {manualGroups.length} +
+
+ + +
+
+
+
Клиент
+
Телефон
+
1-я SMS
+
2-я SMS
+
Обновлён
+
+ {manualGroups.length === 0 ? ( +
Нет групп в ручном управлении
+ ) : ( + manualGroups.map((group) => ( +
handleOpenGroup(group.id)} + className="grid grid-cols-[minmax(160px,2fr)_minmax(120px,1fr)_minmax(120px,1fr)_minmax(120px,1fr)_minmax(100px,0.8fr)] gap-0 border-t border-[var(--color-border)] text-xs hover:bg-[var(--color-accent-soft)] cursor-pointer" + > +
+
{group.customer_name || "—"}
+ {group.last_sms_error &&
{group.last_sms_error.slice(0, 60)}
} +
+
{fmtPhone(group.customer_phone)}
+
+ {group.first_sms_sent_at ? fmtTime(group.first_sms_sent_at) : "—"} +
+
+ {group.second_sms_sent_at ? fmtTime(group.second_sms_sent_at) : не отправлена} +
+
+ {fmtTime(group.updated_at)} +
{fmtElapsed(group.updated_at)}
+
+
+ )) + )} +
+
+
+ + ) : ( + <> {/* ── Stats ─────────────────────────────────────────────────────────── */}
@@ -649,6 +726,8 @@ export const SmsCampaignPanel = () => { ↻ Обновить
+ + )} ); };