import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { X, Tag, Pencil, Trash2, Loader2, Search, Check, AlertTriangle } from 'lucide-react'; import { api } from '../api/client'; import type { TagInfo } from '../api/client'; import { Card, CardContent } from './Card'; import { Button } from './Button'; import { useToast } from '../contexts/ToastContext'; interface TagManagementModalProps { onClose: () => void; } export function TagManagementModal({ onClose }: TagManagementModalProps) { const queryClient = useQueryClient(); const { showToast } = useToast(); const [search, setSearch] = useState(''); const [editingTag, setEditingTag] = useState(null); const [editValue, setEditValue] = useState(''); const [deleteConfirm, setDeleteConfirm] = useState(null); const [sortBy, setSortBy] = useState<'count' | 'name'>('count'); // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (editingTag) { setEditingTag(null); } else if (deleteConfirm) { setDeleteConfirm(null); } else { onClose(); } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose, editingTag, deleteConfirm]); const { data: tags, isLoading } = useQuery({ queryKey: ['tags'], queryFn: api.getTags, }); const renameMutation = useMutation({ mutationFn: ({ oldName, newName }: { oldName: string; newName: string }) => api.renameTag(oldName, newName), onSuccess: (data, { oldName, newName }) => { queryClient.invalidateQueries({ queryKey: ['tags'] }); queryClient.invalidateQueries({ queryKey: ['archives'] }); showToast(`Renamed "${oldName}" to "${newName}" in ${data.affected} archive${data.affected !== 1 ? 's' : ''}`); setEditingTag(null); }, onError: (error: Error) => { showToast(error.message || 'Failed to rename tag', 'error'); }, }); const deleteMutation = useMutation({ mutationFn: (name: string) => api.deleteTag(name), onSuccess: (data, name) => { queryClient.invalidateQueries({ queryKey: ['tags'] }); queryClient.invalidateQueries({ queryKey: ['archives'] }); showToast(`Deleted "${name}" from ${data.affected} archive${data.affected !== 1 ? 's' : ''}`); setDeleteConfirm(null); }, onError: (error: Error) => { showToast(error.message || 'Failed to delete tag', 'error'); }, }); const startEdit = (tag: TagInfo) => { setEditingTag(tag.name); setEditValue(tag.name); setDeleteConfirm(null); }; const cancelEdit = () => { setEditingTag(null); setEditValue(''); }; const submitEdit = () => { if (!editingTag || !editValue.trim()) return; const newName = editValue.trim(); if (newName === editingTag) { cancelEdit(); return; } renameMutation.mutate({ oldName: editingTag, newName }); }; const handleEditKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); submitEdit(); } else if (e.key === 'Escape') { e.preventDefault(); cancelEdit(); } }; const confirmDelete = (name: string) => { setDeleteConfirm(name); setEditingTag(null); }; const executeDelete = () => { if (deleteConfirm) { deleteMutation.mutate(deleteConfirm); } }; // Filter and sort tags const filteredTags = tags ?.filter(t => t.name.toLowerCase().includes(search.toLowerCase())) .sort((a, b) => { if (sortBy === 'count') { return b.count - a.count || a.name.localeCompare(b.name); } return a.name.localeCompare(b.name); }); const totalUsage = tags?.reduce((sum, t) => sum + t.count, 0) || 0; return (
{/* Header */}

Manage Tags

{/* Search and sort */}
setSearch(e.target.value)} />
{tags && (

{tags.length} tag{tags.length !== 1 ? 's' : ''} across {totalUsage} usage{totalUsage !== 1 ? 's' : ''}

)}
{/* Tags list */}
{isLoading ? (
) : !filteredTags?.length ? (
{search ? 'No tags match your search' : 'No tags found'}
) : (
{filteredTags.map((tag) => (
{editingTag === tag.name ? ( // Edit mode
setEditValue(e.target.value)} onKeyDown={handleEditKeyDown} autoFocus />
) : deleteConfirm === tag.name ? ( // Delete confirmation
Delete "{tag.name}" from {tag.count} archive{tag.count !== 1 ? 's' : ''}?
) : ( // Normal display <> {tag.name} {tag.count}
)}
))}
)}
{/* Footer */}
); }