From 023df71d3c005c090feeebe0bef7a00ac304dcbb Mon Sep 17 00:00:00 2001 From: Selamawitk Date: Sun, 7 Sep 2025 18:15:31 +0300 Subject: [PATCH] Enhance SMS card UI from web folder --- .../(components)/message-history.tsx | 564 +++++++++--------- 1 file changed, 273 insertions(+), 291 deletions(-) diff --git a/web/app/(app)/dashboard/(components)/message-history.tsx b/web/app/(app)/dashboard/(components)/message-history.tsx index 3f24d91..41ce25e 100644 --- a/web/app/(app)/dashboard/(components)/message-history.tsx +++ b/web/app/(app)/dashboard/(components)/message-history.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState, useRef } from 'react' -import { useQuery } from '@tanstack/react-query' +import { useQuery, useMutation } from '@tanstack/react-query' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' @@ -16,6 +16,8 @@ import { Smartphone, RefreshCw, Timer, + Copy, + Trash2, } from 'lucide-react' import { Select, @@ -30,38 +32,89 @@ import { useForm, Controller } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { sendSmsSchema } from '@/lib/schemas' import type { SendSmsFormData } from '@/lib/schemas' -import { useMutation } from '@tanstack/react-query' -import { Spinner } from '@/components/ui/spinner' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, - DialogTrigger, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' +import { Spinner } from '@/components/ui/spinner' +import { toast } from 'sonner' -function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { - const [open, setOpen] = useState(false) +// Helper function to format timestamps +const formatTimestamp = (timestamp: string | null | undefined) => { + if (!timestamp) return 'N/A' + return new Date(timestamp).toLocaleString('en-US', { + hour: '2-digit', + minute: '2-digit', + day: 'numeric', + month: 'short', + year: 'numeric', + }) +} +// Helper to get status color and icon +const getStatusBadge = (status: string) => { + const normalizedStatus = status?.toLowerCase() || 'pending' + switch (normalizedStatus) { + case 'pending': + return { + color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300', + icon: , + label: 'Pending', + } + case 'sent': + return { + color: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', + icon: , + label: 'Sent', + } + case 'delivered': + return { + color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + icon: , + label: 'Delivered', + } + case 'failed': + return { + color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', + icon: , + label: 'Failed', + } + default: + return { + color: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300', + icon: , + label: normalizedStatus, + } + } +} + +function ReplyDialog({ sms, onClose, open, onOpenChange }: { sms: any; onClose?: () => void; open: boolean; onOpenChange: (open: boolean) => void }) { const { mutate: sendSms, isPending: isSendingSms, - error: sendSmsError, isSuccess: isSendSmsSuccess, } = useMutation({ mutationKey: ['send-sms'], mutationFn: (data: SendSmsFormData) => httpBrowserClient.post(ApiEndpoints.gateway.sendSMS(data.deviceId), data), onSuccess: () => { + toast.success('SMS sent successfully!') setTimeout(() => { - setOpen(false) + onOpenChange(false) if (onClose) onClose() }, 1500) }, + onError: (error: any) => { + toast.error('Failed to send SMS.', { + description: error.response?.data?.message || 'Please try again.', + }) + }, }) const { @@ -79,12 +132,9 @@ function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { }, }) - const { data: devices, isLoading: isLoadingDevices } = useQuery({ + const { data: devices } = useQuery({ queryKey: ['devices'], - queryFn: () => - httpBrowserClient - .get(ApiEndpoints.gateway.listDevices()) - .then((res) => res.data), + queryFn: () => httpBrowserClient.get(ApiEndpoints.gateway.listDevices()).then((res) => res.data), }) useEffect(() => { @@ -98,25 +148,19 @@ function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { }, [open, sms, reset]) return ( - - - - + - + Reply to {sms.sender} - Send a reply message to this sender + Send a reply message to this sender.
handleSubmit((data) => sendSms(data))(e)} + onSubmit={handleSubmit((data) => sendSms(data))} className='space-y-4 mt-4' >
@@ -134,7 +178,7 @@ function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) { - {devices?.data?.map((device) => ( + {devices?.data?.map((device: any) => ( {device.brand} {device.model}{' '} {device.enabled ? '' : '(disabled)'} @@ -150,7 +194,6 @@ function ReplyDialog({ sms, onClose }: { sms: any; onClose?: () => void }) {

)}
-
void }) {

)}
-