// components/ui/Feed.tsx 'use client'; import * as React from 'react'; import { ChatBubbleLeftEllipsisIcon, TagIcon, } from '@heroicons/react/20/solid'; import clsx from 'clsx'; /* ───────── Types ───────── */ export type FeedPerson = { name: string; href?: string; imageUrl?: string; // optional: Avatar-Bild }; export type FeedTag = { name: string; href?: string; /** z.B. 'fill-red-500' */ color?: string; }; export type FeedChange = { field: string; /** Anzeigename, z.B. "Standort" statt "location" */ label?: string; from: string | null; to: string | null; }; export type FeedItem = | { id: string | number; type: 'comment'; person: FeedPerson; imageUrl?: string; comment: string; date: string; } | { id: string | number; type: 'assignment'; person: FeedPerson; assigned: FeedPerson; date: string; } | { id: string | number; type: 'tags'; person: FeedPerson; tags: FeedTag[]; date: string; } | { id: string | number; type: 'change'; person: FeedPerson; changes: FeedChange[]; date: string; }; export interface FeedProps { items: FeedItem[]; className?: string; } /* ───────── Helper ───────── */ function classNames(...classes: Array) { return classes.filter(Boolean).join(' '); } // deterministische Farbe aus dem Namen function colorFromName(name: string): string { const palette = [ 'bg-sky-500', 'bg-emerald-500', 'bg-violet-500', 'bg-amber-500', 'bg-rose-500', 'bg-indigo-500', 'bg-teal-500', 'bg-fuchsia-500', ]; let hash = 0; for (let i = 0; i < name.length; i++) { hash = (hash * 31 + name.charCodeAt(i)) | 0; } const idx = Math.abs(hash) % palette.length; return palette[idx]; } // sprechende Zusammenfassung für "change" function getChangeSummary(item: Extract): string { const { changes } = item; if (!changes.length) return 'hat Änderungen vorgenommen'; if (changes.length === 1 && changes[0].field === 'tags') { const c = changes[0]; const beforeList = (c.from ?? '') .split(',') .map((s) => s.trim()) .filter(Boolean); const afterList = (c.to ?? '') .split(',') .map((s) => s.trim()) .filter(Boolean); const beforeLower = beforeList.map((x) => x.toLowerCase()); const afterLower = afterList.map((x) => x.toLowerCase()); const added = afterList.filter( (t) => !beforeLower.includes(t.toLowerCase()), ); const removed = beforeList.filter( (t) => !afterLower.includes(t.toLowerCase()), ); const parts: string[] = []; if (added.length) { parts.push(`hinzugefügt: ${added.join(', ')}`); } if (removed.length) { parts.push(`entfernt: ${removed.join(', ')}`); } if (parts.length > 0) { return `hat Tags ${parts.join(' · ')}`; } return 'hat Tags angepasst'; } if (changes.length === 1) { const c = changes[0]; const label = c.label ?? c.field; return `hat ${label} geändert`; } const labels = changes.map((c) => c.label ?? c.field); const uniqueLabels = Array.from(new Set(labels)); const maxShow = 3; if (uniqueLabels.length <= maxShow) { return `hat ${uniqueLabels.join(', ')} geändert`; } const first = uniqueLabels.slice(0, maxShow).join(', '); return `hat ${first} und weitere geändert`; } /* ───────── Component ───────── */ export default function Feed({ items, className }: FeedProps) { if (!items.length) { return (

Keine Aktivitäten vorhanden.

); } return (
    {items.map((item, idx) => { // Icon + Hintergrund ähnlich wie im Beispiel let Icon: React.ComponentType> = ChatBubbleLeftEllipsisIcon; let iconBg = 'bg-gray-400 dark:bg-gray-600'; if (item.type === 'tags') { Icon = TagIcon; iconBg = 'bg-amber-500'; } else if (item.type === 'change') { const isTagsOnly = item.changes.length === 1 && item.changes[0].field === 'tags'; Icon = isTagsOnly ? TagIcon : ChatBubbleLeftEllipsisIcon; iconBg = isTagsOnly ? 'bg-amber-500' : 'bg-emerald-500'; } else if (item.type === 'comment') { iconBg = colorFromName(item.person.name); } else if (item.type === 'assignment') { iconBg = 'bg-indigo-500'; } // Textinhalt ähnlich wie "content + target" let content: React.ReactNode = null; if (item.type === 'comment') { content = (

    {item.person.name} {' '} hat kommentiert:{' '} {item.comment}

    ); } else if (item.type === 'assignment') { content = (

    {item.person.name} {' '} hat{' '} {item.assigned.name} {' '} zugewiesen.

    ); } else if (item.type === 'tags') { content = (

    {item.person.name} {' '} hat Tags hinzugefügt:{' '} {item.tags.map((t) => t.name).join(', ')}

    ); } else if (item.type === 'change') { const summary = getChangeSummary(item); content = (

    {item.person.name} {' '} {summary}

    {item.changes.length > 0 && (

    {item.changes.slice(0, 2).map((c, i) => ( {c.from ?? '—'} {c.to ?? '—'} {i < Math.min(2, item.changes.length) - 1 && ( · )} ))} {item.changes.length > 2 && ( · … )}

    )}
    ); } return (
  • {idx !== items.length - 1 ? (
  • ); })}
); }