feat: add admin test checklist page
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
export { AdminTestChecklistPage } from './ui/AdminTestChecklistPage'
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import Accordion from '@mui/material/Accordion'
|
||||||
|
import AccordionDetails from '@mui/material/AccordionDetails'
|
||||||
|
import AccordionSummary from '@mui/material/AccordionSummary'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Checkbox from '@mui/material/Checkbox'
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
|
import Dialog from '@mui/material/Dialog'
|
||||||
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
|
import DialogContent from '@mui/material/DialogContent'
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText'
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import Stack from '@mui/material/Stack'
|
||||||
|
import Table from '@mui/material/Table'
|
||||||
|
import TableBody from '@mui/material/TableBody'
|
||||||
|
import TableCell from '@mui/material/TableCell'
|
||||||
|
import TableContainer from '@mui/material/TableContainer'
|
||||||
|
import TableHead from '@mui/material/TableHead'
|
||||||
|
import TableRow from '@mui/material/TableRow'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { TEST_CHECKLIST_ITEMS } from '@shared/constants/test-checklist-items'
|
||||||
|
import { fetchTestChecklistResults, resetTestChecklist, updateTestChecklistItem } from '@/entities/test-checklist/api/test-checklist-api'
|
||||||
|
|
||||||
|
function formatDate(iso: string): string {
|
||||||
|
return new Date(iso).toLocaleString('ru-RU', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminTestChecklistPage() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||||
|
const [expanded, setExpanded] = useState<string | false>(false)
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['admin', 'test-checklist'],
|
||||||
|
queryFn: fetchTestChecklistResults,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: ({ itemKey, passed }: { itemKey: string; passed: boolean }) => updateTestChecklistItem(itemKey, passed),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'test-checklist'] })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetMutation = useMutation({
|
||||||
|
mutationFn: resetTestChecklist,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'test-checklist'] })
|
||||||
|
setConfirmOpen(false)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const sections = useMemo(() => {
|
||||||
|
const map = new Map<string, typeof TEST_CHECKLIST_ITEMS>()
|
||||||
|
for (const item of TEST_CHECKLIST_ITEMS) {
|
||||||
|
const existing = map.get(item.section) || []
|
||||||
|
existing.push(item)
|
||||||
|
map.set(item.section, existing)
|
||||||
|
}
|
||||||
|
return Array.from(map.entries())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const results = data?.results ?? {}
|
||||||
|
|
||||||
|
const total = TEST_CHECKLIST_ITEMS.length
|
||||||
|
const passedCount = TEST_CHECKLIST_ITEMS.filter((i) => results[i.key]?.passed).length
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 3 }}>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||||
|
Тест-чеклист
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Пройдено: {passedCount} из {total}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button variant="outlined" color="warning" onClick={() => setConfirmOpen(true)} disabled={resetMutation.isPending}>
|
||||||
|
Сбросить все
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
sections.map(([section, items]) => (
|
||||||
|
<Accordion key={section} expanded={expanded === section} onChange={(_, isExpanded) => setExpanded(isExpanded ? section : false)}>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
|
<Typography sx={{ fontWeight: 600 }}>{section}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ px: 0 }}>
|
||||||
|
<TableContainer>
|
||||||
|
<Table size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell padding="checkbox" sx={{ width: 56 }} />
|
||||||
|
<TableCell sx={{ fontWeight: 600 }}>Действие</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 600 }}>Ожидаемый результат</TableCell>
|
||||||
|
<TableCell sx={{ fontWeight: 600, whiteSpace: 'nowrap' }}>Дата проверки</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{items.map((item) => {
|
||||||
|
const r = results[item.key]
|
||||||
|
return (
|
||||||
|
<TableRow key={item.key} hover>
|
||||||
|
<TableCell padding="checkbox">
|
||||||
|
<Checkbox
|
||||||
|
checked={r?.passed ?? false}
|
||||||
|
onChange={(_, checked) => updateMutation.mutate({ itemKey: item.key, passed: checked })}
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{item.action}</TableCell>
|
||||||
|
<TableCell>{item.expectedResult}</TableCell>
|
||||||
|
<TableCell sx={{ whiteSpace: 'nowrap', color: r ? 'text.primary' : 'text.disabled' }}>
|
||||||
|
{r ? formatDate(r.checkedAt) : '—'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Dialog open={confirmOpen} onClose={() => setConfirmOpen(false)}>
|
||||||
|
<DialogTitle>Сбросить все проверки?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>Все отметки будут удалены. Это действие нельзя отменить.</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setConfirmOpen(false)}>Отмена</Button>
|
||||||
|
<Button color="warning" onClick={() => resetMutation.mutate()} autoFocus>
|
||||||
|
Сбросить
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user