From b5e524aafd6d24ac0d6579086fc843b5206ac675 Mon Sep 17 00:00:00 2001 From: isra el Date: Tue, 18 Mar 2025 08:04:24 +0300 Subject: [PATCH] feat(web): allow auto-refreshing of message history in the dashboard --- .../(components)/message-history.tsx | 307 +++++++++++++----- 1 file changed, 225 insertions(+), 82 deletions(-) diff --git a/web/app/(app)/dashboard/(components)/message-history.tsx b/web/app/(app)/dashboard/(components)/message-history.tsx index 8d73c95..5b95324 100644 --- a/web/app/(app)/dashboard/(components)/message-history.tsx +++ b/web/app/(app)/dashboard/(components)/message-history.tsx @@ -1,11 +1,22 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { useQuery } from '@tanstack/react-query' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' -import { Clock, Reply, ArrowUpRight, ArrowDownLeft, MessageSquare, Check, X, Smartphone } from 'lucide-react' +import { + Clock, + Reply, + ArrowUpRight, + ArrowDownLeft, + MessageSquare, + Check, + X, + Smartphone, + RefreshCw, + Timer, +} from 'lucide-react' import { Select, SelectContent, @@ -31,7 +42,7 @@ import { } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' -import { Badge } from "@/components/ui/badge" +import { Badge } from '@/components/ui/badge' function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { const [open, setOpen] = useState(false) @@ -201,7 +212,13 @@ function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { ) } -function FollowUpDialog({ message, onClose }: { message: any; onClose?: () => void }) { +function FollowUpDialog({ + message, + onClose, +}: { + message: any + onClose?: () => void +}) { const [open, setOpen] = useState(false) const { @@ -231,7 +248,11 @@ function FollowUpDialog({ message, onClose }: { message: any; onClose?: () => vo resolver: zodResolver(sendSmsSchema), defaultValues: { deviceId: message?.device?._id, - recipients: [message.recipient || (message.recipients && message.recipients[0]) || ''], + recipients: [ + message.recipient || + (message.recipients && message.recipients[0]) || + '', + ], message: '', }, }) @@ -248,7 +269,11 @@ function FollowUpDialog({ message, onClose }: { message: any; onClose?: () => vo if (open) { reset({ deviceId: message?.device?._id, - recipients: [message.recipient || (message.recipients && message.recipients[0]) || ''], + recipients: [ + message.recipient || + (message.recipients && message.recipients[0]) || + '', + ], message: '', }) } @@ -266,7 +291,10 @@ function FollowUpDialog({ message, onClose }: { message: any; onClose?: () => vo - Follow Up with {message.recipient || (message.recipients && message.recipients[0]) || 'Recipient'} + Follow Up with{' '} + {message.recipient || + (message.recipients && message.recipients[0]) || + 'Recipient'} Send a follow-up message to this recipient @@ -372,7 +400,9 @@ function FollowUpDialog({ message, onClose }: { message: any; onClose?: () => vo function MessageCard({ message, type }) { const isSent = type === 'sent' - const formattedDate = new Date((isSent ? message.requestedAt : message.receivedAt) || message.createdAt).toLocaleString('en-US', { + const formattedDate = new Date( + (isSent ? message.requestedAt : message.receivedAt) || message.createdAt + ).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', day: 'numeric', @@ -380,10 +410,14 @@ function MessageCard({ message, type }) { year: 'numeric', }) - - return ( - +
@@ -391,7 +425,12 @@ function MessageCard({ message, type }) { {isSent ? (
- To: {message.recipient || (message.recipients && message.recipients[0]) || 'Unknown'} + + To:{' '} + {message.recipient || + (message.recipients && message.recipients[0]) || + 'Unknown'} +
) : (
@@ -415,7 +454,7 @@ function MessageCard({ message, type }) {
)} - + {isSent && (
@@ -460,6 +499,9 @@ export default function MessageHistory() { const [messageType, setMessageType] = useState('all') const [page, setPage] = useState(1) const [limit, setLimit] = useState(20) + const [autoRefreshInterval, setAutoRefreshInterval] = useState(0) // 0 means no auto-refresh + const [isRefreshing, setIsRefreshing] = useState(false) + const refreshTimerRef = useRef(null) useEffect(() => { if (devices?.data?.length) { @@ -472,21 +514,57 @@ export default function MessageHistory() { data: messagesResponse, isLoading: isLoadingMessages, error: messagesError, + refetch, } = useQuery({ queryKey: ['messages-history', currentDevice, messageType, page, limit], enabled: !!currentDevice, queryFn: () => httpBrowserClient .get( - `${ApiEndpoints.gateway.getMessages(currentDevice)}?type=${messageType}&page=${page}&limit=${limit}` + `${ApiEndpoints.gateway.getMessages( + currentDevice + )}?type=${messageType}&page=${page}&limit=${limit}` ) .then((res) => res.data), }) - + // Handle manual refresh + const handleRefresh = async () => { + if (!currentDevice) return // Don't refresh if no device is selected + + setIsRefreshing(true) + await refetch() + setTimeout(() => setIsRefreshing(false), 500) // Show refresh animation for at least 500ms + } + + // Setup auto-refresh timer + useEffect(() => { + // Clear any existing timer + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current) + refreshTimerRef.current = null + } + + // Set up new timer if interval > 0 + if (autoRefreshInterval > 0 && currentDevice) { + refreshTimerRef.current = setInterval(() => { + refetch() + // Brief visual feedback that refresh happened + setIsRefreshing(true) + setTimeout(() => setIsRefreshing(false), 300) + }, autoRefreshInterval * 1000) + } + + // Cleanup on unmount + return () => { + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current) + } + } + }, [autoRefreshInterval, currentDevice, messageType, page, limit, refetch]) const messages = messagesResponse?.data || [] - + const pagination = messagesResponse?.meta || { page: 1, limit: 20, @@ -536,60 +614,131 @@ export default function MessageHistory() { return (
-
-
-
-
- -

Device

+
+
+
+
+
+ +

Device

+
+
- + + + + + +
+
+ All Messages
- ))} -
- + +
+
+ Received +
+
+ +
+
+ Sent +
+
+ + +
- -
-
- -

Message Type

+ + {/* Refresh Controls */} +
+
+ + + {/* {messagesResponse && ( + + Updated: {new Date().toLocaleTimeString()} + + )} */} +
+ +
+ + Auto Refresh: + +
+ {[ + { value: 0, label: 'Off' }, + { value: 15, label: '15s' }, + { value: 30, label: '30s' }, + { value: 60, label: '60s' }, + ].map((interval) => ( + + ))} +
-
@@ -608,7 +757,7 @@ export default function MessageHistory() {
)} - {(!isLoadingDevices && !messages) && ( + {!isLoadingDevices && !messages && (
No messages found
@@ -616,10 +765,10 @@ export default function MessageHistory() {
{messages?.map((message) => ( - ))}
@@ -677,10 +826,7 @@ export default function MessageHistory() { } // Ensure page is within bounds and not the first or last page - if ( - pageToShow > 1 && - pageToShow < pagination.totalPages - ) { + if (pageToShow > 1 && pageToShow < pagination.totalPages) { return (
) -} \ No newline at end of file +}