/
voting
/
frontend
Обзор
Документация
Войти
/
voting
/
frontend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
main
src/modules/event-data-loader/EventDataLoader.tsx
226 строк
8 KB
pulintim
Запрос на слияние 'features/EventDataLoader' (
#25
) из features/EventDataLoader в main
19 июл 2026, 15:37
Верифицирован
19 июл 2026, 15:37
9809747
Код
Авторство
О чём код?
import { Box, Button, Stack, Typography } from '@mui/material' import { useState } from 'react' import { useParams } from 'react-router-dom' import { useBulkSyncNominations, useBulkSyncNominees } from '../../api/admin.mutation' import type { AdminNominationResponseDto } from '../../api/types' import { XlsxUpload } from '../../components/common' import { type NominationXlsxRow, nominationsSchema } from './schemes/nominations' import { type NomineeXlsxRow, nomineeSchema } from './schemes/nominee' type UploadFeedback<T> = { rows: T[] | null errors: string[] } type WizardStep = 'nominations' | 'nominees' | 'done' const emptyFeedback = <T,>(): UploadFeedback<T> => ({ rows: null, errors: [] }) export function EventDataLoader() { const { eventId } = useParams<{ eventId: string }>() const { mutateAsync: bulkSyncNominations, isPending: isNominationsPending } = useBulkSyncNominations() const { mutateAsync: bulkSyncNominees, isPending: isNomineesPending } = useBulkSyncNominees() const [step, setStep] = useState<WizardStep>('nominations') const [syncedNominations, setSyncedNominations] = useState<AdminNominationResponseDto[]>([]) const [currentIndex, setCurrentIndex] = useState(0) const [nominations, setNominations] = useState<UploadFeedback<NominationXlsxRow>>(emptyFeedback) const [nominees, setNominees] = useState<UploadFeedback<NomineeXlsxRow>>(emptyFeedback) const [nomineeStepSucceeded, setNomineeStepSucceeded] = useState(false) const currentNomination = syncedNominations[currentIndex] const totalNominations = syncedNominations.length const nominationsSynced = syncedNominations.length > 0 const handleNominationsSuccess = async (data: NominationXlsxRow[]) => { setNominations({ rows: data, errors: [] }) if (!eventId) { setNominations({ rows: data, errors: ['Не удалось определить событие для отправки номинаций'] }) return } try { const response = await bulkSyncNominations({ eventId, items: data.map((row, index) => ({ name: row.name, position: index + 1, status: 'DRAFT' })) }) setSyncedNominations(response) } catch (error) { const message = error instanceof Error && error.message ? error.message : 'Не удалось отправить номинации' setNominations({ rows: data, errors: [message] }) } } const handleNomineesSuccess = async (data: NomineeXlsxRow[]) => { if (!currentNomination) return setNominees({ rows: data, errors: [] }) try { await bulkSyncNominees({ nominationId: currentNomination.id, items: data.map((row) => ({ name: row.fullname })) }) setNomineeStepSucceeded(true) } catch (error) { const message = error instanceof Error && error.message ? error.message : 'Не удалось отправить номинантов' setNominees({ rows: data, errors: [message] }) } } const goToNominees = () => { setNominees(emptyFeedback()) setNomineeStepSucceeded(false) setCurrentIndex(0) setStep('nominees') } const goBack = () => { setNominees(emptyFeedback()) setNomineeStepSucceeded(false) if (step === 'done') { setCurrentIndex(Math.max(syncedNominations.length - 1, 0)) setStep('nominees') return } if (step === 'nominees' && currentIndex > 0) { setCurrentIndex((i) => i - 1) return } if (step === 'nominees' && currentIndex === 0) { setStep('nominations') } } const goNext = () => { if (!nomineeStepSucceeded) return setNominees(emptyFeedback()) setNomineeStepSucceeded(false) if (currentIndex < syncedNominations.length - 1) { setCurrentIndex((i) => i + 1) } else { setStep('done') } } return ( <Box sx={{ maxWidth: 640, mx: 'auto' }}> <Typography variant="h5" component="h1" sx={{ mb: 3 }}> Загрузка данных </Typography> <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> {!nominationsSynced && 'Шаг: номинации'} {step === 'nominees' && `Шаг: номинанты ${currentIndex + 1} из ${totalNominations}`} {step === 'nominations' && nominationsSynced && `Синхронизировано номинаций: ${totalNominations}`} {step === 'done' && `Готово · ${totalNominations} номинаций`} </Typography> <Stack spacing={4}> {step === 'nominations' && ( <Box> <Typography variant="h6" component="h2" sx={{ mb: 1 }}> Номинации </Typography> {nominationsSynced ? ( <> {syncedNominations.map((nomination) => ( <Typography key={nomination.id} color="text.secondary"> {nomination.name} </Typography> ))} <Button variant="contained" onClick={goToNominees} sx={{ mt: 2 }}> К номинантам </Button> </> ) : ( <XlsxUpload schema={nominationsSchema} label="Загрузить номинации" disabled={isNominationsPending} onSuccess={handleNominationsSuccess} onError={(errors) => setNominations({ rows: null, errors })} /> )} {!nominationsSynced && nominations.rows && ( <Typography color="text.secondary" sx={{ mt: 1 }}> Загружено номинаций: {nominations.rows.length} </Typography> )} {nominations.errors.map((message) => ( <Typography key={message} color="error" role="alert" sx={{ mt: 1 }}> {message} </Typography> ))} </Box> )} {step === 'nominees' && currentNomination && ( <Box> <Typography variant="h6" component="h2" sx={{ mb: 1 }}> Номинанты [{currentNomination.name}] </Typography> <XlsxUpload schema={nomineeSchema} label="Загрузить номинантов" disabled={isNomineesPending} onSuccess={handleNomineesSuccess} onError={(errors) => { setNominees({ rows: null, errors }) setNomineeStepSucceeded(false) }} /> {nominees.rows && ( <Typography color="text.secondary" sx={{ mt: 1 }}> Загружено номинантов: {nominees.rows.length} </Typography> )} {nominees.errors.map((message) => ( <Typography key={message} color="error" role="alert" sx={{ mt: 1 }}> {message} </Typography> ))} <Stack direction="row" spacing={2} sx={{ mt: 2 }}> <Button variant="outlined" onClick={goBack}> Назад </Button> <Button variant="contained" onClick={goNext} disabled={!nomineeStepSucceeded}> Далее </Button> </Stack> </Box> )} {step === 'done' && ( <Box> <Typography variant="h6" component="h2" sx={{ mb: 1 }}> Готово </Typography> <Typography color="text.secondary"> Данные загружены для всех номинаций ({totalNominations}) </Typography> <Button variant="outlined" onClick={goBack} sx={{ mt: 2 }}> Назад </Button> </Box> )} </Stack> </Box> ) }