fix: SMS status — show real delivery state from log, expandable SMS text in history

This commit is contained in:
root 2026-07-22 14:10:56 +00:00
parent a5817110c9
commit d11ccd4f76
1 changed files with 51 additions and 22 deletions

View File

@ -66,6 +66,38 @@ const fmtCountdown = (targetTs) => {
return `${m}м ${s}с`;
};
// SMS log entry (expandable to show text)
const SmsLogEntry = ({ log }) => {
const [expanded, setExpanded] = useState(false);
const hasText = !!log.sms_text;
return (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] px-2.5 py-1.5">
<div
className={`flex items-center gap-2 text-[11px] ${hasText ? "cursor-pointer" : ""}`}
onClick={hasText ? () => setExpanded(e => !e) : undefined}
>
<span className="text-[var(--color-text-muted)]">{fmtTime(log.created_at)}</span>
<Badge tone={log.status === "delivered" ? "accent" : log.status === "sent" || log.status === "checking" ? "info" : "danger"}>
{log.status === "delivered" ? "доставлено" : log.status === "sent" ? "отправлено" : log.status === "checking" ? "проверка" : log.status === "expired" ? "истёк" : log.status}
</Badge>
{log.campaign_type && (
<span className="text-[var(--color-text-muted)]">
{log.campaign_type === "first_sms" ? "1-е SMS" : log.campaign_type === "second_sms" ? "2-е SMS" : log.campaign_type === "manual" ? "ручное" : log.campaign_type === "paid_storage" ? "платное хранение" : log.campaign_type}
</span>
)}
{hasText && (
<span className="ml-auto text-[var(--color-accent)] text-[10px]">{expanded ? "▲" : "▼"}</span>
)}
</div>
{hasText && expanded && (
<div className="mt-1.5 rounded-md bg-[var(--color-surface-strong)] px-2.5 py-2 text-[11px] text-[var(--color-text)] leading-relaxed whitespace-pre-wrap">
{log.sms_text}
</div>
)}
</div>
);
};
// Component
export const SmsStatusCard = ({ order, userRole }) => {
const [restarting, setRestarting] = useState(false);
@ -170,10 +202,14 @@ export const SmsStatusCard = ({ order, userRole }) => {
};
const countdown = nextCheck ? fmtCountdown(nextCheck) : null;
// Determine SMS display state
const hasSmsSent = !!smsSentAt; // SMS physically sent (may not be delivered yet)
const hasFirstSms = !!firstSmsAt; // First SMS confirmed delivered (103)
const hasSecondSms = !!secondSmsAt; // Second SMS confirmed delivered (103)
// Determine SMS display state from sms_log (source of truth)
const firstSmsLog = smsLog.find(l => l.campaign_type === "first_sms");
const secondSmsLog = smsLog.find(l => l.campaign_type === "second_sms");
const isDelivered = (log) => log && (log.status === "delivered" || log.sms_code === "103");
const hasFirstSms = isDelivered(firstSmsLog);
const hasSecondSms = isDelivered(secondSmsLog);
const firstSmsFailed = firstSmsLog && ["expired", "error", "send_failed", "limit_exceeded"].includes(firstSmsLog.status);
const secondSmsFailed = secondSmsLog && ["expired", "error", "send_failed", "limit_exceeded"].includes(secondSmsLog.status);
return (
<Panel className="p-4">
@ -185,13 +221,15 @@ export const SmsStatusCard = ({ order, userRole }) => {
<div className="space-y-2 text-xs">
{/* 1st SMS */}
<div className="flex items-start gap-2">
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasFirstSms ? "bg-[#22c55e]" : hasSmsSent && notifStatus === "sms_sending" ? "bg-[var(--color-accent)]" : notifStatus === "link_ready" || notifStatus === "not_started" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasFirstSms ? "bg-[#22c55e]" : firstSmsFailed ? "bg-[var(--color-danger)]" : firstSmsAt ? "bg-[var(--color-accent)]" : notifStatus === "link_ready" || notifStatus === "not_started" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
<div className="flex-1">
<div className="text-[var(--color-text)]">1-е SMS</div>
{hasFirstSms ? (
<div className="text-[var(--color-text-muted)]">{fmtTime(firstSmsAt)} получено клиентом</div>
) : hasSmsSent && notifStatus === "sms_sending" ? (
<div className="text-[var(--color-text-muted)]">{fmtTime(smsSentAt)} · отправлено, ждём ответ оператора</div>
<div className="text-[var(--color-text-muted)]">{fmtTime(firstSmsLog.created_at)} получено клиентом</div>
) : firstSmsFailed ? (
<div className="text-[var(--color-danger)]">{fmtTime(firstSmsLog.created_at)} {firstSmsLog.status === "expired" ? "срок доставки истёк" : firstSmsLog.status}</div>
) : firstSmsAt ? (
<div className="text-[var(--color-text-muted)]">{fmtTime(firstSmsAt)} · отправлено, ждём ответ оператора</div>
) : notifStatus === "link_ready" ? (
<div className="text-[var(--color-text-muted)]">в очереди на отправку</div>
) : (
@ -202,11 +240,13 @@ export const SmsStatusCard = ({ order, userRole }) => {
{/* 2nd SMS */}
<div className="flex items-start gap-2">
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasSecondSms ? "bg-[#22c55e]" : notifStatus === "first_sms_sent" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
<span className={`mt-0.5 h-2 w-2 rounded-full ${hasSecondSms ? "bg-[#22c55e]" : secondSmsFailed ? "bg-[var(--color-danger)]" : notifStatus === "first_sms_sent" ? "bg-[var(--color-warning)]" : "bg-[var(--color-border)]"}`} />
<div className="flex-1">
<div className="text-[var(--color-text)]">2-е SMS</div>
{hasSecondSms ? (
<div className="text-[var(--color-text-muted)]">{fmtTime(secondSmsAt)} получено клиентом</div>
<div className="text-[var(--color-text-muted)]">{fmtTime(secondSmsLog.created_at)} получено клиентом</div>
) : secondSmsFailed ? (
<div className="text-[var(--color-danger)]">{fmtTime(secondSmsLog.created_at)} {secondSmsLog.status === "expired" ? "срок доставки истёк" : secondSmsLog.status}</div>
) : notifStatus === "first_sms_sent" && countdown ? (
<div className="text-[var(--color-text-muted)]">
отправка через <span className="font-mono text-[var(--color-accent)]">{countdown}</span>
@ -244,18 +284,7 @@ export const SmsStatusCard = ({ order, userRole }) => {
<div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">История SMS</div>
<div className="space-y-1.5">
{smsLog.map((log) => (
<div key={log.id} className="flex items-center gap-2 text-[11px]">
<span className="text-[var(--color-text-muted)]">{fmtTime(log.created_at)}</span>
<Badge tone={log.status === "delivered" ? "accent" : log.status === "sent" || log.status === "checking" ? "info" : "danger"}>
{log.status === "delivered" ? "доставлено" : log.status === "sent" ? "отправлено" : log.status === "checking" ? "проверка" : log.status === "expired" ? "истёк" : log.status}
</Badge>
{log.sent_to && (
<span className="text-[var(--color-text-muted)]"> {log.sent_to}</span>
)}
{log.sms_code && (
<span className="text-[var(--color-text-muted)]">код: {log.sms_code}</span>
)}
</div>
<SmsLogEntry key={log.id} log={log} />
))}
</div>
</div>