'use client'; import { useState } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { PageHeader } from '@/components/shared/page-header'; import { CountryCombobox } from '@/components/shared/country-combobox'; import { TimezoneCombobox } from '@/components/shared/timezone-combobox'; import { SubdivisionCombobox } from '@/components/shared/subdivision-combobox'; import { PhoneInput, type PhoneInputValue } from '@/components/shared/phone-input'; import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; import type { CountryCode } from '@/lib/i18n/countries'; interface ResidentialClientRow { id: string; fullName: string; email: string | null; phone: string | null; placeOfResidence: string | null; status: string; source: string | null; updatedAt: string; } interface ListResponse { data: ResidentialClientRow[]; pagination: { total: number; page: number; pageSize: number }; } const STATUS_LABELS: Record = { prospect: 'Prospect', active: 'Active', inactive: 'Inactive', }; export function ResidentialClientsList() { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const [createOpen, setCreateOpen] = useState(false); const [search, setSearch] = useState(''); const { data, isLoading } = useQuery({ queryKey: ['residential-clients', { search }], queryFn: () => { const qs = new URLSearchParams({ search, limit: '50' }); return apiFetch(`/api/v1/residential/clients?${qs.toString()}`); }, }); useRealtimeInvalidation({ 'residential_client:created': [['residential-clients']], 'residential_client:updated': [['residential-clients']], 'residential_client:archived': [['residential-clients']], 'residential_client:restored': [['residential-clients']], }); return (
setCreateOpen(true)}> New } />
setSearch(e.target.value)} className="max-w-sm" />
{/* Desktop: table layout. Hidden below lg because the 6 columns clip off the viewport at phone widths. */}
{isLoading && ( )} {!isLoading && data?.data.length === 0 && ( )} {data?.data.map((c) => ( ))}
Name Email Phone Residence Status Source
Loading…
No residential clients yet.
{c.fullName} {c.email ?? '-'} {c.phone ?? '-'} {c.placeOfResidence ?? '-'} {STATUS_LABELS[c.status] ?? c.status} {c.source ?? '-'}
{/* Mobile: card list. Each card mirrors the table row data with name + status pill on top, then meta line(s) below. */}
{isLoading && (
Loading…
)} {!isLoading && data?.data.length === 0 && (
No residential clients yet.
)} {data?.data.map((c) => (

{c.fullName}

{STATUS_LABELS[c.status] ?? c.status}
{c.email ? {c.email} : null} {c.phone ? {c.phone} : null} {c.placeOfResidence ? {c.placeOfResidence} : null} {c.source ? · {c.source} : null}
))}
); } function NewResidentialClientSheet({ open, onOpenChange, }: { open: boolean; onOpenChange: (v: boolean) => void; }) { const qc = useQueryClient(); const [fullName, setFullName] = useState(''); const [email, setEmail] = useState(''); const [phone, setPhone] = useState(null); const [nationalityIso, setNationalityIso] = useState(null); const [timezone, setTimezone] = useState(null); const [placeOfResidence, setPlaceOfResidence] = useState(''); const [residenceCountry, setResidenceCountry] = useState(null); const [residenceSubdivision, setResidenceSubdivision] = useState(null); const [notes, setNotes] = useState(''); function reset() { setFullName(''); setEmail(''); setPhone(null); setNationalityIso(null); setTimezone(null); setPlaceOfResidence(''); setResidenceCountry(null); setResidenceSubdivision(null); setNotes(''); } const create = useMutation({ mutationFn: () => apiFetch('/api/v1/residential/clients', { method: 'POST', body: { fullName, email: email || undefined, phone: phone?.e164 ?? undefined, phoneE164: phone?.e164 ?? undefined, phoneCountry: phone?.country ?? undefined, nationalityIso: nationalityIso ?? undefined, timezone: timezone ?? undefined, placeOfResidence: placeOfResidence || undefined, placeOfResidenceCountryIso: residenceCountry ?? undefined, subdivisionIso: residenceSubdivision ?? undefined, notes: notes || undefined, source: 'manual', }, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['residential-clients'] }); onOpenChange(false); reset(); toast.success('Residential client added'); }, onError: (err) => { toast.error(err instanceof Error ? err.message : 'Failed to create'); }, }); return ( New residential client
{ e.preventDefault(); create.mutate(); }} >
setFullName(e.target.value)} required />
setEmail(e.target.value)} />
setPlaceOfResidence(e.target.value)} placeholder="City or area" />
{ setResidenceCountry(iso); // Wipe subdivision when country flips - codes are scoped per country. setResidenceSubdivision(null); }} data-testid="rc-residence-country" />
setNotes(e.target.value)} />
); }