fix: Evaluation.tsx nach Assignment gruppiert, Papierkorb/Status gefiltert
- Oberste Gliederungsebene ist jetzt das Assignment, Institutees darunter - Meeting-Filter: nur status='done', keine Papierkorb-Meetings (deletedAt) - Migriert auf feedbackCriterionItems statt Legacy categories/criteria - zustand aus package.json entfernt (ungenutzt)
This commit is contained in:
parent
63ca6583fa
commit
f57db9d161
|
|
@ -75,7 +75,7 @@ export default function Evaluation() {
|
|||
for (const a of assignments) {
|
||||
const meetingIds = (await db.meetingInstances
|
||||
.where('assignmentId').equals(a.id!).toArray()
|
||||
).map(m => m.id!)
|
||||
).filter(m => !m.deletedAt && m.status === 'done').map(m => m.id!)
|
||||
|
||||
for (const instId of a.instituteeIds) {
|
||||
const inst = consultants.find(c => c.id === instId)
|
||||
|
|
@ -87,7 +87,7 @@ export default function Evaluation() {
|
|||
if (instAssessments.length === 0) continue
|
||||
|
||||
const getScore = (itemId: number): FeedbackRating | null => {
|
||||
const relevant = instAssessments.filter(a => a.criteriaId === itemId && a.score !== null)
|
||||
const relevant = instAssessments.filter(a => a.criteriaId === itemId && a.score !== null && a.score !== 'na')
|
||||
if (relevant.length === 0) return null
|
||||
const avg = relevant.reduce((s, a) => s + RATING_NUM_MAP[a.score!], 0) / relevant.length
|
||||
if (avg < 1.5) return 'not_client_ready'
|
||||
|
|
@ -114,62 +114,82 @@ export default function Evaluation() {
|
|||
|
||||
const rowKey = (r: InstScore) => `${r.assignment.id}-${r.institutee.id}`
|
||||
|
||||
const assignmentGroups = data.reduce<Map<number, { assignment: Assignment; rows: InstScore[] }>>(
|
||||
(map, row) => {
|
||||
const key = row.assignment.id!
|
||||
const group = map.get(key)
|
||||
if (group) group.rows.push(row)
|
||||
else map.set(key, { assignment: row.assignment, rows: [row] })
|
||||
return map
|
||||
},
|
||||
new Map()
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-xl font-bold text-gray-800">Auswertung</h1>
|
||||
{data.length === 0 && (
|
||||
<p className="text-gray-500 text-sm">Noch keine Bewertungen vorhanden.</p>
|
||||
)}
|
||||
{data.map(row => (
|
||||
<div key={rowKey(row)} className="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<div
|
||||
className="flex items-start justify-between cursor-pointer"
|
||||
onClick={() => setSelected(selected === rowKey(row) ? null : rowKey(row))}>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-800">
|
||||
{row.institutee.firstName} {row.institutee.lastName}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">{row.assignment.title} · {row.assignment.client}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`px-3 py-1 rounded-full text-sm font-bold ${ratingColor(row.overall)}`}>
|
||||
{ratingLabel(row.overall)}
|
||||
</div>
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); navigate(`/assignment/${row.assignment.id}/feedback/${row.institutee.id}`) }}
|
||||
className="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded-lg">
|
||||
Feedback
|
||||
</button>
|
||||
</div>
|
||||
{[...assignmentGroups.values()].map(({ assignment, rows }) => (
|
||||
<div key={assignment.id} className="space-y-2">
|
||||
<div className="px-1">
|
||||
{assignment.assignmentNumber && (
|
||||
<div className="text-xs font-semibold text-brand uppercase tracking-wide">{assignment.assignmentNumber}</div>
|
||||
)}
|
||||
<h2 className="font-bold text-gray-800">{assignment.title}</h2>
|
||||
<div className="text-xs text-gray-400">{assignment.client}</div>
|
||||
</div>
|
||||
|
||||
{selected === rowKey(row) && (
|
||||
<div className="mt-4 space-y-4">
|
||||
{dimensions.map(dim => {
|
||||
const dimCats = categories.filter(c => c.dimensionId === dim.id)
|
||||
const dimScores = row.catScores.filter(cs =>
|
||||
dimCats.some(c => c.id === cs.category.id) && cs.avg !== null
|
||||
)
|
||||
if (dimScores.length === 0) return null
|
||||
return (
|
||||
<div key={dim.id}>
|
||||
<div className="text-xs font-semibold uppercase text-gray-400 tracking-wide mb-2">
|
||||
{dim.name}
|
||||
</div>
|
||||
{dimScores.map(({ category, avg }) => (
|
||||
<div key={category.id}
|
||||
className="flex items-center justify-between py-1.5 border-b border-gray-50 last:border-0">
|
||||
<span className="text-sm text-gray-700">{category.name}</span>
|
||||
<span className={`text-xs font-semibold px-2 py-0.5 rounded ${ratingColor(avg)}`}>
|
||||
{ratingLabel(avg)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{rows.map(row => (
|
||||
<div key={rowKey(row)} className="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<div
|
||||
className="flex items-start justify-between cursor-pointer"
|
||||
onClick={() => setSelected(selected === rowKey(row) ? null : rowKey(row))}>
|
||||
<div className="font-semibold text-gray-800">
|
||||
{row.institutee.firstName} {row.institutee.lastName}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`px-3 py-1 rounded-full text-sm font-bold ${ratingColor(row.overall)}`}>
|
||||
{ratingLabel(row.overall)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); navigate(`/assignment/${row.assignment.id}/feedback/${row.institutee.id}`) }}
|
||||
className="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded-lg">
|
||||
Feedback
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selected === rowKey(row) && (
|
||||
<div className="mt-4 space-y-4">
|
||||
{dimensions.map(dim => {
|
||||
const dimCats = categories.filter(c => c.dimensionId === dim.id)
|
||||
const dimScores = row.catScores.filter(cs =>
|
||||
dimCats.some(c => c.id === cs.category.id) && cs.avg !== null
|
||||
)
|
||||
if (dimScores.length === 0) return null
|
||||
return (
|
||||
<div key={dim.id}>
|
||||
<div className="text-xs font-semibold uppercase text-gray-400 tracking-wide mb-2">
|
||||
{dim.name}
|
||||
</div>
|
||||
{dimScores.map(({ category, avg }) => (
|
||||
<div key={category.id}
|
||||
className="flex items-center justify-between py-1.5 border-b border-gray-50 last:border-0">
|
||||
<span className="text-sm text-gray-700">{category.name}</span>
|
||||
<span className={`text-xs font-semibold px-2 py-0.5 rounded ${ratingColor(avg)}`}>
|
||||
{ratingLabel(avg)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user