Files
template-tutoring-admin-das…/frontend/src/pages/admin/EnquiriesPanel.tsx
T

110 lines
4.0 KiB
TypeScript

import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import MarkEmailReadRoundedIcon from '@mui/icons-material/MarkEmailReadRounded';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { useEffect, useState } from 'react';
import { deleteEnquiry, listEnquiries, patchEnquiry } from '../../services/content';
import { SEED, type Enquiry } from '../../services/mockData';
export function EnquiriesPanel() {
const [rows, setRows] = useState<Enquiry[]>([]);
const [loaded, setLoaded] = useState(false);
const load = () => {
listEnquiries()
.then((d) => {
setRows(d);
setLoaded(true);
})
.catch(() => {
// Preview / offline: show the seed so the panel isn't empty.
setRows(SEED.enquiries);
setLoaded(true);
});
};
useEffect(load, []);
const toggleRead = async (e: Enquiry) => {
setRows((rs) => rs.map((r) => (r.id === e.id ? { ...r, is_read: !r.is_read } : r)));
try {
await patchEnquiry(e.id, !e.is_read);
} catch {
/* optimistic */
}
};
const remove = async (e: Enquiry) => {
setRows((rs) => rs.filter((r) => r.id !== e.id));
try {
await deleteEnquiry(e.id);
} catch {
/* optimistic */
}
};
const unread = rows.filter((r) => !r.is_read).length;
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
<Typography variant="h5">Enquiries</Typography>
{unread > 0 && <Chip label={`${unread} unread`} color="secondary" size="small" />}
</Box>
<Stack spacing={1.5}>
{loaded && rows.length === 0 && (
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
No enquiries yet. Messages from your contact form will appear here.
</Typography>
)}
{rows.map((e) => (
<Card key={e.id} sx={{ borderColor: e.is_read ? 'divider' : 'secondary.main' }}>
<CardContent>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 1 }}>
<Box sx={{ minWidth: 0 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
{e.name}
{!e.is_read && (
<Chip label="New" color="secondary" size="small" sx={{ ml: 1, height: 20 }} />
)}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{[e.email, e.phone].filter(Boolean).join(' · ')}
{e.subject_interest ? ` · Interested in ${e.subject_interest}` : ''}
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 0.5, flexShrink: 0 }}>
<Tooltip title={e.is_read ? 'Mark as unread' : 'Mark as read'}>
<IconButton onClick={() => toggleRead(e)} color={e.is_read ? 'default' : 'primary'}>
<MarkEmailReadRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton color="error" onClick={() => remove(e)}>
<DeleteRoundedIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
<Typography variant="body1" sx={{ mt: 1.5 }}>
{e.message}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary', mt: 1, display: 'block' }}>
{new Date(e.created_at).toLocaleString()}
</Typography>
</CardContent>
</Card>
))}
</Stack>
</Box>
);
}