'use client'; import { useMemo, useState, useEffect } from 'react'; import { Star, RefreshCw, Search, ShieldCheck, Image as ImageIcon } from 'lucide-react'; import { toast } from 'sonner'; import { TablePagination, useClientPagination } from '@/components/ui/TablePagination'; import { adminService } from '@/services/api/adminService'; interface ReviewItem { review_id: string; product_id: string; author_name: string; author_email?: string; rating: number; title: string; comment: string; verified_purchase: boolean; is_approved: boolean; review_date?: string; images: string[]; } type StatusFilter = 'all' | 'pending' | 'approved'; function formatDate(value?: string) { if (!value) return '—'; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' }); } export default function ReviewsModerationPage() { const [reviews, setReviews] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const fetchReviews = () => { setLoading(true); adminService.getCmsReviews() .then((data) => { setReviews(data); setLoading(false); }) .catch(() => { toast.error('Failed to load reviews'); setLoading(false); }); }; useEffect(() => { fetchReviews(); }, []); const pendingCount = reviews.filter((r) => !r.is_approved).length; const approvedCount = reviews.filter((r) => r.is_approved).length; const toggleApproval = async (review_id: string, current: boolean) => { try { if (current) { await adminService.rejectReview(review_id); setReviews((prev) => prev.map((r) => r.review_id === review_id ? { ...r, is_approved: false } : r)); toast.success('Review unpublished from the store'); } else { await adminService.approveReview(review_id); setReviews((prev) => prev.map((r) => r.review_id === review_id ? { ...r, is_approved: true } : r)); toast.success('Review approved and visible on the store'); } } catch { toast.error('Action failed — please try again'); } }; const filtered = useMemo(() => { const q = searchQuery.trim().toLowerCase(); return reviews.filter((r) => { const statusMatch = filter === 'all' || (filter === 'pending' && !r.is_approved) || (filter === 'approved' && r.is_approved); const searchMatch = !q || r.author_name.toLowerCase().includes(q) || r.title.toLowerCase().includes(q) || r.comment.toLowerCase().includes(q); return statusMatch && searchMatch; }); }, [reviews, filter, searchQuery]); const pager = useClientPagination(filtered); const dataCardShell = 'crm-radius-section border border-border bg-card shadow-[0_1px_3px_rgba(16,24,40,0.06)] overflow-visible min-w-0'; const renderStars = (rating: number) => ( {Array.from({ length: 5 }).map((_, i) => ( ))} ); return (

Customer Reviews

{reviews.length}
setSearchQuery(e.target.value)} className="w-full h-9 pl-9 pr-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" />
{([ { id: 'all', label: `All (${reviews.length})` }, { id: 'pending', label: `Pending (${pendingCount})` }, { id: 'approved', label: `Approved (${approvedCount})` }, ] as { id: StatusFilter; label: string }[]).map((tab) => ( ))}
{loading ? (
Loading reviews...
) : (
{filtered.length === 0 ? ( ) : ( pager.items.map((rev) => { const photos = (rev.images || []).filter(Boolean); return ( ); }) )}
Customer Product Rating Review Status Action
{searchQuery || filter !== 'all' ? 'No reviews match your search.' : 'No reviews found.'}

{rev.author_name}

{formatDate(rev.review_date)}

{rev.verified_purchase && ( Verified )}
{rev.product_id} {renderStars(rev.rating)}

{rev.title}

{rev.comment}

{photos.length > 0 && (
{photos.map((imgUrl, i) => ( ))}
)} {rev.images.length > 0 && photos.length === 0 && ( Photo attached )}
{rev.is_approved ? 'Approved' : 'Pending'}
)}
); }