From b469a09cdee0baee24512c178c1814b49ea035cc Mon Sep 17 00:00:00 2001 From: debu-2 Date: Wed, 7 Oct 2026 12:15:48 +0530 Subject: [PATCH] UI finish --- .../src/features/admin/AdminWhatsAppPage.tsx | 518 ++++++++++++------ frontend/src/index.css | 265 +++++++-- 2 files changed, 552 insertions(+), 231 deletions(-) diff --git a/frontend/src/features/admin/AdminWhatsAppPage.tsx b/frontend/src/features/admin/AdminWhatsAppPage.tsx index be3a75f..4fa877a 100644 --- a/frontend/src/features/admin/AdminWhatsAppPage.tsx +++ b/frontend/src/features/admin/AdminWhatsAppPage.tsx @@ -1,4 +1,4 @@ -import { MessageSquareText, RefreshCw, Settings2 } from 'lucide-react' +import { MessageSquareText, RefreshCw, Search, Send, Settings2, X } from 'lucide-react' import { useEffect, useMemo, useRef, useState } from 'react' import type { FormEvent } from 'react' import { API_BASE_URL } from '../../lib/apiBase' @@ -26,6 +26,42 @@ function mediaUrl(mediaId: string) { return `${API_BASE_URL}/api/v1/admin/whatsapp/media/${mediaId}` } +function peerInitials(value: string) { + const digits = value.replace(/\D/g, '') + return digits.slice(-2) || 'WA' +} + +function formatChatTime(value: string) { + const date = new Date(value) + const now = new Date() + const sameDay = date.toDateString() === now.toDateString() + if (sameDay) { + return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) + } + const yesterday = new Date(now) + yesterday.setDate(now.getDate() - 1) + if (date.toDateString() === yesterday.toDateString()) return 'Yesterday' + return date.toLocaleDateString([], { month: 'short', day: 'numeric' }) +} + +function formatDayLabel(value: string) { + const date = new Date(value) + const now = new Date() + if (date.toDateString() === now.toDateString()) return 'Today' + const yesterday = new Date(now) + yesterday.setDate(now.getDate() - 1) + if (date.toDateString() === yesterday.toDateString()) return 'Yesterday' + return date.toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' }) +} + +const SEND_MODES: { id: SendMode; label: string }[] = [ + { id: 'text', label: 'Text' }, + { id: 'template', label: 'Template' }, + { id: 'media', label: 'Media' }, + { id: 'buttons', label: 'Buttons' }, + { id: 'reaction', label: 'Reaction' }, +] + export function AdminWhatsAppPage() { const [config, setConfig] = useState(null) const [messages, setMessages] = useState([]) @@ -57,8 +93,10 @@ export function AdminWhatsAppPage() { const [buttonThree, setButtonThree] = useState('') const [reactionEmoji, setReactionEmoji] = useState('👍') const [reactionTarget, setReactionTarget] = useState('') + const [threadQuery, setThreadQuery] = useState('') const chatEndRef = useRef(null) + const replyRef = useRef(null) async function loadMessages(peer?: string | null) { const items = await adminApi.listWhatsAppMessages(300, peer || undefined) @@ -104,12 +142,24 @@ export function AdminWhatsAppPage() { .sort((a, b) => (b.latest?.created_at ?? '').localeCompare(a.latest?.created_at ?? '')) }, [messages]) + const visibleThreads = useMemo(() => { + const query = threadQuery.trim().toLowerCase() + if (!query) return threads + return threads.filter(({ peer, latest }) => { + const haystack = `${formatWaNumber(peer)} ${peer} ${latest.body ?? ''} ${latest.message_type}`.toLowerCase() + return haystack.includes(query) + }) + }, [threads, threadQuery]) + const activeThread = useMemo(() => { if (!selectedPeer) return [] const found = threads.find((item) => item.peer === selectedPeer) return found?.thread ?? [] }, [threads, selectedPeer]) + const activeLatest = activeThread[activeThread.length - 1] + const activeFirst = activeThread[0] + useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [activeThread.length, selectedPeer]) @@ -271,18 +321,28 @@ export function AdminWhatsAppPage() { )}
-