import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box, Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
CircularProgress, Alert, Button, TextField, TableSortLabel, Select, MenuItem, FormControl,
InputLabel, Chip, OutlinedInput, IconButton
} from '@mui/material';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import DeleteIcon from '@mui/icons-material/Delete';
import { getMeetings, updateMeeting, deleteMeeting, createMeeting } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import NewMeetingDialog from '../components/NewMeetingDialog';
// Helper function for sorting
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) return -1;
if (b[orderBy] > a[orderBy]) return 1;
return 0;
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
const DashboardPage = () => {
const navigate = useNavigate();
const { user } = useAuth();
const [meetings, setMeetings] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [deletingIds, setDeletingIds] = useState(new Set());
const [isModalOpen, setIsModalOpen] = useState(false);
// State for filtering and searching
const [topicSearch, setTopicSearch] = useState('');
const [ownerSearch, setOwnerSearch] = useState('');
const [statusFilter, setStatusFilter] = useState([]);
// State for sorting
const [order, setOrder] = useState('asc');
const [orderBy, setOrderBy] = useState('meeting_date');
const fetchMeetings = useCallback(async () => {
try {
const data = await getMeetings();
setMeetings(data);
} catch (err) {
setError('Could not fetch meetings.');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchMeetings();
}, [fetchMeetings]);
const handleCreateMeeting = async (topic, meetingDate) => {
try {
const newMeeting = await createMeeting(topic, new Date(meetingDate).toISOString());
setIsModalOpen(false);
navigate(`/meeting/${newMeeting.id}`);
} catch (err) {
throw new Error(err.response?.data?.error || "Failed to create meeting.");
}
};
const handleSortRequest = (property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const handleStatusChange = async (meetingId, newStatus) => {
try {
await updateMeeting(meetingId, { status: newStatus });
fetchMeetings();
} catch (err) {
setError(`Failed to update status for meeting ${meetingId}.`);
}
};
const handleDelete = async (meetingId) => {
if (window.confirm('Are you sure you want to delete this meeting?')) {
setDeletingIds(prev => new Set(prev).add(meetingId));
try {
await deleteMeeting(meetingId);
fetchMeetings();
} catch (err) {
setError(`Failed to delete meeting ${meetingId}.`);
} finally {
setDeletingIds(prev => {
const newSet = new Set(prev);
newSet.delete(meetingId);
return newSet;
});
}
}
};
const uniqueStatuses = useMemo(() => {
const statuses = new Set(meetings.map(m => m.status));
return Array.from(statuses);
}, [meetings]);
const filteredAndSortedMeetings = useMemo(() => {
let filtered = meetings.filter(meeting => {
const topicMatch = meeting.topic.toLowerCase().includes(topicSearch.toLowerCase());
const ownerMatch = meeting.owner_name ? meeting.owner_name.toLowerCase().includes(ownerSearch.toLowerCase()) : ownerSearch === '';
const statusMatch = statusFilter.length === 0 || statusFilter.includes(meeting.status);
return topicMatch && ownerMatch && statusMatch;
});
return filtered.sort(getComparator(order, orderBy));
}, [meetings, topicSearch, ownerSearch, statusFilter, order, orderBy]);
if (loading) {
return ;
}
const headCells = [
{ id: 'topic', label: 'Topic' },
{ id: 'owner_name', label: 'Owner' },
{ id: 'meeting_date', label: 'Meeting Date' },
{ id: 'status', label: 'Status' },
{ id: 'action_item_count', label: 'Action Items' },
{ id: 'created_at', label: 'Created At' },
];
const statusColorMap = {
'Completed': 'success',
'In Progress': 'info',
'To Do': 'warning',
'Failed': 'error',
};
const allPossibleStatuses = ['To Do', 'In Progress', 'Completed', 'Failed'];
return (
Dashboard
} onClick={() => setIsModalOpen(true)}>
New Meeting
setIsModalOpen(false)}
onCreate={handleCreateMeeting}
/>
{error && setError('')}>{error}}
setTopicSearch(e.target.value)}
sx={{ flex: '1 1 40%' }}
/>
setOwnerSearch(e.target.value)}
sx={{ flex: '1 1 30%' }}
/>
Filter by Status
{headCells.map((headCell) => (
handleSortRequest(headCell.id)}
>
{headCell.label}
))}
Actions
{filteredAndSortedMeetings.map((meeting) => {
const isOwnerOrAdmin = user && (user.role === 'admin' || String(user.id) === String(meeting.created_by_id));
const isDeleting = deletingIds.has(meeting.id);
let taipeiTime = 'N/A';
if (meeting.created_at) {
taipeiTime = new Date(meeting.created_at).toLocaleString('zh-TW', {
timeZone: 'Asia/Taipei',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
}
return (
{meeting.topic}
{meeting.owner_name || 'N/A'}
{meeting.meeting_date ? new Date(meeting.meeting_date).toLocaleDateString() : 'N/A'}
{meeting.action_item_count}
{taipeiTime}
{isOwnerOrAdmin ? (
handleDelete(meeting.id)} color="error" disabled={isDeleting}>
{isDeleting ? : }
) : (
// Invisible placeholder to maintain alignment
)}
);
})}
);
};
export default DashboardPage;