diff --git a/src/app/common/utils/normaliseWhitespaces.ts b/src/app/common/utils/normaliseWhitespaces.ts new file mode 100644 index 000000000..327d9993e --- /dev/null +++ b/src/app/common/utils/normaliseWhitespaces.ts @@ -0,0 +1,2 @@ +const normaliseWhitespaces = (value: string) => (value && value.replace(/\s+/g, ' ')); +export default normaliseWhitespaces; \ No newline at end of file diff --git a/src/app/common/utils/uniquenessValidator.ts b/src/app/common/utils/uniquenessValidator.ts new file mode 100644 index 000000000..7021677f7 --- /dev/null +++ b/src/app/common/utils/uniquenessValidator.ts @@ -0,0 +1,13 @@ +/* eslint-disable max-len */ +import normaliseWhitespaces from './normaliseWhitespaces'; + +const uniquenessValidator = (getAll:()=>string[], getInitial:()=>string|undefined, message?:string) => (async (rule: any, value: string) => new Promise((resolve, reject) => { + const normalizedValue = normaliseWhitespaces(value)?.trim(); + if (!!value && getAll().includes(normalizedValue) && normalizedValue !== getInitial()) { + reject(message || 'Value already exists'); + } else { + resolve(); + } +})); + +export default uniquenessValidator; diff --git a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx index 9dd9867e5..0611f619b 100644 --- a/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx +++ b/src/features/AdminPage/CategoriesPage/CategoriesPage/CategoryAdminModal.component.tsx @@ -26,6 +26,8 @@ import base64ToUrl from '@/app/common/utils/base64ToUrl.utility'; import PreviewFileModal from '../../NewStreetcode/MainBlock/PreviewFileModal/PreviewFileModal.component'; import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; +import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; +import normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; interface SourceModalProps { isModalVisible: boolean; @@ -92,12 +94,18 @@ const SourceModal: React.FC = ({ setIsModalOpen(false); }; + const validateCategory = uniquenessValidator( + () => (sourcesAdminStore.getSourcesAdmin.map((source) => source.title)), + () => (initialData?.title), + 'Категорія з такою назвою вже існує', + ); + const onSubmit = async (formData: any) => { await form.validateFields(); const currentSource: SourceCategoryAdmin = { id: initialData ? initialData.id : 0, - title: formData.title, + title: (formData.title as string).trim(), imageId: imageId.current, image, }; @@ -138,7 +146,7 @@ const SourceModal: React.FC = ({ return; } form.submit(); - message.success('Категорію успішно додано!', 2); + message.success(`Категорію успішно ${isEditing ? "змінено" : "додано"}!`, 2); } catch (error) { message.config({ top: 100, @@ -176,7 +184,10 @@ const SourceModal: React.FC = ({ ({ value: normaliseWhitespaces(value) })} > diff --git a/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx b/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx index b4c4f721d..9c899d6d9 100644 --- a/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx +++ b/src/features/AdminPage/ContextPage/ContextModal/ContextAdminModal.component.tsx @@ -7,6 +7,8 @@ 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 normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; +import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; interface ContextAdminProps { setIsModalOpen: React.Dispatch>; @@ -16,15 +18,14 @@ interface ContextAdminProps { } const ContextAdminModalComponent: React.FC = observer(({ - isModalVisible, - setIsModalOpen, - initialData, - isNewContext - }) => { + isModalVisible, + setIsModalOpen, + initialData, + isNewContext +}) => { const {contextStore} = useMobx(); const [form] = Form.useForm(); const isEditing = !!initialData; - const [fileList, setFileList] = useState([]); const closeModal = () => { setIsModalOpen(false); }; @@ -39,24 +40,22 @@ const ContextAdminModalComponent: React.FC = observer(({ } }, [initialData, isModalVisible, form]); - const validateContext = async (rule: any, value: string) => { - return new Promise((resolve, reject) => { - if (contextStore.getContextArray.map((context) => context.title).includes(value)) { - reject('Контекст з такою назвою вже існує'); - } else { - resolve(); - } - }); - }; + const validateContext = uniquenessValidator( + ()=>(contextStore.getContextArray.map((context) => context.title)), + ()=>(initialData?.title), + 'Контекст з такою назвою вже існує' + ); const onSubmit = async (formData: any) => { await form.validateFields(); const currentContext = { - ...(initialData?.id && {id: initialData?.id}), - title: formData.title, + ...(initialData?.id && { id: initialData?.id }), + title: (formData.title as string).trim(), }; + if (currentContext.title === initialData?.title) return; + if (currentContext.id) { await contextStore.updateContext(currentContext as Context); } else { @@ -71,14 +70,13 @@ const ContextAdminModalComponent: React.FC = observer(({ const handleCancel = () => { closeModal(); form.resetFields(); - setFileList([]); }; const handleOk = async () => { try { await form.validateFields(); form.submit(); - message.success('Контекст успішно додано!'); + message.success(`Контекст успішно ${isEditing ? 'змінено' : 'додано'}!`); } catch (error) { message.config({ top: 100, @@ -125,6 +123,7 @@ const ContextAdminModalComponent: React.FC = observer(({ rules={[{required: true, message: 'Введіть назву', max: MAX_LENGTH.title}, {validator: validateContext} ]} + getValueProps={(value) => ({ value: normaliseWhitespaces(value) })} > diff --git a/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx b/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx index da36a780b..6952f3d3b 100644 --- a/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx +++ b/src/features/AdminPage/TagsPage/TagsPage/TagAdminModal.tsx @@ -15,6 +15,8 @@ import { import Tag from '@/models/additional-content/tag.model'; import POPOVER_CONTENT from '../../JobsPage/JobsModal/constants/popoverContent'; +import normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; +import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; interface SourceModalProps { isModalVisible: boolean; @@ -59,24 +61,22 @@ const SourceModal: React.FC = ({ setIsModalOpen(false); }; - const validateTag = async (rule: any, value: string) => { - return new Promise((resolve, reject) => { - if (tagsStore.getTagArray.map((tag) => tag.title).includes(value)) { - reject('Тег з такою назвою вже існує'); - } else { - resolve(); - } - }); - }; + const validateTag = uniquenessValidator( + ()=>(tagsStore.getTagArray.map((tag) => tag.title)), + ()=>(initialData?.title), + 'Тег з такою назвою вже існує' + ); const onSubmit = async (formData: any) => { await form.validateFields(); const currentTag = { ...(initialData?.id && { id: initialData?.id }), - title: formData.title, + title: (formData.title as string).trim(), }; + if (currentTag.title === initialData?.title) return; + if (currentTag.id) { await tagsStore.updateTag(currentTag as Tag); } else { @@ -97,7 +97,7 @@ const SourceModal: React.FC = ({ try { await form.validateFields(); form.submit(); - message.success('Тег успішно додано!', 2); + message.success(`Тег успішно ${isEditing ? 'змінено' : 'додано'}!`, 2); } catch (error) { message.config({ top: 100, @@ -136,6 +136,7 @@ const SourceModal: React.FC = ({ rules={[{ required: true, message: 'Введіть назву' }, { validator: validateTag } ]} + getValueProps={(value: string) => ({ value: normaliseWhitespaces(value) })} > diff --git a/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx b/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx index aa4cfacf6..288f91de4 100644 --- a/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx +++ b/src/features/AdminPage/TeamPositionsPage/TeamPositionsModal/TeamPositionsAdminModal.component.tsx @@ -8,6 +8,8 @@ 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 normaliseWhitespaces from '@/app/common/utils/normaliseWhitespaces'; +import uniquenessValidator from '@/app/common/utils/uniquenessValidator'; interface TeamPositionsAdminProps { setIsModalOpen: React.Dispatch>; @@ -17,15 +19,14 @@ interface TeamPositionsAdminProps { } const TeamPositionsAdminModalComponent: React.FC = observer(({ - isModalVisible, - setIsModalOpen, - initialData, - isNewPosition - }) => { + isModalVisible, + setIsModalOpen, + initialData, + isNewPosition +}) => { const {teamPositionsStore} = useMobx(); const [form] = Form.useForm(); const isEditing = !!initialData; - const [fileList, setFileList] = useState([]); const closeModal = () => { setIsModalOpen(false); }; @@ -40,24 +41,22 @@ const TeamPositionsAdminModalComponent: React.FC = obse } }, [initialData, isModalVisible, form]); - const validatePosition = async (rule: any, value: string) => { - return new Promise((resolve, reject) => { - if (teamPositionsStore.getPositionsArray.map((positiondata) => positiondata.position).includes(value)) { - reject('Позиція з такою назвою вже існує'); - } else { - resolve(); - } - }); - }; + const validatePosition = uniquenessValidator( + ()=>(teamPositionsStore.getPositionsArray.map((position) => position.position)), + ()=>(initialData?.position), + 'Позиція з такою назвою вже існує' + ); const onSubmit = async (formData: any) => { await form.validateFields(); const currentPosition = { - ...(initialData?.id && {id: initialData?.id}), - position: formData.position, + ...(initialData?.id && { id: initialData?.id }), + position: (formData.position as string).trim(), }; + if (currentPosition.position === initialData?.position) return; + if (currentPosition.id) { await teamPositionsStore.updatePosition(currentPosition as Position); } else { @@ -72,14 +71,13 @@ const TeamPositionsAdminModalComponent: React.FC = obse const handleCancel = () => { closeModal(); form.resetFields(); - setFileList([]); }; const handleOk = async () => { try { await form.validateFields(); form.submit(); - message.success('Позицію успішно додано!'); + message.success(`Позицію успішно ${isEditing ? 'змінено' : 'додано'}!`); } catch (error) { message.config({ top: 100, @@ -126,6 +124,7 @@ const TeamPositionsAdminModalComponent: React.FC = obse rules={[{required: true, message: 'Введіть назву', max: MAX_LENGTH.title}, {validator: validatePosition} ]} + getValueProps={(value) => ({ value: normaliseWhitespaces(value) })} >