Files
motovaultpro/frontend/src/features/admin/pages/AdminCommunityStationsPage.tsx
2025-12-21 11:31:10 -06:00

237 lines
7.3 KiB
TypeScript

/**
* @ai-summary Admin desktop page for managing community station submissions
*/
import React, { useState, useCallback } from 'react';
import {
Box,
Paper,
Tabs,
Tab,
Select,
MenuItem,
FormControl,
InputLabel,
useMediaQuery,
useTheme,
Grid,
Typography,
} from '@mui/material';
import { AdminSectionHeader } from '../components/AdminSectionHeader';
import { CommunityStationReviewQueue } from '../components/CommunityStationReviewQueue';
import { CommunityStationsList } from '../../stations/components/CommunityStationsList';
import {
usePendingSubmissions,
useAllCommunitySubmissions,
useReviewStation,
} from '../../stations/hooks/useCommunityStations';
import toast from 'react-hot-toast';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
const TabPanel: React.FC<TabPanelProps> = ({ children, value, index }) => {
if (value !== index) {
return null;
}
return (
<div role="tabpanel">
<Box sx={{ padding: 2 }}>{children}</Box>
</div>
);
};
/**
* Admin page for reviewing and managing community station submissions
* Desktop layout with tab navigation and status filtering
*/
export const AdminCommunityStationsPage: React.FC = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [tabValue, setTabValue] = useState(0);
const [statusFilter, setStatusFilter] = useState<string>('');
const [page, setPage] = useState(0);
// Hooks
const pendingSubmissions = usePendingSubmissions(page, 50);
const allSubmissions = useAllCommunitySubmissions(statusFilter || undefined, page, 50);
const reviewMutation = useReviewStation();
// Determine which data to display
const displayData = tabValue === 0 ? pendingSubmissions : allSubmissions;
const stations = displayData.data?.stations || [];
const totalPages = displayData.data?.total
? Math.ceil(displayData.data.total / 50)
: 1;
// Handle approval
const handleApprove = useCallback(
async (id: string) => {
try {
await reviewMutation.mutateAsync({
id,
decision: { status: 'approved' },
});
toast.success('Station approved');
} catch (error: any) {
toast.error(error?.response?.data?.message || 'Failed to approve station');
}
},
[reviewMutation]
);
// Handle rejection
const handleReject = useCallback(
async (id: string, reason: string) => {
try {
await reviewMutation.mutateAsync({
id,
decision: { status: 'rejected', rejectionReason: reason },
});
toast.success('Station rejected');
} catch (error: any) {
toast.error(error?.response?.data?.message || 'Failed to reject station');
}
},
[reviewMutation]
);
return (
<Box>
{/* Header */}
<AdminSectionHeader
title="Community Gas Station Reviews"
stats={[
{ label: 'Pending', value: pendingSubmissions.data?.total || 0 },
{ label: 'Total', value: allSubmissions.data?.total || 0 }
]}
/>
{/* Main content */}
<Paper sx={{ m: 2 }}>
<Tabs
value={tabValue}
onChange={(_, newValue) => setTabValue(newValue)}
indicatorColor="primary"
textColor="primary"
aria-label="admin community stations tabs"
variant={isMobile ? 'scrollable' : 'standard'}
>
<Tab label={`Pending (${pendingSubmissions.data?.total || 0})`} id="tab-0" />
<Tab label="All Submissions" id="tab-1" />
</Tabs>
{/* Pending tab */}
<TabPanel value={tabValue} index={0}>
<CommunityStationReviewQueue
stations={stations}
loading={displayData.isLoading}
error={displayData.error ? 'Failed to load submissions' : null}
onApprove={handleApprove}
onReject={handleReject}
page={page}
onPageChange={setPage}
totalPages={totalPages}
isReviewLoading={reviewMutation.isPending}
/>
</TabPanel>
{/* All submissions tab */}
<TabPanel value={tabValue} index={1}>
<Grid container spacing={2} sx={{ mb: 2 }}>
<Grid item xs={12} sm={6} md={3}>
<FormControl fullWidth size="small">
<InputLabel>Filter by Status</InputLabel>
<Select
value={statusFilter}
label="Filter by Status"
onChange={(e) => {
setStatusFilter(e.target.value);
setPage(0);
}}
>
<MenuItem value="">All</MenuItem>
<MenuItem value="pending">Pending</MenuItem>
<MenuItem value="approved">Approved</MenuItem>
<MenuItem value="rejected">Rejected</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<CommunityStationsList
stations={stations}
loading={displayData.isLoading}
error={displayData.error ? 'Failed to load submissions' : null}
isAdmin={true}
onApprove={handleApprove}
onReject={handleReject}
page={page}
onPageChange={setPage}
totalPages={totalPages}
/>
</TabPanel>
</Paper>
{/* Stats card */}
{pendingSubmissions.data && (
<Paper sx={{ m: 2, p: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={3}>
<Box>
<Typography variant="h4" color="primary">
{pendingSubmissions.data.total}
</Typography>
<Typography variant="body2" color="textSecondary">
Pending Review
</Typography>
</Box>
</Grid>
{allSubmissions.data && (
<>
<Grid item xs={12} sm={6} md={3}>
<Box>
<Typography variant="h4" color="success.main">
{allSubmissions.data.stations.filter((s) => s.status === 'approved').length}
</Typography>
<Typography variant="body2" color="textSecondary">
Approved
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box>
<Typography variant="h4" color="error.main">
{allSubmissions.data.stations.filter((s) => s.status === 'rejected').length}
</Typography>
<Typography variant="body2" color="textSecondary">
Rejected
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box>
<Typography variant="h4">
{allSubmissions.data.total}
</Typography>
<Typography variant="body2" color="textSecondary">
Total Submitted
</Typography>
</Box>
</Grid>
</>
)}
</Grid>
</Paper>
)}
</Box>
);
};
export default AdminCommunityStationsPage;