diff --git a/src/App.jsx b/src/App.jsx index 7d5a783..445cff5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,6 +21,24 @@ import { EditStockPage } from './pages/EditStockPage'; import { AddStockPage } from './pages/AddStockPage'; import { InfoclinicListPage } from './pages/InfoclinicListPage'; import { LostDoctorsPage } from './pages/LostDoctorsPage'; +import { NewsListPage } from './pages/NewsListPage'; +import { EditNewsPage } from './pages/EditNewsPage'; +import { AddNewsPage } from './pages/AddNewsPage'; +import { SitePromoListPage } from './pages/SitePromoListPage'; +import { EditSitePromoPage } from './pages/EditSitePromoPage'; +import { AddSitePromoPage } from './pages/AddSitePromoPage'; +import { DiseaseListPage } from './pages/DiseaseListPage'; +import { EditDiseasePage } from './pages/EditDiseasePage'; +import { AddDiseasePage } from './pages/AddDiseasePage'; +import { MedicalCenterListPage } from './pages/MedicalCenterListPage'; +import { EditMedicalCenterPage } from './pages/EditMedicalCenterPage'; +import { AddMedicalCenterPage } from './pages/AddMedicalCenterPage'; +import { ArticleListPage } from './pages/ArticleListPage'; +import { EditArticlePage } from './pages/EditArticlePage'; +import { AddArticlePage } from './pages/AddArticlePage'; +import { SiteServicesListPage } from './pages/SiteServicesListPage'; +import { EditSiteServicesPage } from './pages/EditSiteServicesPage'; +import { AddSiteServicesPage } from './pages/AddSiteServicesPage'; function App() { return ( @@ -50,7 +68,25 @@ function App() { } /> } /> } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/src/api/apiArticle.js b/src/api/apiArticle.js new file mode 100644 index 0000000..2a3b9fc --- /dev/null +++ b/src/api/apiArticle.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const articleApi = API.injectEndpoints({ + endpoints: (build) => ({ + getArticleList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&limit=20`; + else queryString += `limit=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/article/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getArticle: build.query({ + query: ({ articleId }) => ({ + url: `/article/${articleId}`, + }), + }), + createArticle: build.mutation({ + query: ({ data }) => ({ + url: `/article/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateArticle: build.mutation({ + query: ({ articleId, data }) => ({ + url: `/article/${articleId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteArticle: build.mutation({ + query: ({ articleId }) => ({ + url: `/article/${articleId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetArticleQuery, + useGetArticleListQuery, + useCreateArticleMutation, + useUpdateArticleMutation, + useDeleteArticleMutation, +} = articleApi; diff --git a/src/api/apiDisease.js b/src/api/apiDisease.js new file mode 100644 index 0000000..7f24f5d --- /dev/null +++ b/src/api/apiDisease.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const diseaseApi = API.injectEndpoints({ + endpoints: (build) => ({ + getDiseaseList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&perPage=20`; + else queryString += `perPage=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/disease/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getDisease: build.query({ + query: ({ diseaseId }) => ({ + url: `/disease/${diseaseId}`, + }), + }), + createDisease: build.mutation({ + query: ({ data }) => ({ + url: `/disease/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateDisease: build.mutation({ + query: ({ diseaseId, data }) => ({ + url: `/disease/${diseaseId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteDisease: build.mutation({ + query: ({ diseaseId }) => ({ + url: `/disease/${diseaseId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetDiseaseQuery, + useGetDiseaseListQuery, + useCreateDiseaseMutation, + useUpdateDiseaseMutation, + useDeleteDiseaseMutation, +} = diseaseApi; diff --git a/src/api/apiMedicalCenter.js b/src/api/apiMedicalCenter.js new file mode 100644 index 0000000..115078f --- /dev/null +++ b/src/api/apiMedicalCenter.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const medicalCenterApi = API.injectEndpoints({ + endpoints: (build) => ({ + getMedicalCenterList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&perPage=20`; + else queryString += `perPage=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/medical-center/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getMedicalCenter: build.query({ + query: ({ medicalCenterId }) => ({ + url: `/medical-center/${medicalCenterId}`, + }), + }), + createMedicalCenter: build.mutation({ + query: ({ data }) => ({ + url: `/medical-center/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateMedicalCenter: build.mutation({ + query: ({ medicalCenterId, data }) => ({ + url: `/medical-center/${medicalCenterId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteMedicalCenter: build.mutation({ + query: ({ medicalCenterId }) => ({ + url: `/medical-center/${medicalCenterId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetMedicalCenterQuery, + useGetMedicalCenterListQuery, + useCreateMedicalCenterMutation, + useUpdateMedicalCenterMutation, + useDeleteMedicalCenterMutation, +} = medicalCenterApi; diff --git a/src/api/apiNews.js b/src/api/apiNews.js new file mode 100644 index 0000000..6f29b87 --- /dev/null +++ b/src/api/apiNews.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const newsApi = API.injectEndpoints({ + endpoints: (build) => ({ + getNewsList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&perPage=20`; + else queryString += `perPage=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/news/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getNews: build.query({ + query: ({ newsId }) => ({ + url: `/news/${newsId}`, + }), + }), + createNews: build.mutation({ + query: ({ data }) => ({ + url: `/news/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateNews: build.mutation({ + query: ({ newsId, data }) => ({ + url: `/news/${newsId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteNews: build.mutation({ + query: ({ newsId }) => ({ + url: `/news/${newsId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetNewsQuery, + useGetNewsListQuery, + useCreateNewsMutation, + useUpdateNewsMutation, + useDeleteNewsMutation, +} = newsApi; diff --git a/src/api/apiSitePromo.js b/src/api/apiSitePromo.js new file mode 100644 index 0000000..429714f --- /dev/null +++ b/src/api/apiSitePromo.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const promoApi = API.injectEndpoints({ + endpoints: (build) => ({ + getSitePromoList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&perPage=20`; + else queryString += `perPage=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/promo/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getSitePromo: build.query({ + query: ({ promoId }) => ({ + url: `/promo/${promoId}`, + }), + }), + createSitePromo: build.mutation({ + query: ({ data }) => ({ + url: `/promo/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateSitePromo: build.mutation({ + query: ({ promoId, data }) => ({ + url: `/promo/${promoId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteSitePromo: build.mutation({ + query: ({ promoId }) => ({ + url: `/promo/${promoId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetSitePromoQuery, + useGetSitePromoListQuery, + useCreateSitePromoMutation, + useUpdateSitePromoMutation, + useDeleteSitePromoMutation, +} = promoApi; diff --git a/src/api/apiSiteServices.js b/src/api/apiSiteServices.js new file mode 100644 index 0000000..e11d9bc --- /dev/null +++ b/src/api/apiSiteServices.js @@ -0,0 +1,55 @@ +import { API, authHeader } from './apiSlice'; + +export const siteServicesApi = API.injectEndpoints({ + endpoints: (build) => ({ + getSiteServicesList: build.query({ + query: ({ search = '', page = '' }) => { + let queryString = '?'; + if (page) queryString += `page=${page}&perPage=20`; + else queryString += `perPage=20`; + if (search) queryString += `&search=${encodeURIComponent(search)}`; + return { + url: `/site-services/list${queryString}`, + }; + }, + refetchOnMountOrArgChange: true, + keepUnusedDataFor: 0, + }), + getSiteServices: build.query({ + query: ({ siteServicesId }) => ({ + url: `/site-services/${siteServicesId}`, + }), + }), + createSiteServices: build.mutation({ + query: ({ data }) => ({ + url: `/site-services/create`, + method: 'POST', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + updateSiteServices: build.mutation({ + query: ({ siteServicesId, data }) => ({ + url: `/site-services/${siteServicesId}`, + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(data), + }), + }), + deleteSiteServices: build.mutation({ + query: ({ siteServicesId }) => ({ + url: `/site-services/${siteServicesId}`, + method: 'DELETE', + headers: authHeader(), + }), + }), + }), +}); + +export const { + useGetSiteServicesQuery, + useGetSiteServicesListQuery, + useCreateSiteServicesMutation, + useUpdateSiteServicesMutation, + useDeleteSiteServicesMutation, +} = siteServicesApi; diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx index a2de39d..7dd3f23 100644 --- a/src/components/Navbar/Navbar.jsx +++ b/src/components/Navbar/Navbar.jsx @@ -15,6 +15,12 @@ export const Navbar = () => { { to: '/promotions',icon: 'fas fa-percent', label: 'Акции' }, { to: '/departments',icon: 'fas fa-stethoscope', label: 'Отделения' }, { to: '/filials',icon: 'fas fa-building', label: 'Филиалы' }, + { to: '/news', icon: 'fas fa-newspaper', label: 'Новости' }, + { to: '/site-promo', icon: 'fas fa-bullhorn', label: 'Промо (контент)' }, + { to: '/disease', icon: 'fas fa-heartbeat', label: 'Заболевания' }, + { to: '/medical-center', icon: 'fas fa-hospital', label: 'Медцентры' }, + { to: '/article', icon: 'fas fa-file-alt', label: 'Статьи' }, + { to: '/site-services', icon: 'fas fa-concierge-bell', label: 'Услуги сайта' }, ]; const [open, setOpen] = useState(false); const toggleRef = useRef(null); diff --git a/src/components/Sidebar/Sidebar.jsx b/src/components/Sidebar/Sidebar.jsx index 024299a..954808e 100644 --- a/src/components/Sidebar/Sidebar.jsx +++ b/src/components/Sidebar/Sidebar.jsx @@ -10,6 +10,12 @@ export const Sidebar = () => { { to: '/promotions',icon: 'fas fa-percent', label: 'Акции' }, { to: '/departments',icon: 'fas fa-stethoscope', label: 'Отделения' }, { to: '/filials',icon: 'fas fa-building', label: 'Филиалы' }, + { to: '/news', icon: 'fas fa-newspaper', label: 'Новости' }, + { to: '/site-promo', icon: 'fas fa-bullhorn', label: 'Промо (контент)' }, + { to: '/disease', icon: 'fas fa-heartbeat', label: 'Заболевания' }, + { to: '/medical-center', icon: 'fas fa-hospital', label: 'Медцентры' }, + { to: '/article', icon: 'fas fa-file-alt', label: 'Статьи' }, + { to: '/site-services', icon: 'fas fa-concierge-bell', label: 'Услуги сайта' }, ]; return ( diff --git a/src/pages/AddArticlePage.jsx b/src/pages/AddArticlePage.jsx new file mode 100644 index 0000000..7f4ef38 --- /dev/null +++ b/src/pages/AddArticlePage.jsx @@ -0,0 +1,88 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { useCreateArticleMutation } from '/src/api/apiArticle'; +import { selectRegions } from '../store/slice/regionSlice'; +import { TextEditor } from '../components/Editors/TextEditor'; +import { LoadingComponent } from '../components/Placeholders/LoadingComponent'; +import { ErrorComponent } from '../components/Placeholders/ErrorComponent'; +import { NotFindElement } from '../components/Placeholders/NotFindElement'; +import { EditElementForm } from '../components/Forms/EditElementForm'; +import Modal from '../components/Modals/Modal'; + +export function AddArticlePage() { + + const navigate = useNavigate(); + const navigateBack = () => navigate(`/article`); + const regions = useSelector(selectRegions); + const [createArticle] = useCreateArticleMutation(); + const [isModalSuccess, setModalSuccess] = useState(false); + const [errors, setErrors] = useState({ name: '', alias: '', regionId: '' }); + const [form, setForm] = useState({ + name: '', + active: false, + regionId: '', + alias: '', + previewPicture: '', + doctors: '', + services: '', + }); + const [anons, setAnons] = useState(''); + const [content, setContent] = useState(''); + + + + const handleChange = (key) => (e) => { + const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; + setForm((f) => ({ ...f, [key]: value })); + if (key === 'name' || key === 'alias' || key === 'regionId') setErrors((err) => ({ ...err, [key]: '' })); + }; + + + + const handleSave = async () => { + const newErrors = { name: '', alias: '', regionId: '' }; + let hasError = false; + if (!String(form.name ?? '').trim()) { newErrors.name = 'Название не может быть пустым'; hasError = true; } + if (!String(form.alias ?? '').trim()) { newErrors.alias = 'Alias не может быть пустым'; hasError = true; } + if (form.regionId === '' || form.regionId == null) { newErrors.regionId = 'Укажите регион'; hasError = true; } + if (hasError) { setErrors(newErrors); window.alert('Пожалуйста исправьте ошибки в форме.'); return; } + const data = { anons, content }; + try { + data.name = form.name === '' ? null : form.name; + data.active = Boolean(form.active); + data.regionId = form.regionId === '' ? null : Number(form.regionId); + data.alias = form.alias === '' ? null : form.alias; + data.previewPicture = form.previewPicture === '' ? null : form.previewPicture; + data.doctors = !form.doctors || !String(form.doctors).trim() ? null : JSON.parse(form.doctors); + data.services = !form.services || !String(form.services).trim() ? null : JSON.parse(form.services); + } catch (e) { window.alert('Пожалуйста исправьте ошибки в форме.'); return; } + try { + const response = await createArticle({ data }).unwrap(); + setModalSuccess(true); + window.setTimeout(() => { navigate(`/article/edit/${response.id}`); window.location.reload(); }, 2000); + } catch (err) { + console.error('Ошибка сохранения:', err); + } + }; + + + + return ( + {}} isAddSpecialist={true}> +
+ {errors.name && {errors.name}}
+
+
+ {errors.regionId && {errors.regionId}}
+
+ {errors.alias && {errors.alias}}
+
+
+
+