'use client'; /* EngagementRecentMeetings — phase 08 (D-18..D-21). * Purpose: Collapsible list of up to 10 recent Teams meetings. Tap-to-expand * reveals matched time entries and attendee list. Local Set * state tracks expanded IDs — period changes do not reset expansion (D-20). */ import { useState } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { useUserTimezone, formatInUserTimezone } from '@/lib/hooks/use-user-timezone'; export interface RecentMeeting { subject: string | null; startTime: string; durationMinutes: number | null; attendeeCount: number; clientAttendeeCount: number; hasClientAttendees: boolean; clientCompanies: string[]; participantNames: string[]; matchedEntries: Array<{ hours_worked: number; billable: boolean | null; notes: string | null; title: string | null; company_name: string | null; start_date_time: string | null; end_date_time: string | null; }>; } export interface EngagementRecentMeetingsProps { meetings: RecentMeeting[]; } export function EngagementRecentMeetings({ meetings }: EngagementRecentMeetingsProps) { const tz = useUserTimezone(); const [expandedIds, setExpandedIds] = useState>(new Set()); const toggle = (id: string) => { setExpandedIds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; // ID derivation: caller doesn't pass an explicit id, so derive a stable string per row. const idFor = (m: RecentMeeting, i: number) => `${m.startTime}|${m.subject ?? ''}|${i}`; // Format a duration in minutes as "Hh Mm" / "Mm" — used in the collapsed summary const fmtDuration = (mins: number | null): string => { if (mins === null || mins === undefined || !Number.isFinite(mins) || mins <= 0) return ''; const h = Math.floor(mins / 60); const m = Math.round(mins % 60); if (h > 0 && m > 0) return `${h}h ${m}m`; if (h > 0) return `${h}h`; return `${m}m`; }; return (

Recent meetings

{meetings.length === 0 ? (

No meetings recorded

) : (
    {meetings.slice(0, 10).map((meeting, i) => { const id = idFor(meeting, i); const open = expandedIds.has(id); const subjectLabel = meeting.subject ?? '(no subject)'; const startLabel = formatInUserTimezone(meeting.startTime, tz, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', }); const durationLabel = fmtDuration(meeting.durationMinutes); const attendeeLabel = meeting.attendeeCount > 0 ? `${meeting.attendeeCount} attendee${meeting.attendeeCount === 1 ? '' : 's'}` : ''; const visibleParticipants = meeting.participantNames.slice(0, 5); const moreCount = Math.max(0, meeting.participantNames.length - 5); return (
  • toggle(id)}> {meeting.matchedEntries.length > 0 && (

    Matched time entries:

      {meeting.matchedEntries.map((te, j) => (
    • {Number(te.hours_worked).toFixed(1)}h {te.company_name && · {te.company_name}} {te.notes && · {te.notes.split('\n')[0]?.slice(0, 80) ?? ''}}
    • ))}
    )} {visibleParticipants.length > 0 && (

    Attendees:{' '} {visibleParticipants.join(', ')} {moreCount > 0 ? ` and ${moreCount} more` : ''}

    )} {durationLabel && (

    Duration: {durationLabel}

    )}
  • ); })}
)}
); }