"use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Empty, Notice } from "@/components/ui"; type V = { id: string; status: string; proposedSlots: string[]; confirmedSlot: string | null; note: string | null; ownerId: string; requesterId: string; inquiryId: string | null; title: string | null; slug: string; city: string | null; createdAt: string }; const fmt = (iso: string) => new Date(iso).toLocaleString("fr-CA", { timeZone: "America/Toronto", weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }); const LABEL: Record = { REQUESTED: "Demandée", CONFIRMED: "Confirmée", CANCELLED: "Annulée", DONE: "Effectuée" }; export function ViewingsView({ me, items }: { me: string; items: V[] }) { const router = useRouter(); const act = async (id: string, action: string, slot?: string) => { const r = await fetch(`/api/viewings/${id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, slot }) }); if (!r.ok) alert((await r.json()).error?.message ?? "Erreur"); router.refresh(); }; return (

Visites

Heure de l'Est (America/Toronto). Le propriétaire confirme un des créneaux proposés ; les conflits à moins de 30 minutes sont refusés.

{items.length === 0 ? : (
    {items.map((v) => { const owner = v.ownerId === me; return (
  • {LABEL[v.status]}{owner ? "Pour mon annonce" : "Ma demande"} · {new Date(v.createdAt).toLocaleDateString("fr-CA")}

    {v.title ?? "Annonce"} · {v.city}

    {v.note &&

    « {v.note} »

    } {v.status === "CONFIRMED" && v.confirmedSlot &&

    📅 {fmt(v.confirmedSlot)}

    } {v.status === "REQUESTED" &&
    {v.proposedSlots.map((s) => owner ? : {fmt(s)})}
    }
    {!["CANCELLED", "DONE"].includes(v.status) && }{owner && v.status === "CONFIRMED" && }{v.inquiryId && Conversation}
  • ); })}
)}
Par sécurité, l'adresse exacte d'une annonce « rue seulement » ou « secteur » n'est communiquée qu'après confirmation d'une visite, par le propriétaire, dans la conversation.
); }