import { useState, useCallback } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' import remarkBreaks from 'remark-breaks' import { Trash2, Pin, PinOff, Pencil, Maximize2 } from 'lucide-react' import { FONT } from './CollabNotes.constants' import { AuthedImg } from './CollabNotesAuthedImg' import { UserAvatar } from './CollabNotesUserAvatar' import { WebsiteThumbnail } from './CollabNotesWebsiteThumbnail' import type { CollabNote, NoteFile } from './CollabNotes.types' import type { User } from '../../types' // ── Note Card ─────────────────────────────────────────────────────────────── interface NoteCardProps { note: CollabNote currentUser: User canEdit: boolean onUpdate: (noteId: number, data: Partial) => Promise onDelete: (noteId: number) => void onEdit: (note: CollabNote) => void onView: (note: CollabNote) => void onPreviewFile: (file: NoteFile) => void getCategoryColor: (category: string) => string tripId: number t: (key: string) => string } export function NoteCard({ note, currentUser, canEdit, onUpdate, onDelete, onEdit, onView, onPreviewFile, getCategoryColor, tripId, t }: NoteCardProps) { const [hovered, setHovered] = useState(false) const author = note.author || note.user || { username: note.username, avatar: note.avatar_url || (note.avatar ? `/uploads/avatars/${note.avatar}` : null) } const color = getCategoryColor ? getCategoryColor(note.category) : (note.color || '#6366f1') const handleTogglePin = useCallback(() => { onUpdate(note.id, { pinned: !note.pinned }) }, [note.id, note.pinned, onUpdate]) const handleDelete = useCallback(() => { onDelete(note.id) }, [note.id, onDelete]) return (
setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ position: 'relative', borderRadius: 12, overflow: 'hidden', border: `1px solid ${note.pinned ? color + '40' : color + '25'}`, background: note.pinned ? `${color}08` : 'var(--bg-card)', display: 'flex', flexDirection: 'column', fontFamily: FONT, transition: 'transform 0.12s, box-shadow 0.12s', ...(hovered ? { transform: 'translateY(-1px)', boxShadow: '0 4px 16px rgba(0,0,0,0.08)' } : {}), }} > {/* Header bar — like reservation cards */}
{!!note.pinned && } {note.title} {note.category && ( {note.category} )} {/* Hover actions in header */} {(
{note.content && ( )} {canEdit && } {canEdit && } {canEdit && }
{/* Author avatar */}
{ const tip = e.currentTarget.querySelector('[data-tip]'); if (tip) tip.style.opacity = '1' }} onMouseLeave={e => { const tip = e.currentTarget.querySelector('[data-tip]'); if (tip) tip.style.opacity = '0' }}>
{author.username}
)}
{/* Card body */}
{note.content && (
{note.content}
)}
{/* Right: website + attachment thumbnails */} {(note.website || (note.attachments?.length ?? 0) > 0) && (
{/* Website */} {note.website && (
Link
)} {/* Files */} {(note.attachments || []).length > 0 && (
{t('files.title')}
{(note.attachments || []).slice(0, note.website ? 1 : 2).map(a => { const isImage = a.mime_type?.startsWith('image/') const ext = (a.original_name || '').split('.').pop()?.toUpperCase() || '?' return isImage ? ( onPreviewFile?.(a)} onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.08)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }} onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }} /> ) : (
onPreviewFile?.(a)} style={{ width: 48, height: 48, borderRadius: 8, cursor: 'pointer', background: a.mime_type === 'application/pdf' ? '#ef44441a' : 'var(--bg-secondary)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 1, transition: 'transform 0.12s, box-shadow 0.12s', }} onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.08)'; e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)' }} onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = 'none' }}> {ext}
) })} {(note.attachments?.length || 0) > (note.website ? 1 : 2) && ( +{(note.attachments?.length || 0) - (note.website ? 1 : 2)} )}
)}
)}
) }