diff --git a/admin/DashboardController.php b/admin/DashboardController.php index 7a6e020..8c82d2c 100644 --- a/admin/DashboardController.php +++ b/admin/DashboardController.php @@ -2026,24 +2026,29 @@ HTML; } $convRows = db()->query(" SELECT c.phone_number, - MAX(c.contact_name) as contact_name, - MAX(c.company_id) as company_id, - MAX(c.created_at) as last_time, - (SELECT content FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_message, - (SELECT COUNT(*) FROM conversations c3 WHERE c3.phone_number = c.phone_number AND c3.direction = 'inbound' - AND c3.id > COALESCE((SELECT MAX(c4.id) FROM conversations c4 WHERE c4.phone_number = c.phone_number AND c4.direction = 'outbound'),0)) as unread_count + MAX(c.contact_name) as contact_name, + MAX(c.company_id) as company_id, + MAX(c.created_at) as last_time, + (SELECT content FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_message, + (SELECT message_type FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_type, + (SELECT COUNT(*) FROM conversations c3 + WHERE c3.phone_number = c.phone_number AND c3.direction = 'inbound' + AND c3.created_at > COALESCE( + (SELECT MAX(c4.created_at) FROM conversations c4 + WHERE c4.phone_number = c.phone_number AND c4.direction = 'outbound'), '2000-01-01')) as unread_count FROM conversations c GROUP BY c.phone_number ORDER BY last_time DESC ")->fetchAll(); $initialConvs = []; foreach ($convRows as $r) { - $cid = (int)$r['company_id']; + $cid = (int)$r['company_id']; + $preview = mb_substr(self::extractContent($r['last_message'] ?? '', $r['last_type'] ?? 'text'), 0, 80); $initialConvs[] = [ 'phone' => $r['phone_number'], 'contact_name' => $r['contact_name'] ?? '', 'company_name' => $companyMap[$cid] ?? '', - 'last_message' => mb_substr($r['last_message'] ?? '', 0, 80), + 'last_message' => $preview, 'last_time' => $r['last_time'], 'unread_count' => (int)$r['unread_count'], ]; @@ -2373,6 +2378,54 @@ HTML; exit; } + // ─── Helper: extract human-readable text from raw conversation content ─── + + private static function extractContent(string $raw, string $type): string + { + if ($raw === '') return ''; + + // Inbound button_reply: "button_reply: {...}" + if (str_starts_with($raw, 'button_reply: ')) { + $j = json_decode(substr($raw, 14), true); + return $j['title'] ?? $raw; + } + + // Inbound list_reply: "list_reply: {...}" + if (str_starts_with($raw, 'list_reply: ')) { + $j = json_decode(substr($raw, 12), true); + return $j['title'] ?? $raw; + } + + // Try to parse as JSON + $j = json_decode($raw, true); + if (!is_array($j)) return $raw; + + // Outbound text: {"text": "..."} + if (isset($j['text']) && is_string($j['text'])) return $j['text']; + + // Outbound interactive: {"interactive": {"body": {"text":"..."}, "action":{...}}} + if (isset($j['interactive'])) { + $body = $j['interactive']['body']['text'] ?? ''; + $btns = []; + foreach ($j['interactive']['action']['buttons'] ?? [] as $b) { + $btns[] = $b['reply']['title'] ?? ''; + } + foreach ($j['interactive']['action']['sections'] ?? [] as $s) { + foreach ($s['rows'] ?? [] as $row) { + $btns[] = $row['title'] ?? ''; + } + } + $hint = $btns ? ' [' . implode(' | ', array_filter($btns)) . ']' : ''; + return $body . $hint; + } + + // Outbound image/document + if (isset($j['caption'])) return '📎 ' . $j['caption']; + if (isset($j['link'])) return '📎 ' . $j['link']; + + return $raw; + } + // ─── API: lista de conversaciones ──────────────────────────────────────── public static function chatConversations(): void @@ -2382,11 +2435,16 @@ HTML; // Get unique conversations with last message and unread count $rows = $db->query(" SELECT c.phone_number, - MAX(c.contact_name) as contact_name, - MAX(c.company_id) as company_id, - MAX(c.created_at) as last_time, - (SELECT content FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_message, - (SELECT COUNT(*) FROM conversations c3 WHERE c3.phone_number = c.phone_number AND c3.direction = 'inbound' AND c3.id > COALESCE((SELECT MAX(c4.id) FROM conversations c4 WHERE c4.phone_number = c.phone_number AND c4.direction = 'outbound'), 0)) as unread_count + MAX(c.contact_name) as contact_name, + MAX(c.company_id) as company_id, + MAX(c.created_at) as last_time, + (SELECT content FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_message, + (SELECT message_type FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_type, + (SELECT COUNT(*) FROM conversations c3 + WHERE c3.phone_number = c.phone_number AND c3.direction = 'inbound' + AND c3.created_at > COALESCE( + (SELECT MAX(c4.created_at) FROM conversations c4 + WHERE c4.phone_number = c.phone_number AND c4.direction = 'outbound'), '2000-01-01')) as unread_count FROM conversations c GROUP BY c.phone_number ORDER BY last_time DESC @@ -2399,12 +2457,14 @@ HTML; $conversations = []; foreach ($rows as $r) { - $cid = (int)$r['company_id']; + $cid = (int)$r['company_id']; + $raw = $r['last_message'] ?? ''; + $preview = mb_substr(self::extractContent($raw, $r['last_type'] ?? 'text'), 0, 80); $conversations[] = [ 'phone' => $r['phone_number'], 'contact_name' => $r['contact_name'] ?? '', 'company_name' => $companies[$cid] ?? '', - 'last_message' => mb_substr($r['last_message'] ?? '', 0, 80), + 'last_message' => $preview, 'last_time' => $r['last_time'], 'unread_count' => (int)$r['unread_count'], ]; @@ -2430,26 +2490,13 @@ HTML; $stmt->execute([$phone]); $messages = $stmt->fetchAll(); - // Also include outbound_queue items for this phone - $stmt2 = db()->prepare(" - SELECT 'outbound' as direction, message_type, payload as content, NULL as media_id, status, created_at - FROM outbound_queue - WHERE to_number = ? AND status IN ('sent','delivered','failed') - AND id > COALESCE((SELECT MAX(c.id) FROM conversations c WHERE c.phone_number = ?), 0) - ORDER BY id ASC - "); - $stmt2->execute([$phone, $phone]); - $queueMsgs = $stmt2->fetchAll(); - - foreach ($queueMsgs as &$qm) { - $pl = json_decode($qm['content'], true); - $qm['content'] = $pl['text'] ?? $pl['caption'] ?? $qm['content']; + // Extract readable text from raw WhatsApp payloads + foreach ($messages as &$m) { + $m['content'] = self::extractContent((string)($m['content'] ?? ''), (string)($m['message_type'] ?? 'text')); } + unset($m); - $all = array_merge($messages, $queueMsgs); - usort($all, fn($a, $b) => strcmp($a['created_at'] ?? '', $b['created_at'] ?? '')); - - jsonResponse(200, ['messages' => $all]); + jsonResponse(200, ['messages' => $messages]); } // ─── API: enviar mensaje desde el chat ─────────────────────────────────── @@ -2470,16 +2517,20 @@ HTML; $conv = $stmt->fetch(); $companyId = $conv ? (int)$conv['company_id'] : 1; - // Enqueue the message $db = db(); - $payload = json_encode(['text' => $text]); - $qStmt = $db->prepare("INSERT INTO outbound_queue (company_id, to_number, message_type, payload, status, created_at) VALUES (?, ?, 'text', ?, 'queued', NOW())"); - $qStmt->execute([$companyId, $phone, $payload]); - // Add to conversations + // Enqueue the message then process immediately so it's sent to WhatsApp right away + $payload = json_encode(['text' => $text]); + $db->prepare("INSERT INTO outbound_queue (company_id, to_number, message_type, payload, status, created_at) VALUES (?, ?, 'text', ?, 'queued', NOW())") + ->execute([$companyId, $phone, $payload]); + + // Add to conversations log $msgId = 'admin_' . time() . '_' . $phone; - $cStmt = $db->prepare("INSERT INTO conversations (company_id, message_id, phone_number, direction, message_type, content, created_at) VALUES (?, ?, ?, 'outbound', 'text', ?, NOW())"); - $cStmt->execute([$companyId, $msgId, $phone, $text]); + $db->prepare("INSERT INTO conversations (company_id, message_id, phone_number, direction, message_type, content, created_at) VALUES (?, ?, ?, 'outbound', 'text', ?, NOW())") + ->execute([$companyId, $msgId, $phone, $text]); + + // Send now — don't wait for the manual queue processor + OutboundWorker::processQueue(); jsonResponse(200, ['status' => 'sent']); }