2026-07-02 15:35:44 +03:00

298 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useMemo, useState } from 'react';
import { ProjectsApi } from '../../api/projects';
import { CardsSkeleton } from '../../components/common/Skeleton';
import SearchComponent from '../../components/common/SearchComponent';
import { ProjectCardComponent } from './ProjectCard';
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
import { PageContainer, TasksContainer } from '../StyledForPage';
import {
Box,
FormControl,
Button,
Stack,
Pagination,
PaginationItem,
Select,
MenuItem,
Typography,
IconButton,
Tooltip,
TextField,
Autocomplete,
} from '@mui/material';
import { toast } from 'react-toastify';
import { ModalCreateProject } from './ModalCreateProject';
import {
ROLES_NAME_ID,
} from '../../constants/constants';
import { useAuth } from '../../app/context/AuthProvider';
import { PrimaryButton, PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
import {
ExportExitButton,
ExportWithTextButton,
} from '../../components/common/Buttons/ButtonsActions';
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
import { WhitePlus } from '../../components/common/icons/icons';
import { useNavigate } from 'react-router-dom';
import { PaginatedList } from '../../components/common/PaginatedList';
import { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
import { SspApi } from '../../api/ssp';
import NavigationToggle from '../../components/common/NavigationToggle';
export { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
export default function ProjectsPage() {
const navigate = useNavigate();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
// Новые фильтры
const [yearFilter, setYearFilter] = useState('');
const [branchFilter, setBranchFilter] = useState('');
// Pagination state
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(20);
const [totalCount, setTotalCount] = useState(0);
const { user } = useAuth();
const [exportMode, setExportMode] = useState(false);
const [exportList, setExportList] = useState([]);
const [projectData, setProjectData] = useState(DEFAULT_PROJECT_CONFIG);
// Получаем список годов для фильтра (можно динамически или задать диапазон)
const yearOptions = useMemo(() => {
const currentYear = new Date().getFullYear();
const years = [];
for (let year = currentYear - 5; year <= currentYear + 2; year++) {
years.push(year);
}
return years;
}, []);
useEffect(() => {
if (!user) return;
if (user.role_id == ROLES_NAME_ID.admin) {
SspApi.getAll().then(data => {
if (data.success) {
setProjectData(prev => ({
...prev, 'ССП/РФ': {
...prev['ССП/РФ'],
options: data.result
}
}))
return;
}
toast.error('Ошибка при получении ССП/РФ');
});
return;
}
setProjectData(prev => ({
...prev, 'ССП/РФ': {
...prev['ССП/РФ'],
options: user.org_units,
}
}))
}, [user])
const openModal = () => {
setModalOpen(true);
};
const mapFormDataToApi = (formData) => {
const apiData = {
name: formData['Название проекта'] || '',
project_type: formData['Тип проекта развития'] || null,
vsp_format: formData['Формат ВСП'] || null,
object_address: formData['Адрес объекта'] || '',
staff_count: formData['Целевая численность, чел.'] || 0,
placement_type: formData['Тип размещения'] || null,
total_area: formData['Площадь размещения, м2'] || 0,
year: formData['Год'] || null,
branch_id: formData['ССП/РФ'] || null,
};
return apiData;
};
const handleCreateProject = async (formData) => {
try {
const apiData = mapFormDataToApi(formData);
await ProjectsApi.create(apiData);
setPage(1);
loadProjects(1, limit);
setModalOpen(false);
toast.success('Проект успешно создан');
} catch (error) {
toast.error(
error.response?.data?.detail ||
error.response?.data?.message ||
'Ошибка при создании проекта',
);
}
};
const loadProjects = async (currentPage = page, currentLimit = limit) => {
setLoading(true);
try {
const skip = (currentPage - 1) * currentLimit;
const params = {
offset: skip,
limit: currentLimit,
// Добавляем фильтры в параметры запроса
...(yearFilter && { year: parseInt(yearFilter) }),
...(branchFilter && { branch_id: parseInt(branchFilter.id) }),
};
const res = await ProjectsApi.list(params);
const data = res.result;
setItems(data);
setTotalCount(res.count);
} catch (error) {
toast.error('Ошибка загрузки проектов');
} finally {
setLoading(false);
}
};
// Сбрасываем страницу при изменении любых фильтров
useEffect(() => {
setPage(1);
loadProjects(1, limit);
}, [yearFilter, branchFilter]);
const handlePageChange = (event, value) => {
setPage(value);
loadProjects(value, limit);
};
const handleLimitChange = (event) => {
const newLimit = event.target.value;
setLimit(newLimit);
setPage(1);
loadProjects(1, newLimit);
};
// Сброс всех фильтров
const handleClearFilters = () => {
setYearFilter('');
setBranchFilter('');
setPage(1);
};
useEffect(() => {
loadProjects(page, limit);
}, []);
useEffect(() => {
setExportList([]);
}, [exportMode]);
const handleChangeToExportList = (projectId, checked) => {
if (checked) {
setExportList((prev) => [...prev, projectId]);
} else {
const newExportList = exportList.filter((t) => t != projectId);
setExportList(newExportList);
}
};
const handleBulkExport = async () => {
await exportBulkForms(exportList);
setExportMode(false);
};
const handleSingleExport = (projectId, projectTitle) => {
exportSingleForm(projectId, projectTitle);
};
const handleRefresh = () => {
loadProjects(page, limit);
toast.info('Список проектов обновлен');
};
if (!user) return null;
return (
<>
<PageContainer>
<HeaderSwitch />
<NavigationToggle currentView={'projects'} onViewChange={(path) => navigate(path)} />
<PaginatedList
items={items}
loading={loading}
totalCount={totalCount}
page={page}
limit={limit}
onPageChange={handlePageChange}
onLimitChange={handleLimitChange}
entityName="проектов"
emptyMessage="Проекты не найдены"
renderItem={(project, exportProps) => (
<ProjectCardComponent
key={project.id}
project={project}
projectDataInit={projectData}
{...exportProps}
/>
)}
getItemId={(project) => project.id}
getItemTitle={(project) => project.name || 'Проект'}
extraActions={
user.role_id === ROLES_NAME_ID.admin && (
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />} height='2.5rem' >
Создать проект
</PrimaryButton>
)
}
filterActions={
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center', height: '2.5rem' }}>
<Select
size="small"
displayEmpty
value={yearFilter}
onChange={(e) => setYearFilter(e.target.value)}
sx={{ width: 130 }}
>
<MenuItem value="">Все годы</MenuItem>
{yearOptions.map((year) => (
<MenuItem key={year} value={year}>{year}</MenuItem>
))}
</Select>
<Autocomplete
size="small"
options={projectData['ССП/РФ']?.options ?? []}
noOptionsText="Не найдено"
getOptionLabel={(option) => option.title || option.name || '-'}
value={branchFilter}
onChange={(event, newValue) => setBranchFilter(newValue)}
sx={{ width: 200 }}
renderInput={(params) => (
<TextField {...params} placeholder="Все" size="small" />
)}
/>
<PrimaryOutlinedButton onClick={handleClearFilters} height="100%"> Сбросить</PrimaryOutlinedButton>
</Box>
}
/>
</PageContainer>
<ModalCreateProject
open={modalOpen}
onClose={() => setModalOpen(false)}
onCreate={handleCreateProject}
config={projectData}
/>
</>
);
}