feat: manual campaign tab shows order_groups instead of empty sms_campaign_log
- Manual campaign doesn't write to sms_campaign_log, so the tab was always empty - Now loads order_groups with notification_status='manual_required' - Shows: customer name, phone, first/second SMS timestamps, last update - Red 'не отправлена' badge for groups where second SMS was never sent - Also shows manual count on the campaign card
This commit is contained in:
parent
94f534ded3
commit
5e02f61567
|
|
@ -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,8 +153,31 @@ 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);
|
||||
|
||||
// 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")
|
||||
|
|
@ -165,6 +189,7 @@ export const SmsCampaignPanel = () => {
|
|||
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 = () => {
|
|||
</Panel>
|
||||
)}
|
||||
|
||||
{/* ── Manual campaign: groups table ─────────────────────────────────── */}
|
||||
{isManualCampaign ? (
|
||||
<>
|
||||
<Panel className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-semibold text-[var(--color-text-muted)]">В ручном управлении: {manualGroups.length}</span>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="min-w-[800px]">
|
||||
<div className="grid grid-cols-[minmax(160px,2fr)_minmax(120px,1fr)_minmax(120px,1fr)_minmax(120px,1fr)_minmax(100px,0.8fr)] gap-0 border-b border-[var(--color-border)] bg-[var(--color-surface-strong)] text-xs uppercase tracking-[0.08em] text-[var(--color-text-muted)]">
|
||||
<div className="px-3 py-1.5 font-medium">Клиент</div>
|
||||
<div className="px-3 py-1.5 font-medium">Телефон</div>
|
||||
<div className="px-3 py-1.5 font-medium">1-я SMS</div>
|
||||
<div className="px-3 py-1.5 font-medium">2-я SMS</div>
|
||||
<div className="px-3 py-1.5 font-medium">Обновлён</div>
|
||||
</div>
|
||||
{manualGroups.length === 0 ? (
|
||||
<div className="px-4 py-6 text-xs text-[var(--color-text-muted)]">Нет групп в ручном управлении</div>
|
||||
) : (
|
||||
manualGroups.map((group) => (
|
||||
<div
|
||||
key={group.id}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="px-3 py-1.5 text-[var(--color-text)]">
|
||||
<div className="font-medium">{group.customer_name || "—"}</div>
|
||||
{group.last_sms_error && <div className="mt-0.5 text-[10px] text-[var(--color-danger)]">{group.last_sms_error.slice(0, 60)}</div>}
|
||||
</div>
|
||||
<div className="px-3 py-1.5 text-[var(--color-text-muted)]">{fmtPhone(group.customer_phone)}</div>
|
||||
<div className="px-3 py-1.5 text-[var(--color-text-muted)]">
|
||||
{group.first_sms_sent_at ? fmtTime(group.first_sms_sent_at) : "—"}
|
||||
</div>
|
||||
<div className="px-3 py-1.5 text-[var(--color-text-muted)]">
|
||||
{group.second_sms_sent_at ? fmtTime(group.second_sms_sent_at) : <span className="text-[var(--color-danger)]">не отправлена</span>}
|
||||
</div>
|
||||
<div className="px-3 py-1.5 text-[var(--color-text-muted)]">
|
||||
{fmtTime(group.updated_at)}
|
||||
<div className="text-[10px]">{fmtElapsed(group.updated_at)}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* ── Stats ─────────────────────────────────────────────────────────── */}
|
||||
<Panel className="p-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
|
|
@ -649,6 +726,8 @@ export const SmsCampaignPanel = () => {
|
|||
↻ Обновить
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue