'use client'; import { BadgeCheck, Building2, Calendar, MapPin, Phone, Mail, Star, Home, MessageSquare, TrendingUp, Award, BarChart2, } from 'lucide-react'; import Image from 'next/image'; import * as React from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; import { KpiCard, DataTable, EmptyState, StatusChip, type DataTableColumn } from '@/components/design-system'; import { InquiryModal } from '@/components/listings/inquiry-modal'; import { Badge as UiBadge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Link } from '@/i18n/navigation'; import type { AgentPublicProfile, AgentReviewItem } from '@/lib/agents-api'; import { shimmerBlurDataURL } from '@/lib/image-blur'; import type { ListingDetail } from '@/lib/listings-api'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const VND = new Intl.NumberFormat('vi-VN'); function fmtVND(value: string | number | bigint): string { const n = typeof value === 'bigint' ? Number(value) : Number(value); if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)} tỷ`; if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(0)} tr`; return VND.format(n); } function qualityLabel(score: number): string { if (score >= 80) return 'Xuất sắc'; if (score >= 60) return 'Tốt'; if (score >= 40) return 'Trung bình'; return 'Cần cải thiện'; } function qualityColor(score: number): string { if (score >= 80) return 'text-signal-up'; if (score >= 60) return 'text-primary'; if (score >= 40) return 'text-signal-neutral'; return 'text-signal-down'; } // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- interface AgentProfileClientProps { agent: AgentPublicProfile; reviews: AgentReviewItem[]; /** Agent's managed listings — fetched server-side. */ listings: ListingDetail[]; /** Total listing count (may exceed `listings.length` if paginated). */ listingsTotal: number; } // --------------------------------------------------------------------------- // Listings table columns // --------------------------------------------------------------------------- const listingColumns: DataTableColumn[] = [ { id: 'title', header: 'Bất động sản', cell: (row) => (

{row.property.title}

{row.property.district}, {row.property.city}

), width: '30%', }, { id: 'type', header: 'Loại', cell: (row) => ( {row.transactionType === 'SALE' ? 'Bán' : 'Cho thuê'} ), width: '8%', }, { id: 'status', header: 'Trạng thái', cell: (row) => { const s = row.status.toLowerCase() as Parameters[0]['status']; const safe = ['active', 'pending', 'sold', 'rented', 'rejected', 'draft'].includes(s) ? s : 'draft'; return ; }, width: '10%', }, { id: 'area', header: 'DT (m²)', numeric: true, align: 'right', sortable: true, sortValue: (row) => row.property.areaM2, cell: (row) => ( {row.property.areaM2} ), width: '8%', }, { id: 'price', header: 'Giá', numeric: true, align: 'right', sortable: true, sortValue: (row) => Number(row.priceVND), cell: (row) => ( {fmtVND(row.priceVND)} ), width: '12%', }, { id: 'pricePerM2', header: 'đ/m²', numeric: true, align: 'right', sortable: true, sortValue: (row) => row.pricePerM2 ?? 0, cell: (row) => row.pricePerM2 != null ? ( {fmtVND(row.pricePerM2)} ) : ( ), width: '12%', }, { id: 'views', header: 'Lượt xem', numeric: true, align: 'right', sortable: true, sortValue: (row) => row.viewCount, cell: (row) => ( {VND.format(row.viewCount)} ), width: '10%', }, { id: 'inquiries', header: 'Liên hệ', numeric: true, align: 'right', sortable: true, sortValue: (row) => row.inquiryCount ?? 0, cell: (row) => row.inquiryCount != null ? ( {row.inquiryCount} ) : ( ), width: '10%', }, ]; // --------------------------------------------------------------------------- // Performance chart — derived from real listings data // --------------------------------------------------------------------------- interface MonthBucket { month: string; published: number; sold: number; } function buildPerformanceData(listings: ListingDetail[]): MonthBucket[] { const map = new Map(); const now = new Date(); for (let i = 11; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; const label = d.toLocaleDateString('vi-VN', { month: 'short', year: '2-digit' }); map.set(key, { month: label, published: 0, sold: 0 }); } for (const l of listings) { const src = l.publishedAt ?? l.createdAt; const d = new Date(src); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; const bucket = map.get(key); if (!bucket) continue; bucket.published++; if (l.status === 'SOLD' || l.status === 'RENTED') bucket.sold++; } return Array.from(map.values()); } // --------------------------------------------------------------------------- // Main Component // --------------------------------------------------------------------------- export function AgentProfileClient({ agent, reviews, listings, listingsTotal, }: AgentProfileClientProps) { const [inquiryOpen, setInquiryOpen] = React.useState(false); const firstListing = agent.activeListings[0] ?? null; const handleMessageClick = React.useCallback(() => { if (firstListing) { setInquiryOpen(true); } else { window.location.href = `sms:${agent.phone}`; } }, [firstListing, agent.phone]); const perfData = React.useMemo(() => buildPerformanceData(listings), [listings]); // Derived KPIs from real data const activeCount = listings.filter((l) => l.status === 'ACTIVE').length; const avgPriceVND = listings.length > 0 ? listings.reduce((acc, l) => acc + Number(l.priceVND), 0) / listings.length : null; const yearsExp = Math.floor( (Date.now() - new Date(agent.memberSince).getTime()) / (365.25 * 24 * 3600 * 1000), ); return (
{/* ── Breadcrumb ── */} {/* ── Profile Header ── */}
{/* Avatar */}
{agent.avatarUrl ? ( {agent.fullName} ) : (
{agent.fullName.charAt(0).toUpperCase()}
)}
{/* Info */}
{/* Name + badges */}

{agent.fullName}

{agent.isVerified && ( KYC xác minh )} ★ {agent.qualityScore}/100 · {qualityLabel(agent.qualityScore)}
{/* Meta */}
{agent.agency && ( {agent.agency} )} {agent.licenseNumber && ( Giấy phép: {agent.licenseNumber} )} {yearsExp > 0 ? `${yearsExp} năm kinh nghiệm` : 'Mới tham gia'} · từ{' '} {new Date(agent.memberSince).toLocaleDateString('vi-VN', { month: 'long', year: 'numeric', })}
{/* Service areas */} {agent.serviceAreas.length > 0 && (
{agent.serviceAreas.map((area) => ( {area} ))}
)}
{/* Desktop CTA */}
{/* ── KPI Strip ── */}
} footnote="Tất cả thời gian" /> } footnote="Đang rao bán" /> } footnote="Tổng deals thành công" /> } footnote="Danh mục hiện tại" /> 0 ? `${agent.avgReviewRating.toFixed(1)}/5` : '—' } icon={} footnote={ agent.totalReviews > 0 ? `${agent.totalReviews} lượt đánh giá` : 'Chưa có đánh giá' } />
{/* ── Main Grid ── */}
{/* Left column — chart + table + reviews */}
{/* Performance Chart */} Hiệu suất 12 tháng — tin đăng & giao dịch Tổng hợp từ danh mục thực [ value, name === 'published' ? 'Tin đăng' : 'Giao dịch', ]} />
Tin đăng Giao dịch thành công
{/* Listings Table */}
Danh mục bất động sản{' '} ({VND.format(listingsTotal)})
Sắp xếp theo giá, diện tích, lượt xem
row.id} defaultSortId="price" defaultSortDir="desc" dense stickyHeader emptyText={ } title="Chưa có tin đăng" description="Môi giới này chưa có bất động sản nào" /> } />
{/* Reviews */} Đánh giá{' '} ({agent.totalReviews}) {agent.totalReviews > 0 && ( Trung bình{' '} {agent.avgReviewRating.toFixed(1)}/5 )} {reviews.length > 0 ? (
{reviews.map((review) => ( ))}
) : ( } title="Chưa có đánh giá" description="Chưa có khách hàng nào để lại đánh giá cho môi giới này" /> )}
{/* Right column — sticky contact + quality + bio */}
{/* Mobile contact */}
{/* Desktop contact (hidden on mobile, shown lg via sticky container) */}
{/* Quality Score */} Chất lượng môi giới
{/* Donut */}
{agent.qualityScore}

{qualityLabel(agent.qualityScore)}

Dựa trên phản hồi, thời gian phản hồi và deals thành công

{/* Bio */} {agent.bio && ( Giới thiệu

{agent.bio}

)}
{firstListing && ( )}
); } // --------------------------------------------------------------------------- // Sub-Components // --------------------------------------------------------------------------- function ContactCard({ agent, onMessageClick, }: { agent: AgentPublicProfile; onMessageClick: () => void; }) { return ( Liên hệ môi giới {agent.email && ( )}

Số điện thoại

{agent.phone}

{agent.email && (

Email

{agent.email}

)}
); } function ReviewRow({ review }: { review: AgentReviewItem }) { return (
{(review.userName ?? 'Ẩn').charAt(0).toUpperCase()}

{review.userName ?? 'Ẩn danh'}

{new Date(review.createdAt).toLocaleDateString('vi-VN')}

{/* Stars */}
{Array.from({ length: 5 }).map((_, i) => ( ))}
{review.comment && (

{review.comment}

)}
); }