From 2f9048d09d05e218860957e5a79078a0d083d81e Mon Sep 17 00:00:00 2001 From: Michael-Kolpakov Date: Mon, 17 Mar 2025 01:04:02 +0200 Subject: [PATCH 1/4] Unnecessary requests are eliminated and all calls to the server are brought to the general view --- src/app/api/streetcode/streetcodes.api.ts | 19 +++- .../api/streetcode/text-content/terms.api.ts | 14 ++- .../HeaderLoginModal.component.tsx | 20 ++-- src/app/layout/footer/Footer.component.tsx | 19 ++-- src/app/stores/news-store/news-store.ts | 4 +- src/app/stores/streetcodeshort-store.ts | 49 ++++++++-- src/app/stores/term-store.ts | 35 ++++++- .../CategoriesPage.component.tsx | 31 +------ .../CategoryAdminModal.component.tsx | 9 +- .../ContextPage/ContextMainPage.component.tsx | 15 +-- .../ContextAdminModal.component.tsx | 54 ++++++----- .../JobsModal/JobsModal.component.tsx | 16 +++- .../JobsTable/JobsTable.component.tsx | 38 +++----- .../TextForm/Editors/TextEditor.component.tsx | 4 +- .../AdminPage/NewsPage/News.component.tsx | 38 +++++--- .../NewsModal/NewsModal.component.tsx | 29 ++++-- .../PartnerModal/PartnerModal.component.tsx | 36 ++++---- .../PartnersPage/Partners.component.tsx | 61 ++++++------ .../StreetcodesTable.component.tsx | 18 ++-- .../TagsPage/TagsMainPage.component.tsx | 19 ++-- .../TagsPage/TagsPage/TagAdminModal.tsx | 13 ++- .../TeamModal/TeamModal.component.tsx | 16 +++- .../AdminPage/TeamPage/TeamPage.component.tsx | 46 ++++------ .../TeamPositionsMainPage.component.tsx | 16 +--- .../TeamPositionsAdminModal.component.tsx | 45 +++++---- .../TermDictionary.component.tsx | 92 +++++++++++++------ .../TermDictionary/TermDictionary.styles.scss | 4 + 27 files changed, 442 insertions(+), 318 deletions(-) diff --git a/src/app/api/streetcode/streetcodes.api.ts b/src/app/api/streetcode/streetcodes.api.ts index ae4268611..e0b023535 100644 --- a/src/app/api/streetcode/streetcodes.api.ts +++ b/src/app/api/streetcode/streetcodes.api.ts @@ -2,7 +2,7 @@ import Agent from '@api/agent.api'; import { API_ROUTES } from '@constants/api-routes.constants'; import Streetcode, { - EventStreetcode, PersonStreetcode, StreetcodeCatalogRecord, + StreetcodeCatalogRecord, StreetcodeCreate, StreetcodeFavourite, StreetcodeMainPage, StreetcodeShort, StreetcodeUpdate, } from '@models/streetcode/streetcode-types.model'; @@ -58,9 +58,20 @@ const StreetcodesApi = { Agent.get(`${API_ROUTES.STREETCODES.GET_COUNT}?onlyPublished=${onlyPublished}`) ), - getAllShort: () => ( - Agent.get(`${API_ROUTES.STREETCODES.GET_ALL_SHORT}`) - ), + getAllShort: (page?: number, pageSize?: number) => { + const params = Object.entries({ + page: page?.toString() ?? '', + pageSize: pageSize?.toString() ?? '', + }); + + const queryParams = params.filter((p) => !!p[1]); + + const searchParams = new URLSearchParams(queryParams); + return Agent.get<{ totalAmount: number, streetcodesShort: StreetcodeShort[] }>( + `${API_ROUTES.STREETCODES.GET_ALL_SHORT}`, + searchParams, + ); + }, create: (streetcode: StreetcodeCreate) => Agent .post(`${API_ROUTES.STREETCODES.CREATE}`, streetcode), diff --git a/src/app/api/streetcode/text-content/terms.api.ts b/src/app/api/streetcode/text-content/terms.api.ts index 9fe9d30e5..9faa80976 100644 --- a/src/app/api/streetcode/text-content/terms.api.ts +++ b/src/app/api/streetcode/text-content/terms.api.ts @@ -7,9 +7,17 @@ const TermsApi = { Agent.get(`${API_ROUTES.TERMS.GET}/${id}`) ), - getAll: () => ( - Agent.get(`${API_ROUTES.TERMS.GET_ALL}`) - ), + getAll: (page?: number, pageSize?: number) => { + const params = Object.entries({ + page: page?.toString() ?? '', + pageSize: pageSize?.toString() ?? '', + }); + + const queryParams = params.filter((p) => !!p[1]); + + const searchParams = new URLSearchParams(queryParams); + return Agent.get<{ totalAmount: number, terms: Term[] }>(`${API_ROUTES.TERMS.GET_ALL}`, searchParams); + }, create: (term: Term) => ( Agent.post(`${API_ROUTES.TERMS.CREATE}`, term) diff --git a/src/app/common/components/modals/HeaderLogin/HeaderLoginModal.component.tsx b/src/app/common/components/modals/HeaderLogin/HeaderLoginModal.component.tsx index 4d01def03..1aed093f1 100644 --- a/src/app/common/components/modals/HeaderLogin/HeaderLoginModal.component.tsx +++ b/src/app/common/components/modals/HeaderLogin/HeaderLoginModal.component.tsx @@ -22,19 +22,19 @@ const HeaderLoginModal = () => { const [hasVacancies, setHasVacancies] = useState(false); const SURVEY_LINK = 'https://forms.gle/eWwX5RP84X7dymLR6'; const { modalStore: { setModal, modalsState: { login } } } = useModalContext(); + const handleClick = () => login.isOpen = false; + useEffect(() => { - JobApi.getActive() - .then( - (result) => { + if (login.isOpen) { + JobApi.getActive() + .then((result) => { setHasVacancies(result.length > 0); - }, - ) - .catch( - (e) => { - console.error(e); - }, - ); + }) + .catch((error) => { + console.error(error); + }); + } }, [login.isOpen]); const becomePartnerHandler = () => { diff --git a/src/app/layout/footer/Footer.component.tsx b/src/app/layout/footer/Footer.component.tsx index 23ff8f111..73238d026 100644 --- a/src/app/layout/footer/Footer.component.tsx +++ b/src/app/layout/footer/Footer.component.tsx @@ -11,6 +11,7 @@ import Youtube from '@images/footer/You.svg'; import { useEffect, useState } from 'react'; import { HashLink as Link } from 'react-router-hash-link'; import useWindowSize from '@hooks/stateful/useWindowSize.hook'; +import { useQuery } from '@tanstack/react-query'; import JobApi from '@/app/api/job/Job.api'; import { ContactUsModal } from '@/app/common/components/modals/ContactUsModal/ContactUsModal.component'; @@ -23,15 +24,17 @@ const Footer = () => { const [hasVacancies, setHasVacancies] = useState(false); const REPORTS = 'https://drive.google.com/drive/folders/11Ef4y_6ZHyqT5eDxD5Cn-aWhr-kThh3A?usp=drive_link'; + const { data } = useQuery({ + queryKey: ['jobsActive'], + queryFn: () => JobApi.getActive(), + }); + useEffect(() => { - JobApi.getActive() - .then( - (result) => { - setHasVacancies(result.length > 0); - }, - ) - .catch(); - }, []); + if (data) { + setHasVacancies(data.length > 0); + } + }, [data]); + return ( <> {windowSize.width > 1024 && ( diff --git a/src/app/stores/news-store/news-store.ts b/src/app/stores/news-store/news-store.ts index 2d019f742..783922df6 100644 --- a/src/app/stores/news-store/news-store.ts +++ b/src/app/stores/news-store/news-store.ts @@ -101,15 +101,15 @@ export default class NewsStore { }); }; + // eslint-disable-next-line class-methods-use-this public createNews = async (news: News) => { await NewsApi.create(news) - .then(() => this.getAll(this.PaginationInfo.PageSize)) .catch((error) => console.error(error)); }; + // eslint-disable-next-line class-methods-use-this public updateNews = async (news: News) => { await NewsApi.update(news) - .then(() => this.getAll(this.PaginationInfo.PageSize)) .catch((error) => console.error(error)); }; diff --git a/src/app/stores/streetcodeshort-store.ts b/src/app/stores/streetcodeshort-store.ts index a557f549e..312d61c17 100644 --- a/src/app/stores/streetcodeshort-store.ts +++ b/src/app/stores/streetcodeshort-store.ts @@ -1,24 +1,53 @@ /* eslint-disable no-restricted-imports */ -import { action, makeAutoObservable, observable } from 'mobx'; +import { makeAutoObservable } from 'mobx'; +import { PaginationInfo } from '@models/pagination/pagination.model'; +import { StreetcodeShort } from '@models/streetcode/streetcode-types.model'; -import { StreetcodeShort } from '../../models/streetcode/streetcode-types.model'; import StreetcodesApi from '../api/streetcode/streetcodes.api'; export default class StreetcodeShortStore { - public streetcodes = new Array(); + public StreetcodesShortMap = new Map(); + + private defaultPageSize = 10; + + private paginationInfo: PaginationInfo = { + PageSize: this.defaultPageSize, + TotalPages: 1, + TotalItems: 1, + CurrentPage: 1, + }; public constructor() { makeAutoObservable(this); } - public fetchStreetcodesAll = async () => { - StreetcodesApi.getAllShort() - .then((value) => { - this.streetcodes = value.map((s) => ({ id: s.id, title: s.title, index: s.index })); - }).catch((error) => {}); + public setInternalMap(streetcodesShort: StreetcodeShort[]) { + this.StreetcodesShortMap.clear(); + streetcodesShort.forEach(this.setItem); + } + + public setItem = (streetcodesShort: StreetcodeShort) => { + this.StreetcodesShortMap.set(streetcodesShort.id, streetcodesShort); }; - public addItemToArray = (item: StreetcodeShort) => { - this.streetcodes.push(item); + public setCurrentPage(currPage: number) { + this.paginationInfo.CurrentPage = currPage; + } + + public set PaginationInfo(paginationInfo: PaginationInfo) { + this.paginationInfo = paginationInfo; + } + + public get PaginationInfo(): PaginationInfo { + return this.paginationInfo; + } + + public fetchStreetcodesAll = async (pageSize?: number) => { + StreetcodesApi.getAllShort(this.PaginationInfo.CurrentPage, pageSize ?? this.paginationInfo.PageSize) + .then((response) => { + this.paginationInfo.TotalItems = response.totalAmount; + this.setInternalMap(response.streetcodesShort); + }) + .catch((error) => console.error(error)); }; } diff --git a/src/app/stores/term-store.ts b/src/app/stores/term-store.ts index 1e6c25a62..61688a434 100644 --- a/src/app/stores/term-store.ts +++ b/src/app/stores/term-store.ts @@ -1,15 +1,26 @@ import { makeAutoObservable, runInAction } from 'mobx'; import termsApi from '@api/streetcode/text-content/terms.api'; +import { PaginationInfo } from '@models/pagination/pagination.model'; import { Term } from '@models/streetcode/text-contents.model'; export default class TermStore { public TermMap = new Map(); + private defaultPageSize = 10; + + private paginationInfo: PaginationInfo = { + PageSize: this.defaultPageSize, + TotalPages: 1, + TotalItems: 1, + CurrentPage: 1, + }; + public constructor() { makeAutoObservable(this); } public setInternalMap = (terms: Term[]) => { + this.TermMap.clear(); terms.forEach(this.setItem); }; @@ -21,11 +32,25 @@ export default class TermStore { return Array.from(this.TermMap.values()); } - public fetchTerms = async () => { - try { - const terms = await termsApi.getAll(); - this.setInternalMap(terms); - } catch (error: unknown) {} + public setCurrentPage(currPage: number) { + this.paginationInfo.CurrentPage = currPage; + } + + public set PaginationInfo(paginationInfo: PaginationInfo) { + this.paginationInfo = paginationInfo; + } + + public get PaginationInfo(): PaginationInfo { + return this.paginationInfo; + } + + public getAll = async (pageSize?: number) => { + await termsApi.getAll(this.PaginationInfo.CurrentPage, pageSize ?? this.paginationInfo.PageSize) + .then((response) => { + this.PaginationInfo.TotalItems = response.totalAmount; + this.setInternalMap(response.terms); + }) + .catch((error) => console.error(error)); }; public createTerm = async (term: Term) => { diff --git a/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx b/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx index 9f01b4263..74ad50764 100644 --- a/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx +++ b/src/features/AdminPage/CategoriesPage/CategoriesPage.component.tsx @@ -1,11 +1,10 @@ import './CategoriesPage.style.scss'; import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; -import ImageStore from '@stores/image-store'; import useMobx, { useModalContext } from '@stores/root-store'; import { useQuery } from '@tanstack/react-query'; @@ -25,34 +24,11 @@ const CategoriesMainPage: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [categoryToEdit, setSourcesToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['categories', sourcesStore.PaginationInfo.CurrentPage], queryFn: () => sourcesStore.fetchSrcCategoriesAll(), }); - const updatedCategories = () => { - Promise.all([ - sourcesStore.fetchSrcCategoriesAll(), - ]).then(() => { - sourcesStore?.srcCategoriesMap.forEach((val, key) => { - if (!!val.imageId && !val.image) { - ImageStore.getImageById(val.imageId!).then((image) => { - sourcesStore.srcCategoriesMap.set( - key, - { ...val, image }, - ); - }); - } - }); - }).then(() => { - sourcesStore.setInternalCategoriesMap(sourcesStore.getSrcCategoriesArray); - }); - }; - - useEffect(() => { - updatedCategories(); - }, [modalAddOpened, modalEditOpened]); - const handleDeleteCategory = (categoryId: number | undefined) => { modalStore.setConfirmationModal( 'confirmation', @@ -127,6 +103,7 @@ const CategoriesMainPage: React.FC = observer(() => { ), }, ]; + return (
@@ -174,11 +151,13 @@ const CategoriesMainPage: React.FC = observer(() => {
diff --git a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx index 4175810aa..759724081 100644 --- a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx +++ b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx @@ -10,7 +10,6 @@ import ImagesApi from '@api/media/images.api'; import FileUploader from '@components/FileUploader/FileUploader.component'; import combinedImageValidator, { checkFile } from '@components/modals/validators/combinedImageValidator'; import BUTTON_LABELS from '@constants/buttonLabels'; -import { useAsync } from '@hooks/stateful/useAsync.hook'; import Audio from '@models/media/audio.model'; import Image from '@models/media/image.model'; import { SourceCategoryAdmin } from '@models/sources/sources.model'; @@ -34,6 +33,7 @@ interface SourceModalProps { setIsModalOpen: Dispatch>; initialData?: SourceCategoryAdmin; isNewCategory?: (data: boolean) => void; + afterSubmit?: () => void; } const SourceModal: React.FC = ({ @@ -41,6 +41,7 @@ const SourceModal: React.FC = ({ setIsModalOpen, initialData, isNewCategory, + afterSubmit, }) => { const { sourcesAdminStore } = useMobx(); const [form] = Form.useForm(); @@ -52,8 +53,6 @@ const SourceModal: React.FC = ({ const [fileList, setFileList] = useState([]); const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(true); - useAsync(() => sourcesAdminStore.fetchSourceCategories(), []); - const createFileListData = (img: Image) => { if (!initialData) { return []; @@ -125,6 +124,10 @@ const SourceModal: React.FC = ({ if (isNewCategory !== undefined) { isNewCategory(true); } + + if (afterSubmit) { + afterSubmit(); + } }; const handlePreview = async (file: UploadFile) => { diff --git a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx index fb535898f..6d76b01ea 100644 --- a/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextMainPage.component.tsx @@ -1,7 +1,7 @@ import './ContextMainPage.style.scss'; import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; @@ -21,19 +21,11 @@ const ContextMainPage: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [contextToEdit, setContextToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['contexts', contextStore.PaginationInfo.CurrentPage], queryFn: () => contextStore.fetchContexts(), }); - const updatedContexts = () => { - contextStore.fetchContexts(); - }; - - useEffect(() => { - updatedContexts(); - }, [modalAddOpened, modalEditOpened]); - const handleDeleteContext = (contextId: number) => { modalStore.setConfirmationModal( 'confirmation', @@ -135,13 +127,16 @@ const ContextMainPage: React.FC = observer(() => {
); }); + export default ContextMainPage; diff --git a/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx b/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx index 3667679e8..0d8f5c61b 100644 --- a/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx @@ -1,41 +1,46 @@ /* eslint-disable max-len */ import CancelBtn from '@images/utils/Cancel_btn.svg'; + import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; -import { useAsync } from '@hooks/stateful/useAsync.hook'; +import React, { useEffect, useState } from 'react'; +import BUTTON_LABELS from '@constants/buttonLabels'; import Context from '@models/additional-content/context.model'; import useMobx from '@stores/root-store'; -import { Button, Form, Input, message, Modal, Popover, UploadFile } from 'antd'; -import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; + +import { + Button, Form, Input, message, Modal, Popover, +} from 'antd'; + import normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; -import BUTTON_LABELS from "@constants/buttonLabels"; + +import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; interface ContextAdminProps { setIsModalOpen: React.Dispatch>; isModalVisible: boolean; initialData?: Context; isNewContext?: (data: boolean) => void; + afterSubmit?: () => void; } const ContextAdminModalComponent: React.FC = observer(({ isModalVisible, setIsModalOpen, initialData, - isNewContext + isNewContext, + afterSubmit, }) => { - const {contextStore} = useMobx(); + const { contextStore } = useMobx(); const [form] = Form.useForm(); const isEditing = !!initialData; - const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(true); + const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(true); const closeModal = () => { setIsModalOpen(false); - setIsSaveButtonDisabled(true); + setIsSaveButtonDisabled(true); }; - useAsync(() => contextStore.fetchContexts(), []); - useEffect(() => { if (initialData && isModalVisible) { form.setFieldsValue({ @@ -45,9 +50,9 @@ const ContextAdminModalComponent: React.FC = observer(({ }, [initialData, isModalVisible, form]); const validateContext = uniquenessValidator( - ()=>(contextStore.getContextArray.map((context) => context.title)), - ()=>(initialData?.title), - 'Контекст з такою назвою вже існує' + () => (contextStore.getContextArray.map((context) => context.title)), + () => (initialData?.title), + 'Контекст з такою назвою вже існує', ); const onSubmit = async (formData: any) => { @@ -58,7 +63,9 @@ const ContextAdminModalComponent: React.FC = observer(({ title: (formData.title as string).trim(), }; - if (currentContext.title === initialData?.title) return; + if (currentContext.title === initialData?.title) { + return; + } if (currentContext.id) { await contextStore.updateContext(currentContext as Context); @@ -69,6 +76,10 @@ const ContextAdminModalComponent: React.FC = observer(({ if (isNewContext !== undefined) { isNewContext(true); } + + if (afterSubmit) { + afterSubmit(); + } }; const handleCancel = () => { @@ -81,7 +92,7 @@ const ContextAdminModalComponent: React.FC = observer(({ await form.validateFields(); form.submit(); message.success(`Контекст успішно ${isEditing ? 'змінено' : 'додано'}!`); - setIsSaveButtonDisabled(true); + setIsSaveButtonDisabled(true); } catch (error) { message.config({ top: 100, @@ -97,7 +108,7 @@ const ContextAdminModalComponent: React.FC = observer(({ title: 50, }; - const handleInputChange = () => setIsSaveButtonDisabled(false); + const handleInputChange = () => setIsSaveButtonDisabled(false); return ( = observer(({ layout="vertical" onFinish={onSubmit} initialValues={initialData} - onKeyDown={(e) => e.key === 'Enter' ? e.preventDefault() : ''} + onKeyDown={(e) => (e.key === 'Enter' ? e.preventDefault() : '')} >

{isEditing ? 'Редагувати контекст' : 'Додати контекст'}

@@ -127,8 +138,9 @@ const ContextAdminModalComponent: React.FC = observer(({ ({ value: normaliseWhitespaces(value) })} > @@ -137,7 +149,7 @@ const ContextAdminModalComponent: React.FC = observer(({
diff --git a/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx b/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx index 111c324a2..a7bfa013a 100644 --- a/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx +++ b/src/features/AdminPage/JobsPage/JobsTable/JobsTable.component.tsx @@ -2,7 +2,7 @@ import './JobsTable.styles.scss'; import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { DeleteOutlined, DownOutlined, EditOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; @@ -25,7 +25,7 @@ const JobsTable = observer(() => { const { modalStore } = useModalContext(); const [open, setOpen] = useState(false); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['jobs', jobsStore.PaginationInfo.CurrentPage], queryFn: () => jobsStore.getAll(), }); @@ -35,16 +35,12 @@ const JobsTable = observer(() => { 'confirmation', () => { JobApi.deleteJob(id) - .then( - () => { - jobsStore.JobsMap.delete(id); - }, - ) - .catch( - (e) => { - console.error(e); - }, - ); + .then(() => { + jobsStore.JobsMap.delete(id); + }) + .catch((e) => { + console.error(e); + }); modalStore.setConfirmationModal('confirmation'); }, CONFIRMATION_MESSAGES.DELETE_VACANCY, @@ -63,7 +59,6 @@ const JobsTable = observer(() => { const handleMenuClick: MenuProps['onClick'] = async (opt) => { try { - const selectedKey = +opt.key; const currentStatus: boolean = opt.key === '0'; modalStore.setConfirmationModal( 'confirmation', @@ -138,12 +133,6 @@ const JobsTable = observer(() => { }, ]; - useEffect(() => { - if (!open) { - jobsStore.getAll(); - } - }, [open]); - const handleAddButtonClick = () => { setCurrentId(0); setOpen(true); @@ -159,11 +148,6 @@ const JobsTable = observer(() => { {BUTTON_LABELS.ADD_VACANCY}
- { /> + ); }); diff --git a/src/features/AdminPage/NewStreetcode/TextBlock/TextForm/Editors/TextEditor.component.tsx b/src/features/AdminPage/NewStreetcode/TextBlock/TextForm/Editors/TextEditor.component.tsx index 2bc972ee1..0a4ce11d8 100644 --- a/src/features/AdminPage/NewStreetcode/TextBlock/TextForm/Editors/TextEditor.component.tsx +++ b/src/features/AdminPage/NewStreetcode/TextBlock/TextForm/Editors/TextEditor.component.tsx @@ -31,7 +31,7 @@ const TextEditor = ({ const { modalStore: { setModal }, } = useModalContext(); - const { fetchTerms, getTermArray } = termsStore; + const { getAll, getTermArray } = termsStore; const { createRelatedTerm } = relatedTermStore; const [term, setTerm] = useState>(); const [selected, setSelected] = useState(""); @@ -91,7 +91,7 @@ const TextEditor = ({ : errorMessage("Термін не було додано, спробуйте ще."); }; - useAsync(fetchTerms, []); + useAsync(getAll, []); return ( diff --git a/src/features/AdminPage/NewsPage/News.component.tsx b/src/features/AdminPage/NewsPage/News.component.tsx index f158f9ed7..794636013 100644 --- a/src/features/AdminPage/NewsPage/News.component.tsx +++ b/src/features/AdminPage/NewsPage/News.component.tsx @@ -5,6 +5,8 @@ import './News.styles.scss'; import { observer } from 'mobx-react-lite'; import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import BUTTON_LABELS from '@constants/buttonLabels'; +import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; import NewsModal from '@features/AdminPage/NewsPage/NewsModal/NewsModal.component'; import PageBar from '@features/AdminPage/PageBar/PageBar.component'; import useMobx, { useModalContext } from '@stores/root-store'; @@ -18,8 +20,6 @@ import FRONTEND_ROUTES from '@/app/common/constants/frontend-routes.constants'; import base64ToUrl from '@/app/common/utils/base64ToUrl.utility'; import Image from '@/models/media/image.model'; import News from '@/models/news/news.model'; -import BUTTON_LABELS from "@constants/buttonLabels"; -import CONFIRMATION_MESSAGES from "@constants/confirmationMessages"; const Newss: React.FC = observer(() => { const { modalStore } = useModalContext(); @@ -28,7 +28,7 @@ const Newss: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [newsToEdit, setNewsToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['news', newsStore.CurrentPage], queryFn: () => newsStore.getAll(), }); @@ -37,11 +37,13 @@ const Newss: React.FC = observer(() => { modalStore.setConfirmationModal( 'confirmation', () => { - newsStore.deleteNews(news.id).then(() => { - imagesStore.deleteImage(news.imageId); - }).catch((e) => { - console.error(e); - }); + newsStore.deleteNews(news.id) + .then(() => { + imagesStore.deleteImage(news.imageId); + }) + .catch((e) => { + console.error(e); + }); modalStore.setConfirmationModal('confirmation'); }, CONFIRMATION_MESSAGES.DELETE_NEWS, @@ -90,11 +92,12 @@ const Newss: React.FC = observer(() => { render: (value: string) => (

{value ? dayjs(value).format('YYYY-MM-DD') : ''}

- {value && dayjs(value).isAfter(dayjs()) && - - - - } + {value && dayjs(value).isAfter(dayjs()) + && ( + + + + )}
), }, @@ -118,11 +121,11 @@ const Newss: React.FC = observer(() => { setModalEditOpened(true); }} /> - ), }, ]; + return (
@@ -167,11 +170,16 @@ const Newss: React.FC = observer(() => { />
- + refetch()} + /> refetch()} /> diff --git a/src/features/AdminPage/NewsPage/NewsModal/NewsModal.component.tsx b/src/features/AdminPage/NewsPage/NewsModal/NewsModal.component.tsx index e12e3f2f4..4f2bc3d84 100644 --- a/src/features/AdminPage/NewsPage/NewsModal/NewsModal.component.tsx +++ b/src/features/AdminPage/NewsPage/NewsModal/NewsModal.component.tsx @@ -12,6 +12,7 @@ import { observer } from 'mobx-react-lite'; import React, { useEffect, useRef, useState } from 'react'; import ReactQuill from 'react-quill'; import combinedImageValidator, { checkFile } from '@components/modals/validators/combinedImageValidator'; +import BUTTON_LABELS from '@constants/buttonLabels'; import PreviewFileModal from '@features/AdminPage/NewStreetcode/MainBlock/PreviewFileModal/PreviewFileModal.component'; import useMobx from '@stores/root-store'; import dayjs from 'dayjs'; @@ -38,12 +39,12 @@ import { import Editor from '@/app/common/components/Editor/QEditor.component'; import FileUploader from '@/app/common/components/FileUploader/FileUploader.component'; import base64ToUrl from '@/app/common/utils/base64ToUrl.utility'; +import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; import Audio from '@/models/media/audio.model'; import Image from '@/models/media/image.model'; import News from '@/models/news/news.model'; + import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; -import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; -import BUTTON_LABELS from "@constants/buttonLabels"; const NewsModal: React.FC<{ newsItem?: News; @@ -53,7 +54,12 @@ const NewsModal: React.FC<{ initialValue?: any; limit?: any; }> = observer(({ - newsItem, open, setIsModalOpen, afterSubmit, initialValue, limit, + newsItem, + open, + setIsModalOpen, + afterSubmit, + initialValue, + limit, }) => { const [form] = Form.useForm(); const { newsStore } = useMobx(); @@ -228,13 +234,16 @@ const NewsModal: React.FC<{ creationDate: dayjs(formValues.creationDate), }; newsStore.NewsArray.map((t) => t).forEach((t) => { - if (formValues.title == t.title || imageId.current == t.imageId) newsItem = t; + if (formValues.title == t.title || imageId.current == t.imageId) { + newsItem = t; + } }); // need to fix when url is static because from didn't see ti when u press save button on second time try { if (!image.current) { throw new Error("Image isn't uploaded yet"); } + if (newsItem) { news.id = newsItem.id; news.imageId = imageId.current as number; @@ -246,6 +255,7 @@ const NewsModal: React.FC<{ if (afterSubmit) { afterSubmit(news); } + setActionSuccess(true); setRemovedImage(undefined); } catch (e: unknown) { @@ -401,7 +411,6 @@ const NewsModal: React.FC<{ newsItem?.image ? createFileListData(newsItem.image) : [] } onChange={handleFileChange} - >

Виберіть чи перетягніть файл

@@ -412,11 +421,11 @@ const NewsModal: React.FC<{ label="Дата публікації: " rules={[{ required: true, message: 'Введіть дату' }]} > - current && current.isBefore(dayjs().startOf('day'))} + current && current.isBefore(dayjs().startOf('day'))} /> { - if (isStreetcodeVisible) { - streetcodeShortStore.fetchStreetcodesAll(); - } - }, []); - useEffect(() => { setWaitingForApiResponse(false); if (actionSuccess) { @@ -144,10 +139,11 @@ const PartnerModal: React.FC< { const handleInputChange = () => setIsSaved(false); const onStreetcodeSelect = (value: string) => { - const index = streetcodeShortStore.streetcodes.findIndex( - (c) => c.title === value, - ); - selectedStreetcodes.current.push(streetcodeShortStore.streetcodes[index]); + streetcodeShortStore.StreetcodesShortMap.forEach((streetcodeShort) => { + if (streetcodeShort.title === value) { + selectedStreetcodes.current.push(streetcodeShort); + } + }); }; const onStreetcodeDeselect = (value: string) => { @@ -271,7 +267,9 @@ const PartnerModal: React.FC< { }; partnersStore.getPartnerArray.map((t) => t).forEach((t) => { - if (formValues.title === t.title || imageId.current === t.logoId) partnerItem = t; + if (formValues.title === t.title || imageId.current === t.logoId) { + partnerItem = t; + } }); try { @@ -285,10 +283,12 @@ const PartnerModal: React.FC< { } else { partner.id = (await partnersStore.createPartner(partner)).id; } + if (afterSubmit) { const partnerWithLogo = partnersStore.PartnerMap.get(partner.id) as Partner; afterSubmit(partnerWithLogo); } + setActionSuccess(true); } catch (e: unknown) { message.error('Не вдалось оновити/створити партнера. Спробуйте ще раз.'); @@ -336,7 +336,7 @@ const PartnerModal: React.FC< {

{partnerItem ? 'Редагувати' : 'Додати'} {' '} -партнера + партнера

@@ -442,9 +442,11 @@ const PartnerModal: React.FC< { onSelect={onStreetcodeSelect} onDeselect={onStreetcodeDeselect} > - {streetcodeShortStore.streetcodes.map((s) => ( - - {s.title} + {Array.from( + streetcodeShortStore.StreetcodesShortMap.values(), + ).map((streetcodeShort) => ( + + {streetcodeShort.title} ))} diff --git a/src/features/AdminPage/PartnersPage/Partners.component.tsx b/src/features/AdminPage/PartnersPage/Partners.component.tsx index 4dd9e5e8e..fb9708958 100644 --- a/src/features/AdminPage/PartnersPage/Partners.component.tsx +++ b/src/features/AdminPage/PartnersPage/Partners.component.tsx @@ -1,8 +1,7 @@ import './Partners.styles.scss'; -import { runInAction } from 'mobx'; import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined, StarOutlined } from '@ant-design/icons'; import facebook from '@assets/images/partners/facebook.svg'; import instagram from '@assets/images/partners/instagram.svg'; @@ -10,7 +9,6 @@ import twitter from '@assets/images/partners/twitterNew.svg'; import youtube from '@assets/images/partners/youtube.svg'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; -import ImageStore from '@stores/image-store'; import useMobx, { useModalContext } from '@stores/root-store'; import { useQuery } from '@tanstack/react-query'; @@ -30,38 +28,23 @@ import PartnerModal from './PartnerModal/PartnerModal.component'; const LogoType = [twitter, instagram, facebook, youtube]; const Partners: React.FC = observer(() => { - const { partnersStore } = useMobx(); + const { partnersStore, streetcodeShortStore } = useMobx(); const { modalStore } = useModalContext(); const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); - const [partnerToEdit, setPartnerToedit] = useState(); + const [partnerToEdit, setPartnerToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading: isLoadingPartners, refetch: refetchPartners } = useQuery({ queryKey: ['partners', partnersStore.PaginationInfo.CurrentPage], queryFn: () => partnersStore.getAll(), }); - const updatedPartners = () => { - Promise.all([ - partnersStore?.getAll(), - ]).then(() => { - partnersStore?.PartnerMap.forEach((val, key) => { - ImageStore.getImageById(val.logoId).then((logo) => { - runInAction(() => { - partnersStore.PartnerMap.set( - val.id, - { ...val, logo }, - ); - }); - }); - }); - }).then(() => { - partnersStore.setInternalMap(partnersStore.getPartnerArray); - }); - }; - useEffect(() => { - updatedPartners(); - }, [modalAddOpened, modalEditOpened]); + const { isLoading: isLoadingStreetcodesShort, refetch: refetchStreetcodesShort } = useQuery({ + queryKey: ['streetcodesShort', streetcodeShortStore.PaginationInfo.CurrentPage], + queryFn: () => streetcodeShortStore.fetchStreetcodesAll(), + }); + + const isLoading = isLoadingPartners || isLoadingStreetcodesShort; const handleDeletePartner = (partnerId: number) => { modalStore.setConfirmationModal( @@ -97,13 +80,13 @@ const Partners: React.FC = observer(() => { dataIndex: 'targetUrl', key: 'url', width: '28%', - render: (targeteurl) => ( + render: (targetUrl) => ( - {targeteurl.title ?? targeteurl.href} + {targetUrl.title ?? targetUrl.href} ), }, @@ -164,7 +147,7 @@ const Partners: React.FC = observer(() => { key={`${partner.id}${index}222`} className="actionButton" onClick={() => { - setPartnerToedit(partner); + setPartnerToEdit(partner); setModalEditOpened(true); }} /> @@ -220,12 +203,24 @@ const Partners: React.FC = observer(() => {
- + { + refetchPartners(); + refetchStreetcodesShort(); + }} + /> { + refetchPartners(); + refetchStreetcodesShort(); + }} /> diff --git a/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx b/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx index a66aebbf7..9a966db3d 100644 --- a/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx +++ b/src/features/AdminPage/StreetcodesTable/StreetcodesTable.component.tsx @@ -6,9 +6,7 @@ import { Link } from 'react-router-dom'; import { BarChartOutlined, DeleteOutlined, DownOutlined, EditOutlined, RollbackOutlined, } from '@ant-design/icons'; - import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; - import sortOptions from '@features/AdminPage/StreetcodesTable/constants/sortOptions'; import STREETCODE_STATES from '@features/AdminPage/StreetcodesTable/constants/streetcodeStates'; import { format } from 'date-fns'; @@ -298,10 +296,18 @@ const StreetcodesTable = () => { let currentStatus = ''; switch (streetcode.status) { - case STREETCODE_STATES.DRAFT.key: { currentStatus = STREETCODE_STATES.DRAFT.label; break; } - case STREETCODE_STATES.PUBLISHED.key: { currentStatus = STREETCODE_STATES.PUBLISHED.label; break; } - case STREETCODE_STATES.DELETED.key: { currentStatus = STREETCODE_STATES.DELETED.label; break; } - default: { currentStatus = STREETCODE_STATES.DRAFT.label; break; } + case STREETCODE_STATES.DRAFT.key: + currentStatus = STREETCODE_STATES.DRAFT.label; + break; + case STREETCODE_STATES.PUBLISHED.key: + currentStatus = STREETCODE_STATES.PUBLISHED.label; + break; + case STREETCODE_STATES.DELETED.key: + currentStatus = STREETCODE_STATES.DELETED.label; + break; + default: + currentStatus = STREETCODE_STATES.DRAFT.label; + break; } const mapedStreetCode = { key: streetcode.id, diff --git a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx index fc43ba065..faec0271e 100644 --- a/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx +++ b/src/features/AdminPage/TagsPage/TagsMainPage.component.tsx @@ -1,7 +1,7 @@ import './TagsMainPage.style.scss'; import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; @@ -22,19 +22,11 @@ const TagsMainPage: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [tagToEdit, setTagToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['tags', tagsStore.PaginationInfo.CurrentPage], queryFn: () => tagsStore.fetchAllTags(), }); - const updatedTags = () => { - tagsStore.fetchAllTags(); - }; - - useEffect(() => { - updatedTags(); - }, [modalAddOpened, modalEditOpened]); - const handleDeleteTag = (tagId: number) => { modalStore.setConfirmationModal( 'confirmation', @@ -56,7 +48,7 @@ const TagsMainPage: React.FC = observer(() => { title: 'Назва', dataIndex: 'title', key: 'title', - render(value, record) { + render(value) { return (
{value} @@ -89,6 +81,7 @@ const TagsMainPage: React.FC = observer(() => { ), }, ]; + return (
@@ -136,14 +129,16 @@ const TagsMainPage: React.FC = observer(() => {
- ); }); + export default TagsMainPage; diff --git a/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx b/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx index b0b8f8271..c2f332e29 100644 --- a/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx +++ b/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx @@ -4,7 +4,6 @@ import CancelBtn from '@images/utils/Cancel_btn.svg'; import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import BUTTON_LABELS from '@constants/buttonLabels'; -import { useAsync } from '@hooks/stateful/useAsync.hook'; import useMobx from '@stores/root-store'; import { @@ -22,6 +21,7 @@ interface SourceModalProps { setIsModalOpen: Dispatch>; initialData?: Tag; isNewTag?: (data: boolean) => void; + afterSubmit?: () => void; } const SourceModal: React.FC = ({ @@ -29,6 +29,7 @@ const SourceModal: React.FC = ({ setIsModalOpen, initialData, isNewTag, + afterSubmit, }) => { const { tagsStore } = useMobx(); const [form] = Form.useForm(); @@ -44,8 +45,6 @@ const SourceModal: React.FC = ({ setIsSaveButtonDisabled(!isChanged || isExisting || isEmpty); }; - useAsync(() => tagsStore.fetchAllTags(), []); - useEffect(() => { if (initialData && isModalVisible) { form.setFieldsValue({ @@ -74,7 +73,9 @@ const SourceModal: React.FC = ({ title: (formData.title as string).trim(), }; - if (currentTag.title === initialData?.title) return; + if (currentTag.title === initialData?.title) { + return; + } if (currentTag.id) { await tagsStore.updateTag(currentTag as Tag); @@ -85,6 +86,10 @@ const SourceModal: React.FC = ({ if (isNewTag !== undefined) { isNewTag(true); } + + if (afterSubmit) { + afterSubmit(); + } }; const handleCancel = () => { diff --git a/src/features/AdminPage/TeamPage/TeamModal/TeamModal.component.tsx b/src/features/AdminPage/TeamPage/TeamModal/TeamModal.component.tsx index bcbaf026d..5be079058 100644 --- a/src/features/AdminPage/TeamPage/TeamModal/TeamModal.component.tsx +++ b/src/features/AdminPage/TeamPage/TeamModal/TeamModal.component.tsx @@ -35,9 +35,16 @@ import Image from '@/models/media/image.model'; import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; const TeamModal: React.FC<{ - teamMember?: TeamMember, open: boolean, - setIsModalOpen: React.Dispatch>, afterSubmit?: (team: TeamCreateUpdate) => void -}> = observer(({ teamMember, open, setIsModalOpen, afterSubmit }) => { + teamMember?: TeamMember, + open: boolean, + setIsModalOpen: React.Dispatch>, + afterSubmit?: (team: TeamCreateUpdate) => void +}> = observer(({ + teamMember, + open, + setIsModalOpen, + afterSubmit, +}) => { const LOGO_TYPES = Object.keys(LogoType).filter((key) => Number.isNaN(Number(key))); const [form] = Form.useForm(); const { teamStore } = useMobx(); @@ -139,11 +146,9 @@ const TeamModal: React.FC<{ if (selectedIndex < 0) { const id = getNewId(selectedPositions); - setSelectedPositions([...selectedPositions, { id, position: selectedValue }]); } else { const selected = positions[selectedIndex]; - setSelectedPositions([...selectedPositions, { ...selected, position: selectedValue }]); } }; @@ -237,6 +242,7 @@ const TeamModal: React.FC<{ if (afterSubmit) { afterSubmit(team); } + setActionSuccess(true); } catch (error: unknown) { message.error('Не вдалось оновити/створити члена команди. Спробуйте ще раз.'); diff --git a/src/features/AdminPage/TeamPage/TeamPage.component.tsx b/src/features/AdminPage/TeamPage/TeamPage.component.tsx index c8b6208cf..0c2fe3043 100644 --- a/src/features/AdminPage/TeamPage/TeamPage.component.tsx +++ b/src/features/AdminPage/TeamPage/TeamPage.component.tsx @@ -2,7 +2,7 @@ import './TeamPage.styles.scss'; import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { DeleteOutlined, EditOutlined, StarOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; @@ -14,7 +14,6 @@ import { ColumnsType } from 'antd/es/table'; import Image from '@/models/media/image.model'; import base64ToUrl from '../../../app/common/utils/base64ToUrl.utility'; -import ImageStore from '../../../app/stores/image-store'; import useMobx, { useModalContext } from '../../../app/stores/root-store'; import TeamMember, { TeamMemberLink } from '../../../models/team/team.model'; import PageBar from '../PageBar/PageBar.component'; @@ -27,36 +26,24 @@ const TeamPage = () => { const { modalStore } = useModalContext(); const [modalAddOpened, setModalAddOpened] = useState(false); const [modalEditOpened, setModalEditOpened] = useState(false); - const [teamToEdit, setTeamToedit] = useState(); + const [teamToEdit, setTeamToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['team', teamStore.PaginationInfo.CurrentPage], queryFn: () => teamStore.getAll(), }); - const updatedTeam = () => { - Promise.all([teamStore?.getAll()]).then(() => { - teamStore?.TeamMap.forEach((val, key) => { - ImageStore.getImageById(val.imageId).then((image) => { - teamStore.TeamMap.set(val.id, { ...val, image }); - }); - }); - }).then(() => teamStore.setInternalMap(teamStore.getTeamArray)); - }; - - useEffect(() => { - updatedTeam(); - }, []); - const handleDeleteTeamMember = (teamId: number) => { modalStore.setConfirmationModal( 'confirmation', () => { - teamStore.deleteTeam(teamId).then(() => { - teamStore.TeamMap.delete(teamId); - }).catch((e) => { - console.error(e); - }); + teamStore.deleteTeam(teamId) + .then(() => { + teamStore.TeamMap.delete(teamId); + }) + .catch((e) => { + console.error(e); + }); modalStore.setConfirmationModal('confirmation'); }, CONFIRMATION_MESSAGES.DELETE_TEAM_MEMBER, @@ -163,15 +150,15 @@ const TeamPage = () => { key={`${team.id}${index}222`} className="actionButton" onClick={() => { - setTeamToedit(team); + setTeamToEdit(team); setModalEditOpened(true); }} /> -
), }, ]; + return (
@@ -217,14 +204,19 @@ const TeamPage = () => {
- + refetch()} + /> refetch()} /> ); }; + export default observer(TeamPage); diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx index bb07a8051..be24d794d 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsMainPage.component.tsx @@ -1,7 +1,7 @@ import './TeamPositionsMainPage.style.scss'; import { observer } from 'mobx-react-lite'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import BUTTON_LABELS from '@constants/buttonLabels'; import CONFIRMATION_MESSAGES from '@constants/confirmationMessages'; @@ -22,19 +22,11 @@ const TeamPositionsMainPage: React.FC = observer(() => { const [modalEditOpened, setModalEditOpened] = useState(false); const [positionToEdit, setPositionToEdit] = useState(); - const { isLoading } = useQuery({ + const { isLoading, refetch } = useQuery({ queryKey: ['positions', teamPositionsStore.PaginationInfo.CurrentPage], queryFn: () => teamPositionsStore.fetchPositions(), }); - const updatedPositions = () => { - teamPositionsStore.fetchPositions(); - }; - - useEffect(() => { - updatedPositions(); - }, [modalAddOpened, modalEditOpened]); - const handleDeletePosition = (positionId: number) => { modalStore.setConfirmationModal( 'confirmation', @@ -136,14 +128,16 @@ const TeamPositionsMainPage: React.FC = observer(() => { - ); }); + export default TeamPositionsMainPage; diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx index bc219ae9a..2b154c77d 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx @@ -1,39 +1,41 @@ /* eslint-disable max-len */ import CancelBtn from '@images/utils/Cancel_btn.svg'; + import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; -import { useAsync } from '@hooks/stateful/useAsync.hook'; +import React, { useEffect } from 'react'; +import BUTTON_LABELS from '@constants/buttonLabels'; import Position from '@models/additional-content/teampositions.model'; import useMobx from '@stores/root-store'; -import { Button, Form, Input, message, Modal, Popover, UploadFile } from 'antd'; -import {parseJsonNumber} from "ajv/dist/runtime/parseJson"; -import position = parseJsonNumber.position; + +import { + Button, Form, Input, message, Modal, Popover, +} from 'antd'; + import normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; -import BUTTON_LABELS from "@constants/buttonLabels"; interface TeamPositionsAdminProps { setIsModalOpen: React.Dispatch>; isModalVisible: boolean; initialData?: Position; isNewPosition?: (data: boolean) => void; + afterSubmit?: () => void; } const TeamPositionsAdminModalComponent: React.FC = observer(({ isModalVisible, setIsModalOpen, initialData, - isNewPosition + isNewPosition, + afterSubmit, }) => { - const {teamPositionsStore} = useMobx(); + const { teamPositionsStore } = useMobx(); const [form] = Form.useForm(); const isEditing = !!initialData; const closeModal = () => { setIsModalOpen(false); }; - useAsync(() => teamPositionsStore.fetchPositions(), []); - useEffect(() => { if (initialData && isModalVisible) { form.setFieldsValue({ @@ -43,9 +45,9 @@ const TeamPositionsAdminModalComponent: React.FC = obse }, [initialData, isModalVisible, form]); const validatePosition = uniquenessValidator( - ()=>(teamPositionsStore.getPositionsArray.map((position) => position.position)), - ()=>(initialData?.position), - 'Позиція з такою назвою вже існує' + () => (teamPositionsStore.getPositionsArray.map((position) => position.position)), + () => (initialData?.position), + 'Позиція з такою назвою вже існує', ); const onSubmit = async (formData: any) => { @@ -56,7 +58,9 @@ const TeamPositionsAdminModalComponent: React.FC = obse position: (formData.position as string).trim(), }; - if (currentPosition.position === initialData?.position) return; + if (currentPosition.position === initialData?.position) { + return; + } if (currentPosition.id) { await teamPositionsStore.updatePosition(currentPosition as Position); @@ -67,6 +71,10 @@ const TeamPositionsAdminModalComponent: React.FC = obse if (isNewPosition !== undefined) { isNewPosition(true); } + + if (afterSubmit) { + afterSubmit(); + } }; const handleCancel = () => { @@ -114,7 +122,7 @@ const TeamPositionsAdminModalComponent: React.FC = obse layout="vertical" onFinish={onSubmit} initialValues={initialData} - onKeyDown={(e) => e.key === 'Enter' ? e.preventDefault() : ''} + onKeyDown={(e) => (e.key === 'Enter' ? e.preventDefault() : '')} >

{isEditing ? 'Редагувати позицію' : 'Додати позицію'}

@@ -122,12 +130,13 @@ const TeamPositionsAdminModalComponent: React.FC = obse ({ value: normaliseWhitespaces(value) })} > - +
diff --git a/src/features/AdminPage/TermDictionary/TermDictionary.component.tsx b/src/features/AdminPage/TermDictionary/TermDictionary.component.tsx index 375cb0aa4..583b2771e 100644 --- a/src/features/AdminPage/TermDictionary/TermDictionary.component.tsx +++ b/src/features/AdminPage/TermDictionary/TermDictionary.component.tsx @@ -1,13 +1,15 @@ import './TermDictionary.styles.scss'; import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { DeleteFilled, EditFilled } from '@ant-design/icons'; import useMobx, { useModalContext } from '@stores/root-store'; +import { useQuery } from '@tanstack/react-query'; -import { Button, Space, Table } from 'antd'; +import { + Button, Empty, Pagination, Space, Table, +} from 'antd'; -import termsApi from '@/app/api/streetcode/text-content/terms.api'; import AddTermModal from '@/app/common/components/modals/Terms/AddTerm/AddTermModal.component'; import DeleteTermModal from '@/app/common/components/modals/Terms/DeleteTerm/DeleteTermModal.component'; import EditTermModal from '@/app/common/components/modals/Terms/EditTerm/EditTermModal.component'; @@ -17,23 +19,16 @@ import { Term } from '@/models/streetcode/text-contents.model'; const TermDictionary = () => { const { termsStore } = useMobx(); const { modalStore } = useModalContext(); - const { fetchTerms } = termsStore; const { setModal } = modalStore; const [term, setTerm] = useState>(); const [data, setData] = useState(); - const setTableState = async () => { - await termsApi.getAll().then((response) => { - setData(response); - }); - }; - - useEffect(() => { - fetchTerms(); - setTableState(); - }, []); + const { isLoading, refetch } = useQuery({ + queryKey: ['terms', termsStore.PaginationInfo.CurrentPage], + queryFn: () => termsStore.getAll(), + }); - const handleAdd = () => { + const handleAdd = async () => { const newTerm : Term = { id: 0, title: term?.title as string, @@ -45,25 +40,28 @@ const TermDictionary = () => { setTerm({ title: '', description: '' }); }, ); + await refetch(); }; - const handleDelete = (id: number) => { - termsStore.deleteTerm(id); - setData((data?.filter((termId) => termId.id !== id)) || []); - }; - - const handleEdit = (upd: Partial) => { + const handleEdit = async (upd: Partial) => { if (upd && upd.id !== undefined) { - let term: Term = upd as Term; - termsStore.updateTerm(upd.id, term); + const term: Term = upd as Term; + await termsStore.updateTerm(upd.id, term); setData(data?.map( (t) => (t.id === upd?.id ? { ...t, title: upd?.title as string, description: upd.description === undefined ? '' : upd.description as string } : t), )); + await refetch(); } }; + + const handleDelete = async (id: number) => { + await termsStore.deleteTerm(id); + setData((data?.filter((termId) => termId.id !== id)) || []); + }; + const columns = [ { title: 'Назва', @@ -116,10 +114,11 @@ const TermDictionary = () => { ), }, ]; + return (
- +

Словник термінів

@@ -134,16 +133,51 @@ const TermDictionary = () => {
id} - pagination={{ defaultPageSize: 5 }} + locale={{ + emptyText: isLoading ? ( +
+
+
+ ) : ( + + ), + }} /> +
+
+
+ { + termsStore.setCurrentPage(value); + }} + /> +
+
- - - + + + ); diff --git a/src/features/AdminPage/TermDictionary/TermDictionary.styles.scss b/src/features/AdminPage/TermDictionary/TermDictionary.styles.scss index fe93b8a35..f6d8eb0b1 100644 --- a/src/features/AdminPage/TermDictionary/TermDictionary.styles.scss +++ b/src/features/AdminPage/TermDictionary/TermDictionary.styles.scss @@ -82,3 +82,7 @@ color: c.$dark-red-color; } } + +.underTableElement { + padding-bottom: 10px; +} From 4ac1794ba45a1953e2f232ea34ceb7889556d4bc Mon Sep 17 00:00:00 2001 From: Michael-Kolpakov Date: Mon, 17 Mar 2025 13:28:41 +0200 Subject: [PATCH 2/4] Now when you delete the tag, it disappears from the table --- src/app/stores/tags-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/stores/tags-store.ts b/src/app/stores/tags-store.ts index 41e945ce4..314b1b20f 100644 --- a/src/app/stores/tags-store.ts +++ b/src/app/stores/tags-store.ts @@ -132,7 +132,7 @@ export default class TagsStore { try { await tagsApi.delete(tagId); runInAction(() => { - this.TagMap.delete(tagId); + this.AllTagsMap.delete(tagId); }); } catch (error: unknown) { /* empty */ } }; From 5b968ab7c57a50797f1f83bc8b4d2bbee2a54a1c Mon Sep 17 00:00:00 2001 From: Michael-Kolpakov Date: Fri, 18 Apr 2025 23:43:31 +0300 Subject: [PATCH 3/4] Fixed test problems. --- .../PartnerModal/PartnerModal.component.spec.tsx | 8 ++++---- .../PartnersPage/Partners.component.spec.tsx | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.spec.tsx b/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.spec.tsx index ccaaaf346..9df27fd6f 100644 --- a/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.spec.tsx +++ b/src/features/AdminPage/PartnersPage/PartnerModal/PartnerModal.component.spec.tsx @@ -71,10 +71,10 @@ jest.mock("@stores/root-store", () => ({ }, streetcodeShortStore: { fetchStreetcodesAll: jest.fn().mockResolvedValue([]), // Mock the fetch function - streetcodes: [ - { id: "1", title: "Streetcode 1" }, - { id: "2", title: "Streetcode 2" }, - ], + StreetcodesShortMap: new Map([ + [1, { id: 1, title: "Streetcode 1" }], + [2, { id: 2, title: "Streetcode 2" }], + ]), }, }) })); diff --git a/src/features/AdminPage/PartnersPage/Partners.component.spec.tsx b/src/features/AdminPage/PartnersPage/Partners.component.spec.tsx index d1a658d70..969c968cf 100644 --- a/src/features/AdminPage/PartnersPage/Partners.component.spec.tsx +++ b/src/features/AdminPage/PartnersPage/Partners.component.spec.tsx @@ -42,10 +42,13 @@ jest.mock('@stores/root-store', () => ({ imageStore: mockImageStore, streetcodeShortStore: { fetchStreetcodesAll: jest.fn().mockResolvedValue([]), // Mock the fetch function - streetcodes: [ - { id: '1', title: 'Streetcode 1' }, - { id: '2', title: 'Streetcode 2' }, - ], + StreetcodesShortMap: new Map(), + PaginationInfo: { + PageSize: 10, + TotalPages: 1, + TotalItems: 1, + CurrentPage: 1, + }, }, }), useModalContext: jest.fn(() => ({ From 32c725e34b1e0c3f9673c1bfd83ff2bb99ed07a8 Mon Sep 17 00:00:00 2001 From: Michael-Kolpakov Date: Sat, 19 Apr 2025 01:08:26 +0300 Subject: [PATCH 4/4] Fixed CategoryAdminModal test problem. --- .../CategoriesPage/CategoryAdminModal.component.spec.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.spec.tsx b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.spec.tsx index fe584b98b..bf849f6fa 100644 --- a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.spec.tsx +++ b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.spec.tsx @@ -133,6 +133,10 @@ describe('CategoryAdminModal', () => { userEvent.upload(uploader, new File(['(⌐□_□)'], 'new-image.jpg', { type: 'image/jpg' })); }); + await waitFor(() => { + expect(saveButton).not.toBeDisabled(); + }); + await waitFor(() => userEvent.click(saveButton)); await waitFor(() => {