import React, { useState, useEffect } from 'react'; import { Container, Typography, Button, Box, Paper, IconButton, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions, DialogContentText, TextField, InputAdornment, Chip, Alert, Card, CardContent, CardActions, } from '@mui/material'; import Grid from '@mui/material/GridLegacy'; import { Add as AddIcon, Edit as EditIcon, Delete as DeleteIcon, Search as SearchIcon, FilterList as FilterIcon, } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import Label, { type LabelData } from '../components/Labels/Label'; import LabelCreateDialog from '../components/Labels/LabelCreateDialog'; import { useApi } from '../hooks/useApi'; const LabelsPage: React.FC = () => { const navigate = useNavigate(); const api = useApi(); const [labels, setLabels] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [showSystemLabels, setShowSystemLabels] = useState(true); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [editingLabel, setEditingLabel] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [labelToDelete, setLabelToDelete] = useState(null); const [error, setError] = useState(null); // Fetch labels const fetchLabels = async () => { try { setLoading(true); const response = await api.get('/labels?include_counts=true'); // Validate response status and data format if (response.status === 200 && Array.isArray(response.data)) { setLabels(response.data); setError(null); } else { console.error('Invalid response - Status:', response.status, 'Data:', response.data); setError(`Server returned unexpected response (${response.status})`); setLabels([]); // Reset to empty array to prevent filter errors } } catch (error: any) { console.error('Failed to fetch labels:', error); // Handle different types of errors more specifically if (error?.response?.status === 401) { setError('Authentication required. Please log in again.'); } else if (error?.response?.status === 403) { setError('Access denied. You do not have permission to view labels.'); } else if (error?.response?.status >= 500) { setError('Server error. Please try again later.'); } else { setError('Failed to load labels. Please check your connection.'); } setLabels([]); // Reset to empty array to prevent filter errors } finally { setLoading(false); } }; useEffect(() => { fetchLabels(); }, []); // Filter labels based on search and system label preference const filteredLabels = Array.isArray(labels) ? labels.filter(label => { const matchesSearch = label.name.toLowerCase().includes(searchTerm.toLowerCase()) || (label.description || '').toLowerCase().includes(searchTerm.toLowerCase()); const matchesFilter = showSystemLabels || !label.is_system; return matchesSearch && matchesFilter; }) : []; // Group labels const systemLabels = filteredLabels.filter(label => label.is_system); const userLabels = filteredLabels.filter(label => !label.is_system); const handleCreateLabel = async (labelData: Omit) => { try { const response = await api.post('/labels', labelData); await fetchLabels(); // Refresh the list } catch (error) { console.error('Failed to create label:', error); throw error; } }; const handleUpdateLabel = async (labelData: Omit) => { if (!editingLabel) return; try { await api.put(`/labels/${editingLabel.id}`, labelData); await fetchLabels(); // Refresh the list setEditingLabel(null); } catch (error) { console.error('Failed to update label:', error); throw error; } }; const handleDeleteLabel = async (labelId: string) => { try { await api.delete(`/labels/${labelId}`); await fetchLabels(); // Refresh the list setDeleteDialogOpen(false); setLabelToDelete(null); } catch (error) { console.error('Failed to delete label:', error); setError('Failed to delete label'); } }; const openDeleteDialog = (label: LabelData) => { setLabelToDelete(label); setDeleteDialogOpen(true); }; const openEditDialog = (label: LabelData) => { setEditingLabel(label); }; if (loading) { return ( Loading labels... ); } return ( {/* Header */} Label Management {/* Error Alert */} {error && ( setError(null)} sx={{ mb: 3 }}> {error} )} {/* Search and Filters */} setSearchTerm(e.target.value)} InputProps={{ startAdornment: ( ), }} /> setShowSystemLabels(!showSystemLabels)} variant={showSystemLabels ? 'filled' : 'outlined'} /> {/* Labels List */} {/* System Labels */} {systemLabels.length > 0 && ( System Labels {systemLabels.map((label) => ( {label.description && ( {label.description} )} Documents: {label.document_count || 0} Sources: {label.source_count || 0} ))} )} {/* User Labels */} {userLabels.length > 0 && ( My Labels {userLabels.map((label) => ( {label.description && ( {label.description} )} Documents: {label.document_count || 0} Sources: {label.source_count || 0} ))} )} {/* Empty State */} {filteredLabels.length === 0 && ( No labels found {searchTerm ? `No labels match "${searchTerm}"` : "You haven't created any labels yet" } {!searchTerm && ( )} )} {/* Create/Edit Label Dialog */} { setCreateDialogOpen(false); setEditingLabel(null); }} onSubmit={editingLabel ? handleUpdateLabel : handleCreateLabel} editingLabel={editingLabel || undefined} /> {/* Delete Confirmation Dialog */} { setDeleteDialogOpen(false); setLabelToDelete(null); }} > Delete Label Are you sure you want to delete the label "{labelToDelete?.name}"? {(labelToDelete?.document_count || 0) > 0 && ( <> This label is currently used by {labelToDelete?.document_count} document(s). )} ); }; export default LabelsPage;