diff --git a/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx b/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx index c9d0f2d..f5207f5 100644 --- a/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx +++ b/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx @@ -1,17 +1,16 @@ -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import Alert from '@mui/material/Alert' 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 IconButton from '@mui/material/IconButton' import Stack from '@mui/material/Stack' import Table from '@mui/material/Table' import TableBody from '@mui/material/TableBody' @@ -19,7 +18,11 @@ 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 TextField from '@mui/material/TextField' +import Tooltip from '@mui/material/Tooltip' import Typography from '@mui/material/Typography' +import CheckCircleIcon from '@mui/icons-material/CheckCircle' +import CancelIcon from '@mui/icons-material/Cancel' 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' @@ -29,6 +32,13 @@ import { updateTestChecklistItem, } from '@/entities/test-checklist/api/test-checklist-api' +type Status = 'passed' | 'failed' | 'unchecked' + +function statusFromResult(r: { passed: boolean; comment: string | null } | undefined): Status { + if (!r) return 'unchecked' + return r.passed ? 'passed' : 'failed' +} + function formatDate(iso: string): string { return new Date(iso).toLocaleString('ru-RU', { day: '2-digit', @@ -39,10 +49,30 @@ function formatDate(iso: string): string { }) } +function StatusIcon({ status, onClick }: { status: Status; onClick: () => void }) { + const icons = { + passed: , + failed: , + unchecked: , + } + + return ( + + + {icons[status]} + + + ) +} + export function AdminTestChecklistPage() { const queryClient = useQueryClient() const [confirmOpen, setConfirmOpen] = useState(false) const [expanded, setExpanded] = useState(false) + const [errorDialogOpen, setErrorDialogOpen] = useState(false) + const [errorItemKey, setErrorItemKey] = useState(null) + const [errorComment, setErrorComment] = useState('') + const [errorCommentError, setErrorCommentError] = useState(false) const { data, isLoading, isError } = useQuery({ queryKey: ['admin', 'test-checklist'], @@ -50,7 +80,8 @@ export function AdminTestChecklistPage() { }) const updateMutation = useMutation({ - mutationFn: ({ itemKey, passed }: { itemKey: string; passed: boolean }) => updateTestChecklistItem(itemKey, passed), + mutationFn: ({ itemKey, passed, comment }: { itemKey: string; passed: boolean; comment?: string | null }) => + updateTestChecklistItem(itemKey, passed, comment), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'test-checklist'] }) }, @@ -79,6 +110,40 @@ export function AdminTestChecklistPage() { const total = TEST_CHECKLIST_ITEMS.length const passedCount = TEST_CHECKLIST_ITEMS.filter((i) => results[i.key]?.passed).length + const handleStatusClick = useCallback( + (itemKey: string) => { + const current = statusFromResult(results[itemKey]) + if (current === 'failed') { + // Clicking on failed → reset to unchecked + updateMutation.mutate({ itemKey, passed: false, comment: null }) + } else if (current === 'unchecked') { + // Clicking on unchecked → mark as passed + updateMutation.mutate({ itemKey, passed: true }) + } else { + // Clicking on passed → open error dialog + setErrorItemKey(itemKey) + setErrorComment('') + setErrorCommentError(false) + setErrorDialogOpen(true) + } + }, + [results, updateMutation], + ) + + const handleSaveError = () => { + if (!errorItemKey) return + if (!errorComment.trim()) { + setErrorCommentError(true) + return + } + updateMutation.mutate({ itemKey: errorItemKey, passed: false, comment: errorComment.trim() }) + setErrorDialogOpen(false) + setErrorItemKey(null) + setErrorComment('') + } + + const errorItem = errorItemKey ? TEST_CHECKLIST_ITEMS.find((i) => i.key === errorItemKey) : null + return ( @@ -121,26 +186,46 @@ export function AdminTestChecklistPage() { - + Действие Ожидаемый результат + Комментарий Дата проверки {items.map((item) => { const r = results[item.key] + const status = statusFromResult(r) return ( - - updateMutation.mutate({ itemKey: item.key, passed: checked })} - color="success" - /> + + handleStatusClick(item.key)} /> {item.action} {item.expectedResult} + + {r?.comment ? ( + + + {r.comment} + + + ) : ( + + — + + )} + {r ? formatDate(r.checkedAt) : '—'} @@ -158,7 +243,7 @@ export function AdminTestChecklistPage() { setConfirmOpen(false)}> Сбросить все проверки? - Все отметки будут удалены. Это действие нельзя отменить. + Все отметки будут удалены. Это действие нельзя отменить. @@ -167,6 +252,35 @@ export function AdminTestChecklistPage() { + + setErrorDialogOpen(false)} maxWidth="sm" fullWidth> + + Описание ошибки — {errorItem?.action} + + + { + setErrorComment(e.target.value) + if (errorCommentError) setErrorCommentError(false) + }} + placeholder="Опишите, что пошло не так..." + error={errorCommentError} + helperText={errorCommentError ? 'Обязательное поле' : ''} + sx={{ mt: 1 }} + /> + + + + + + ) }