import { useEffect, useRef, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { type Assignment, type PhaseTemplate, type MeetingInstance, type Consultant, type FeedbackCategory, type FeedbackCriterionItem, type ConversationEntry, type Assessment, type FeedbackRating, getAssignment, getAssignmentType, listPhaseTemplatesForType, listDoneMeetingsChronological, getConsultantsByIds, listFeedbackCategoriesAll, listFeedbackCriterionItemsAll, filterVisibleFeedbackStructure, listConversationEntriesForMeetings, listAssessmentsForMeetings, } from '../db' import { NotationText } from '../utils/notationParser' import { INST_COLORS } from '../config/constants' import { weightedAverageRating } from '../utils/ratingTrend' const RATING_BG: Record = { na: 'bg-gray-400 text-white', not_client_ready: 'bg-red-500 text-white', partially_client_ready: 'bg-orange-500 text-white', nearly_client_ready: 'bg-green-400 text-white', client_ready: 'bg-green-700 text-white', } export default function MeetingHistory() { const { id } = useParams() const navigate = useNavigate() const assignmentId = Number(id) const [assignment, setAssignment] = useState(null) const [templates, setTemplates] = useState([]) const [meetings, setMeetings] = useState([]) const [institutees, setInstitutees] = useState([]) const [fbCategories, setFbCategories] = useState([]) const [fbItems, setFbItems] = useState([]) const [entries, setEntries] = useState([]) const [assessments, setAssessments] = useState([]) const [loaded, setLoaded] = useState(false) const latestRef = useRef(null) useEffect(() => { const load = async () => { const a = await getAssignment(assignmentId) if (!a) return setAssignment(a) const [aType, tmpls, doneMeetings, cons] = await Promise.all([ getAssignmentType(a.assignmentTypeId), listPhaseTemplatesForType(a.assignmentTypeId), listDoneMeetingsChronological(assignmentId), getConsultantsByIds(a.instituteeIds), ]) setTemplates(tmpls) setMeetings(doneMeetings) setInstitutees(a.instituteeIds.map(cid => cons.find(c => c.id === cid)!).filter(Boolean)) const [allCats, allItems] = await Promise.all([ listFeedbackCategoriesAll(), listFeedbackCriterionItemsAll(), ]) const { categories: visibleCats, items: visibleItems } = filterVisibleFeedbackStructure(allCats, allItems, aType?.criteriaIds) setFbCategories(visibleCats) setFbItems(visibleItems) const meetingIds = doneMeetings.map(m => m.id!) const [convEntries, asmts] = await Promise.all([ listConversationEntriesForMeetings(meetingIds), listAssessmentsForMeetings(meetingIds), ]) setEntries(convEntries) setAssessments(asmts) setLoaded(true) } load() }, [assignmentId]) useEffect(() => { if (loaded) latestRef.current?.scrollIntoView({ block: 'start' }) }, [loaded]) const displayName = (inst: Consultant) => { const dup = institutees.filter(c => c.firstName === inst.firstName && c.id !== inst.id).length > 0 return dup ? `${inst.firstName} ${inst.lastName.charAt(0)}.` : inst.firstName } if (!assignment) return
Lädt…
return (

📜 Gesamtprotokoll

{assignment.title} · {assignment.client}
{meetings.length === 0 && (

Noch keine abgeschlossenen Meetings.

)} {meetings.map((meeting, idx) => { const template = templates.find(t => t.id === meeting.phaseTemplateId) const meetingEntries = entries .filter(e => e.meetingInstanceId === meeting.id) .sort((a, b) => a.sequenceIndex - b.sequenceIndex) const meetingAssessments = assessments.filter(a => a.meetingInstanceId === meeting.id) const isLatest = idx === meetings.length - 1 return (
{template?.label ?? 'Meeting'}{template?.isRepeatable ? ` #${meeting.phaseIndex}` : ''}
{new Date(meeting.date).toLocaleDateString('de-DE')}
{isLatest && ( Aktuellstes Meeting )}
{meeting.generalNotes?.trim() && (
)} {meetingEntries.length > 0 && (
{meetingEntries.map(entry => { const instIdx = institutees.findIndex(c => c.id === entry.instituteeId) const inst = institutees[instIdx] const color = INST_COLORS[instIdx % INST_COLORS.length] return (
{inst ? displayName(inst) : '?'}
{entry.note && ( { const critId = entry.lineTags?.find(t => t.text === line)?.criterionItemId const critName = critId ? fbItems.find(i => i.id === critId)?.name : null return critName ? {critName} : null }} /> )} {(entry.fillerCount ?? 0) > 0 && ( äh×{entry.fillerCount} )}
) })}
)} {institutees.length > 0 && fbCategories.length > 0 && meetingAssessments.length > 0 && (
{institutees.map(inst => { const chips = fbCategories.map(cat => { const catItems = fbItems.filter(i => i.categoryId === cat.id) const mode = weightedAverageRating(catItems, itemId => meetingAssessments.find(a => a.instituteeId === inst.id && a.criteriaId === itemId)?.score ?? null, ) return mode ? { cat, mode } : null }).filter(Boolean) as { cat: FeedbackCategory; mode: FeedbackRating }[] if (chips.length === 0) return null return (
{displayName(inst)}: {chips.map(({ cat, mode }) => ( {cat.name} ))}
) })}
)}
) })}
) }