Maia/frontend/src/pages/MeetingDetail/index.tsx

523 lines
29 KiB
TypeScript

import { useEffect, useState, useCallback, useRef } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { ArrowLeft, Loader2, Edit2, Check, X, Play, Pause, Volume2 } from 'lucide-react'
import { meetingsApi } from '@/api/meetings'
import api from '@/api/axios'
import { useMeetingsStore } from '@/store/meetingsStore'
import { useWebSocket } from '@/hooks/useWebSocket'
import { Layout } from '@/components/Layout'
import { StatusBadge } from '@/components/StatusBadge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent } from '@/components/ui/card'
import type { Meeting, TranscriptSegment, Minutes, SentimentData, PsychologicalData, CommunicationData, SummaryData } from '@/types'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
type Tab = 'transcript' | 'minutes' | 'sentiment' | 'psychological' | 'communication' | 'summary'
const SPEAKER_COLORS = ['#4ade80', '#60a5fa', '#f472b6', '#fb923c', '#a78bfa', '#34d399']
const TERMINAL = ['completed', 'error']
export function MeetingDetail() {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const { meetings, updateMeeting } = useMeetingsStore()
const [meeting, setMeeting] = useState<Meeting | null>(meetings.find((m) => m.id === id) ?? null)
const [tab, setTab] = useState<Tab>('transcript')
const [minutes, setMinutes] = useState<Minutes | null>(null)
const [sentiment, setSentiment] = useState<SentimentData | null>(null)
const [psychological, setPsychological] = useState<PsychologicalData | null>(null)
const [communication, setCommunication] = useState<CommunicationData | null>(null)
const [summary, setSummary] = useState<SummaryData | null>(null)
const [loadingTab, setLoadingTab] = useState(false)
const [editingLabel, setEditingLabel] = useState<string | null>(null)
const [editName, setEditName] = useState('')
const [audioUrl, setAudioUrl] = useState<string | null>(null)
const [audioLoading, setAudioLoading] = useState(false)
const [isPlaying, setIsPlaying] = useState(false)
const audioRef = useRef<HTMLAudioElement | null>(null)
const [enrollNames, setEnrollNames] = useState<Record<string, string>>({})
const [enrollingLabel, setEnrollingLabel] = useState<string | null>(null)
const [enrollErrors, setEnrollErrors] = useState<Record<string, string>>({})
const [enrollSuccess, setEnrollSuccess] = useState<Record<string, boolean>>({})
const isTerminal = TERMINAL.includes(meeting?.status ?? '')
useEffect(() => {
if (!id) return
meetingsApi.get(id).then(({ data }) => {
setMeeting(data)
updateMeeting(id, data)
if (data.status === 'completed') setTab('transcript')
})
}, [id, updateMeeting])
const onWsMessage = useCallback((data: unknown) => {
const msg = data as { type: string; status?: string; progress?: number }
if (msg.type === 'status_update' && msg.status) {
setMeeting((m) => m ? { ...m, status: msg.status as Meeting['status'], progress: msg.progress } : m)
}
}, [])
useWebSocket(meeting?.status === 'processing' ? (id ?? null) : null, onWsMessage)
useEffect(() => {
if (!id || isTerminal) return
const iv = setInterval(async () => {
try {
const { data } = await meetingsApi.get(id)
setMeeting(data)
updateMeeting(id, data)
if (TERMINAL.includes(data.status)) clearInterval(iv)
} catch { /* ignore */ }
}, 4000)
return () => clearInterval(iv)
}, [id, isTerminal, updateMeeting])
async function loadTab(t: Tab) {
setTab(t)
setLoadingTab(true)
try {
if (t === 'minutes' && !minutes) {
const { data } = await meetingsApi.getAnalysis(id!, 'minutes')
setMinutes(data.content as Minutes)
} else if (t === 'sentiment' && !sentiment) {
const { data } = await meetingsApi.getAnalysis(id!, 'sentiment')
setSentiment(data.content as SentimentData)
} else if (t === 'psychological' && !psychological) {
const { data } = await meetingsApi.getAnalysis(id!, 'psychological')
setPsychological(data.content as PsychologicalData)
} else if (t === 'communication' && !communication) {
const { data } = await meetingsApi.getAnalysis(id!, 'communication')
setCommunication(data.content as CommunicationData)
} else if (t === 'summary' && !summary) {
const { data } = await meetingsApi.getAnalysis(id!, 'summary')
setSummary(data.content as SummaryData)
}
} catch { /* analysis not ready */ } finally {
setLoadingTab(false)
}
}
async function loadAudio() {
if (audioUrl) return
setAudioLoading(true)
try {
const resp = await api.get(`/meetings/${id}/audio`, { responseType: 'blob' })
const url = URL.createObjectURL(resp.data)
setAudioUrl(url)
if (audioRef.current) audioRef.current.src = url
} catch { /* no audio */ } finally {
setAudioLoading(false)
}
}
function togglePlay() {
if (!audioRef.current) return
if (isPlaying) { audioRef.current.pause(); setIsPlaying(false) }
else { audioRef.current.play(); setIsPlaying(true) }
}
async function saveLabel(label: string) {
if (!id || !editName.trim()) return
await meetingsApi.relabelParticipant(id, label, editName.trim())
setMeeting((m) => m ? { ...m } : m)
setEditingLabel(null)
}
function getSpeakerColor(speaker: string): string {
const segs = meeting?.transcription?.segments ?? []
const speakers = [...new Set(segs.map((s) => s.speaker))]
return SPEAKER_COLORS[speakers.indexOf(speaker) % SPEAKER_COLORS.length]
}
const sentimentColor = (s?: string) =>
s === 'positive' ? '#4ade80' : s === 'negative' ? '#f87171' : '#94a3b8'
const tabs: { key: Tab; label: string }[] = [
{ key: 'transcript', label: 'Transcricao' },
{ key: 'summary', label: 'Resumo' },
{ key: 'minutes', label: 'Ata' },
{ key: 'sentiment', label: 'Sentimento' },
{ key: 'psychological', label: 'Perfil' },
{ key: 'communication', label: 'Comunicacao' },
]
const segments: TranscriptSegment[] = meeting?.transcription?.segments ?? []
const unidentifiedSpeakers = [...new Set(segments.filter(s => /^SPEAKER_\d+$/.test(s.speaker)).map(s => s.speaker))].sort()
async function handleEnroll(label: string) {
const name = (enrollNames[label] || '').trim()
if (!name || !id) return
setEnrollingLabel(label)
setEnrollErrors(e => ({ ...e, [label]: '' }))
try {
await meetingsApi.enrollSpeaker(id, label, name)
setEnrollSuccess(s => ({ ...s, [label]: true }))
setMeeting(m => {
if (!m?.transcription?.segments) return m
return {
...m,
transcription: {
...m.transcription,
segments: m.transcription.segments.map((seg: TranscriptSegment) =>
seg.speaker === label ? { ...seg, speaker: name } : seg
),
},
}
})
} catch {
setEnrollErrors(e => ({ ...e, [label]: 'Erro ao salvar. Tente novamente.' }))
} finally {
setEnrollingLabel(null)
}
}
return (
<Layout>
<div className="p-4 max-w-2xl mx-auto space-y-4 pb-8">
<div className="flex items-center gap-3">
<button onClick={() => navigate(-1)} className="text-slate-400 hover:text-white"><ArrowLeft size={20} /></button>
<div className="flex-1 min-w-0">
<h1 className="text-lg font-bold text-white truncate">{meeting?.title || 'Reuniao sem titulo'}</h1>
{meeting && <StatusBadge status={meeting.status} />}
</div>
</div>
{meeting?.status === 'completed' && (
<Card>
<CardContent className="flex items-center gap-3 py-3">
<Volume2 size={16} className="text-slate-400 shrink-0" />
{!audioUrl && !audioLoading && (
<button onClick={loadAudio} className="text-sm text-green-400 hover:text-green-300">
Carregar audio da reuniao
</button>
)}
{audioLoading && <span className="text-slate-400 text-sm flex items-center gap-2"><Loader2 size={14} className="animate-spin" /> Carregando...</span>}
{audioUrl && (
<div className="flex items-center gap-3 flex-1">
<button onClick={togglePlay} className="w-8 h-8 rounded-full bg-green-600 hover:bg-green-500 flex items-center justify-center shrink-0">
{isPlaying ? <Pause size={14} /> : <Play size={14} />}
</button>
<audio
ref={audioRef}
src={audioUrl}
onEnded={() => setIsPlaying(false)}
onPause={() => setIsPlaying(false)}
onPlay={() => setIsPlaying(true)}
controls
className="flex-1 h-8"
style={{ accentColor: '#4ade80' }}
/>
</div>
)}
</CardContent>
</Card>
)}
{!isTerminal && meeting?.status !== undefined && (
<Card>
<CardContent className="flex flex-col items-center py-8 gap-3">
<Loader2 size={32} className="animate-spin text-yellow-400" />
<p className="text-slate-300 text-sm font-medium">
{meeting.status === 'receiving_chunks' && 'Recebendo audio...'}
{meeting.status === 'uploading' && 'Enviando para processamento...'}
{meeting.status === 'processing' && 'Iniciando processamento...'}
{meeting.status === 'transcribing' && 'Transcrevendo com Whisper...'}
{meeting.status === 'analyzing' && 'Analisando com IA...'}
</p>
<p className="text-slate-500 text-xs">A pagina atualiza automaticamente</p>
</CardContent>
</Card>
)}
{meeting?.status === 'completed' && (
<>
{unidentifiedSpeakers.length > 0 && (
<Card>
<CardContent className="py-3">
<div className="flex items-center gap-2 mb-3">
<span className="text-lg">🎤</span>
<div>
<p className="text-white text-sm font-semibold">Identificar participantes</p>
<p className="text-slate-400 text-xs">Para reconhecimento automático em reuniões futuras</p>
</div>
</div>
<div className="space-y-2">
{unidentifiedSpeakers.map(label => (
<div key={label} className="flex items-center gap-2">
<span className="text-xs font-mono text-slate-400 w-24 shrink-0">{label}</span>
<Input
value={enrollNames[label] || ''}
onChange={e => setEnrollNames(n => ({ ...n, [label]: e.target.value }))}
onKeyDown={e => e.key === 'Enter' && handleEnroll(label)}
placeholder="Nome da pessoa"
className="h-7 text-xs flex-1"
disabled={!!enrollSuccess[label]}
/>
<Button
size="sm"
className="h-7 px-2 text-xs shrink-0"
disabled={enrollingLabel === label || !!enrollSuccess[label]}
onClick={() => handleEnroll(label)}
>
{enrollingLabel === label ? <Loader2 size={12} className="animate-spin" /> : enrollSuccess[label] ? <Check size={12} /> : 'Salvar'}
</Button>
</div>
))}
</div>
{Object.entries(enrollErrors).filter(([, v]) => v).map(([k, v]) => (
<p key={k} className="text-red-400 text-xs mt-1">{k}: {v}</p>
))}
</CardContent>
</Card>
)}
<div className="flex gap-1 overflow-x-auto pb-1">
{tabs.map((t) => (
<button key={t.key} onClick={() => loadTab(t.key)}
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${tab === t.key ? 'bg-green-700 text-white' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-700'}`}>
{t.label}
</button>
))}
</div>
{loadingTab && <div className="flex justify-center py-8"><Loader2 size={20} className="animate-spin text-slate-400" /></div>}
{tab === 'transcript' && !loadingTab && (
<div className="space-y-2">
{segments.length > 0 ? segments.map((seg, i) => (
<div key={i} className="flex gap-3">
<div className="flex flex-col items-center">
<div className="w-2 h-2 rounded-full mt-2 shrink-0" style={{ background: getSpeakerColor(seg.speaker) }} />
<div className="w-px flex-1 bg-slate-700 mt-1" />
</div>
<div className="flex-1 pb-3">
<div className="flex items-center gap-2 mb-0.5">
<span className="text-xs font-semibold" style={{ color: getSpeakerColor(seg.speaker) }}>{seg.speaker}</span>
<span className="text-slate-500 text-xs">{Math.floor(seg.start / 60)}:{String(Math.floor(seg.start % 60)).padStart(2, '0')}</span>
{editingLabel === seg.speaker ? (
<div className="flex items-center gap-1 ml-1">
<Input value={editName} onChange={(e) => setEditName(e.target.value)} className="h-5 px-1 text-xs w-24" />
<button onClick={() => saveLabel(seg.speaker)} className="text-green-400"><Check size={12} /></button>
<button onClick={() => setEditingLabel(null)} className="text-red-400"><X size={12} /></button>
</div>
) : (
<button onClick={() => { setEditingLabel(seg.speaker); setEditName(seg.speaker) }} className="text-slate-500 hover:text-slate-300"><Edit2 size={10} /></button>
)}
</div>
<p className="text-slate-200 text-sm">{seg.text}</p>
</div>
</div>
)) : <p className="text-slate-400 text-sm text-center py-8">Transcricao nao disponivel.</p>}
</div>
)}
{tab === 'summary' && !loadingTab && (
<div className="space-y-3">
{summary ? (
<>
{summary.summary && <Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Resumo</h3><p className="text-slate-200 text-sm leading-relaxed">{summary.summary}</p></CardContent></Card>}
{summary.key_points && summary.key_points.length > 0 && (
<Card><CardContent>
<h3 className="text-green-400 text-sm font-semibold mb-2">Pontos-chave</h3>
<ul className="space-y-1">{summary.key_points.map((p, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span className="text-green-400">&#x2022;</span>{p}</li>)}</ul>
</CardContent></Card>
)}
<div className="flex gap-3">
{summary.duration_minutes != null && <Card className="flex-1"><CardContent className="text-center py-3"><p className="text-2xl font-bold text-white">{summary.duration_minutes}'</p><p className="text-slate-400 text-xs">duracao</p></CardContent></Card>}
{summary.total_participants != null && <Card className="flex-1"><CardContent className="text-center py-3"><p className="text-2xl font-bold text-white">{summary.total_participants}</p><p className="text-slate-400 text-xs">participantes</p></CardContent></Card>}
</div>
</>
) : <p className="text-slate-400 text-sm text-center py-8">Resumo nao disponivel.</p>}
</div>
)}
{tab === 'minutes' && !loadingTab && (
<div className="space-y-3">
{minutes ? (
<>
{minutes.agenda && minutes.agenda.length > 0 && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Pauta</h3><ul className="space-y-1">{minutes.agenda.map((a, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span className="text-slate-400">{i+1}.</span>{a}</li>)}</ul></CardContent></Card>
)}
{minutes.decisions && minutes.decisions.length > 0 && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Decisoes</h3><ul className="space-y-1">{minutes.decisions.map((d, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span>&#x2022;</span>{d}</li>)}</ul></CardContent></Card>
)}
{minutes.action_items && minutes.action_items.length > 0 && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Acoes</h3>
<div className="space-y-2">{minutes.action_items.map((a, i) => (
<div key={i} className="text-sm border-l-2 border-slate-700 pl-3">
<p className="text-slate-200">{a.description}</p>
{a.responsible && <p className="text-slate-400 text-xs">&#x2192; {a.responsible}</p>}
{a.deadline && a.deadline !== 'A definir' && <p className="text-slate-500 text-xs">{a.deadline}</p>}
</div>
))}</div>
</CardContent></Card>
)}
{minutes.next_steps && minutes.next_steps.length > 0 && (
<Card><CardContent><h3 className="text-blue-400 text-sm font-semibold mb-2">Proximos passos</h3><ul className="space-y-1">{minutes.next_steps.map((s, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span>&#x2192;</span>{s}</li>)}</ul></CardContent></Card>
)}
{minutes.open_points && minutes.open_points.length > 0 && (
<Card><CardContent><h3 className="text-yellow-400 text-sm font-semibold mb-2">Pontos em aberto</h3><ul className="space-y-1">{minutes.open_points.map((s, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span>&#x2022;</span>{s}</li>)}</ul></CardContent></Card>
)}
</>
) : <p className="text-slate-400 text-sm text-center py-8">Ata nao disponivel.</p>}
</div>
)}
{tab === 'sentiment' && !loadingTab && (
<div className="space-y-3">
{sentiment ? (
<>
{sentiment.overall_sentiment && (
<Card><CardContent className="flex items-center gap-3">
<div className="w-3 h-3 rounded-full" style={{ background: sentimentColor(sentiment.overall_sentiment) }} />
<div>
<p className="text-white text-sm font-medium">Sentimento geral</p>
<p className="text-slate-400 text-xs capitalize">{sentiment.overall_sentiment}</p>
</div>
</CardContent></Card>
)}
{sentiment.participation_metrics && sentiment.participation_metrics.length > 0 && (
<Card><CardContent>
<h3 className="text-green-400 text-sm font-semibold mb-3">Participacao</h3>
<ResponsiveContainer width="100%" height={140}>
<BarChart data={sentiment.participation_metrics}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis dataKey="speaker" tick={{ fill: '#94a3b8', fontSize: 10 }} />
<YAxis tick={{ fill: '#94a3b8', fontSize: 10 }} unit="%" />
<Tooltip contentStyle={{ background: '#1e293b', border: 'none', borderRadius: 8 }} formatter={(v) => [`${v}%`, 'Participacao']} />
<Bar dataKey="talk_percentage" fill="#4ade80" radius={[4,4,0,0]} />
</BarChart>
</ResponsiveContainer>
</CardContent></Card>
)}
{sentiment.participants_sentiment && sentiment.participants_sentiment.length > 0 && (
<Card><CardContent>
<h3 className="text-green-400 text-sm font-semibold mb-2">Por participante</h3>
<div className="space-y-3">{sentiment.participants_sentiment.map((p, i) => (
<div key={i}>
<div className="flex items-center gap-2 mb-1">
<div className="w-2 h-2 rounded-full" style={{ background: sentimentColor(p.sentiment) }} />
<span className="text-slate-300 text-sm font-medium">{p.speaker}</span>
<span className="text-slate-500 text-xs capitalize">({p.sentiment})</span>
</div>
<p className="text-slate-400 text-xs leading-relaxed pl-4">{p.details}</p>
</div>
))}</div>
</CardContent></Card>
)}
{sentiment.timeline && sentiment.timeline.length > 0 && (
<Card><CardContent>
<h3 className="text-green-400 text-sm font-semibold mb-2">Linha do tempo</h3>
<div className="space-y-2">{sentiment.timeline.map((t, i) => (
<div key={i} className="flex gap-3 text-sm">
<span className="text-slate-500 text-xs whitespace-nowrap shrink-0">{t.time_range}</span>
<div>
<span className="font-medium capitalize" style={{ color: sentimentColor(t.sentiment) }}>{t.sentiment}</span>
{t.notes && <p className="text-slate-400 text-xs mt-0.5">{t.notes}</p>}
</div>
</div>
))}</div>
</CardContent></Card>
)}
{sentiment.tension_moments && sentiment.tension_moments.length > 0 && (
<Card><CardContent><h3 className="text-red-400 text-sm font-semibold mb-2">Momentos de tensao</h3><ul className="space-y-1">{sentiment.tension_moments.map((t, i) => <li key={i} className="text-slate-300 text-sm flex gap-2"><span>&#x2022;</span>{t}</li>)}</ul></CardContent></Card>
)}
{sentiment.consensus_moments && sentiment.consensus_moments.length > 0 && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Momentos de consenso</h3><ul className="space-y-1">{sentiment.consensus_moments.map((t, i) => <li key={i} className="text-slate-300 text-sm flex gap-2"><span>&#x2022;</span>{t}</li>)}</ul></CardContent></Card>
)}
</>
) : <p className="text-slate-400 text-sm text-center py-8">Analise de sentimento nao disponivel.</p>}
</div>
)}
{tab === 'psychological' && !loadingTab && (
<div className="space-y-3">
{psychological ? (
<>
{psychological.group_dynamics && (
<Card><CardContent><h3 className="text-purple-400 text-sm font-semibold mb-2">Dinamica do grupo</h3><p className="text-slate-200 text-sm leading-relaxed">{psychological.group_dynamics}</p></CardContent></Card>
)}
{psychological.participants_profiles && psychological.participants_profiles.length > 0 && psychological.participants_profiles.map((p, i) => (
<Card key={i}><CardContent>
<h3 className="text-purple-400 text-sm font-semibold mb-2">{p.name || p.speaker}</h3>
<div className="space-y-2 text-sm">
{p.communication_style && <div><span className="text-slate-400 text-xs">Estilo de comunicacao:</span><p className="text-slate-200">{p.communication_style}</p></div>}
{p.leadership_tendencies && <div><span className="text-slate-400 text-xs">Lideranca:</span><p className="text-slate-200">{p.leadership_tendencies}</p></div>}
{p.power_dynamics_role && <div><span className="text-slate-400 text-xs">Papel na dinamica:</span><p className="text-slate-200">{p.power_dynamics_role}</p></div>}
</div>
</CardContent></Card>
))}
{psychological.key_observations && psychological.key_observations.length > 0 && (
<Card><CardContent><h3 className="text-purple-400 text-sm font-semibold mb-2">Observacoes</h3><ul className="space-y-1">{psychological.key_observations.map((o, i) => <li key={i} className="text-slate-200 text-sm flex gap-2"><span>&#x2022;</span>{o}</li>)}</ul></CardContent></Card>
)}
</>
) : <p className="text-slate-400 text-sm text-center py-8">Perfil psicologico nao disponivel.</p>}
</div>
)}
{tab === 'communication' && !loadingTab && (
<div className="space-y-3">
{communication ? (
<>
{communication.overall_score != null && (
<Card><CardContent className="text-center py-4">
<div className="text-5xl font-bold text-white mb-1">{communication.overall_score}<span className="text-xl text-slate-400">/10</span></div>
<p className="text-slate-400 text-sm">Score de comunicacao</p>
</CardContent></Card>
)}
{communication.general_feedback && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Feedback geral</h3><p className="text-slate-200 text-sm leading-relaxed">{communication.general_feedback}</p></CardContent></Card>
)}
{communication.dimensions && (
<Card><CardContent>
<h3 className="text-green-400 text-sm font-semibold mb-3">Dimensoes</h3>
<div className="space-y-3">{Object.entries(communication.dimensions).map(([dim, val]) => (
<div key={dim}>
<div className="flex justify-between text-xs text-slate-300 mb-1">
<span className="capitalize">{dim.replace(/_/g, ' ')}</span>
<span>{val.score}/10</span>
</div>
<div className="h-1.5 bg-slate-700 rounded-full"><div className="h-full bg-green-500 rounded-full transition-all" style={{ width: `${val.score * 10}%` }} /></div>
{val.notes && <p className="text-slate-500 text-xs mt-1">{val.notes}</p>}
</div>
))}</div>
</CardContent></Card>
)}
{communication.strengths && communication.strengths.length > 0 && (
<Card><CardContent><h3 className="text-green-400 text-sm font-semibold mb-2">Pontos fortes</h3>
<div className="space-y-2">{communication.strengths.map((s, i) => (
<div key={i}><p className="text-slate-200 text-sm">{s.description}</p>{s.example && <p className="text-slate-500 text-xs mt-0.5 pl-4">"{s.example}"</p>}</div>
))}</div>
</CardContent></Card>
)}
{communication.improvement_areas && communication.improvement_areas.length > 0 && (
<Card><CardContent><h3 className="text-yellow-400 text-sm font-semibold mb-2">Melhorias</h3>
<div className="space-y-2">{communication.improvement_areas.map((a, i) => (
<div key={i}><p className="text-slate-200 text-sm">{a.description}</p>{a.suggestion && <p className="text-slate-500 text-xs mt-0.5 pl-4">&#x2192; {a.suggestion}</p>}</div>
))}</div>
</CardContent></Card>
)}
</>
) : <p className="text-slate-400 text-sm text-center py-8">Analise de comunicacao nao disponivel.</p>}
</div>
)}
</>
)}
{meeting?.status === 'error' && (
<Card>
<CardContent className="text-center py-8">
<p className="text-red-400 font-medium">Erro ao processar reuniao</p>
{meeting.error_message && <p className="text-slate-400 text-sm mt-1">{meeting.error_message}</p>}
<Button variant="outline" onClick={() => navigate(-1)} className="mt-4">Voltar</Button>
</CardContent>
</Card>
)}
</div>
</Layout>
)
}