diff --git a/frontend/components/messages/appointment-detail-dialog.tsx b/frontend/components/messages/appointment-detail-dialog.tsx new file mode 100644 index 0000000..682746d --- /dev/null +++ b/frontend/components/messages/appointment-detail-dialog.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { CalendarClock } from "lucide-react"; +import type { ReactNode } from "react"; +import { useTranslation } from "react-i18next"; + +import { Badge } from "@/components/ui/badge"; +import { + Dialog, + DialogDescription, + DialogHeader, + DialogPanel, + DialogPopup, + DialogTitle, +} from "@/components/ui/dialog"; +import type { MessageAttachment } from "@/lib/messages"; + +// The appointment data carried inside a shared-appointment message. It's a +// point-in-time snapshot, so it survives the appointment later being edited or +// deleted. +type AppointmentSnapshot = Extract< + MessageAttachment, + { kind: "appointment" } +>["appointment"]; + +function Row({ label, value }: { label: string; value: ReactNode }) { + if (!value) return null; + return ( +
+
{label}
+
{value}
+
+ ); +} + +// A read-only view of a shared appointment, opened by clicking the card in the +// thread. Works the same for the sender and the recipient. +export function AppointmentDetailDialog({ + appointment, + open, + onOpenChange, +}: { + appointment: AppointmentSnapshot; + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const { t } = useTranslation(); + const a = appointment; + const statusLabel = a.status + ? t(`appointments.status.${a.status}`, { defaultValue: a.status }) + : null; + + return ( + + + + + + {t("messages.attach.apptDetailTitle")} + + {a.name} + + +
+ + + + + {statusLabel} : null + } + /> +
+
+
+
+ ); +} diff --git a/frontend/components/messages/messages-view.tsx b/frontend/components/messages/messages-view.tsx index ac194eb..7c3cb7a 100644 --- a/frontend/components/messages/messages-view.tsx +++ b/frontend/components/messages/messages-view.tsx @@ -5,6 +5,7 @@ import { Download, FileText, Mail, + Paperclip, Plus, Search, SendHorizonal, @@ -21,6 +22,7 @@ import { } from "react"; import { useTranslation } from "react-i18next"; +import { AppointmentDetailDialog } from "@/components/messages/appointment-detail-dialog"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { @@ -40,7 +42,6 @@ import { EmptyTitle, } from "@/components/ui/empty"; import { Input } from "@/components/ui/input"; -import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@/components/ui/menu"; import { type Appointment, listAppointments } from "@/lib/appointments"; import { authClient } from "@/lib/auth-client"; import { @@ -90,6 +91,7 @@ const GROUP_WINDOW_MS = 5 * 60 * 1000; // shared-appointment card. Alignment (left/right) comes from the parent column. function SentAttachment({ att }: { att: MessageAttachment }) { const { t } = useTranslation(); + const [apptOpen, setApptOpen] = useState(false); if (att.kind === "file") { return ( + + ); } @@ -629,51 +642,38 @@ export function MessagesView() { )}
- {/* Visually hidden (not `display:none`) so the programmatic - `.click()` reliably opens the picker across browsers. */} - wrapping the file + input, so the browser opens the picker on a trusted click. + A programmatic `inputRef.click()` (the old menu item) gets + dropped by user-activation gating once the menu closes — + which is why attaching silently failed and no chip appeared. */} + +