-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/73 Write frontend for Admin/Interesting facts block #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
VolodymyrShapoval
merged 9 commits into
main
from
feat/73/write_frontend_for_admin_interesting_facts_block
Dec 17, 2025
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ef063d5
feat/73: enhance fact creation process with image handling and improv…
VolodymyrShapoval 4c1ecda
feat/73: partialy implement frontend for admin interesting facts bloc…
VolodymyrShapoval b0ecdd0
feat/73: add temporary code
VolodymyrShapoval a42cbfe
feat/73: add image description field to fact model
VolodymyrShapoval 3b2f645
feat/73: implement frontend enhancements for admin interesting facts …
VolodymyrShapoval adeeb66
feat/73: enhance frontend for admin interesting facts block with impr…
VolodymyrShapoval 7e2f9bc
refactor/73: return AdminPage to previous view
VolodymyrShapoval ccee495
refactor/73: add error logging for unsaved facts in InterestingFactsM…
VolodymyrShapoval 94e3524
refactor/73: change position of error logging for unsaved facts in In…
VolodymyrShapoval File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
242 changes: 195 additions & 47 deletions
242
...mponents/modals/InterestingFacts/FactsAdminModal/InterestingFactsAdminModal.component.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,87 +1,235 @@ | ||
| import './InterestingFactsAdminModal.style.scss'; | ||
| import './InterestingFactsAdminModal.styles.scss'; | ||
|
|
||
| import { observer } from 'mobx-react-lite'; | ||
| import { useState } from 'react'; | ||
| import { useEffect } from 'react'; | ||
| import { InboxOutlined } from '@ant-design/icons'; | ||
| import CancelBtn from '@assets/images/utils/Cancel_btn.svg'; | ||
| import useMobx from '@stores/root-store'; | ||
|
|
||
| import { Button, Form, Modal, Upload } from 'antd'; | ||
| import FormItem from 'antd/es/form/FormItem'; | ||
| import { UploadFile } from 'antd/lib/upload/interface'; | ||
|
|
||
| import Image, { ImageCreate } from '@/models/media/image.model'; | ||
| import { ImageCreate } from '@/models/media/image.model'; | ||
| import { Fact, FactCreate } from '@/models/streetcode/text-contents.model'; | ||
|
|
||
| const InterestingFactsModal = () => { | ||
| const { modalStore, factsStore, imagesStore: { getImageArray } } = useMobx(); | ||
| interface InterestingFactsModalProps { | ||
| streetcodeId: number; | ||
| factToEdit?: Fact | null; | ||
| } | ||
|
|
||
| const InterestingFactsModal = ({ streetcodeId = 1, factToEdit = null }: InterestingFactsModalProps) => { | ||
| const { modalStore, factsStore, imagesStore: { createImage } } = useMobx(); | ||
| const { setModal, modalsState: { adminFacts } } = modalStore; | ||
| const [factContent, setFactContent] = useState(''); | ||
| const [form] = Form.useForm(); | ||
|
|
||
| const titleValue = Form.useWatch('title', form) || ''; | ||
| const mainTextValue = Form.useWatch('mainText', form) || ''; | ||
| const imageDescriptionValue = Form.useWatch('imageDescription', form) || ''; | ||
|
|
||
| const titleCount = titleValue.length; | ||
| const mainTextCount = mainTextValue.length; | ||
| const imageDescCount = imageDescriptionValue.length; | ||
|
|
||
| useEffect(() => { | ||
| if (adminFacts.isOpen && factToEdit) { | ||
| form.setFieldsValue({ | ||
| title: factToEdit.title, | ||
| mainText: factToEdit.factContent, | ||
| imageDescription: factToEdit.imageDescription, | ||
| }); | ||
| } else if (adminFacts.isOpen && !factToEdit) { | ||
| form.resetFields(); | ||
| } | ||
| }, [adminFacts.isOpen, factToEdit, form]); | ||
|
|
||
| const characterCount = factContent.length | 0; | ||
| const fileToBase64 = (file: File): Promise<string> => { | ||
| return new Promise((resolve, reject) => { | ||
| const reader = new FileReader(); | ||
| reader.readAsDataURL(file); | ||
| reader.onload = () => { | ||
| const result = reader.result as string; | ||
| const base64 = result.split(',')[1]; | ||
| resolve(base64); | ||
| }; | ||
| reader.onerror = (error) => reject(error); | ||
| }); | ||
| }; | ||
|
|
||
| const normFile = (e: any) => { | ||
| if (Array.isArray(e)) { | ||
| return e; | ||
| } | ||
| return e?.fileList; | ||
| }; | ||
|
|
||
| const onFinish = (values: any) => { | ||
| const uploadedFile = values.picture.file as UploadFile<any>; | ||
| const onFinish = async (values: any) => { | ||
| let imgId = factToEdit?.imageId; | ||
|
|
||
| const image: ImageCreate = { | ||
| blobName: uploadedFile.name ?? '', | ||
| mimeType: uploadedFile.type ?? '', | ||
| }; | ||
| if (values.picture && values.picture.length > 0) { | ||
| const uploadedFile = values.picture[0]; | ||
| const file = uploadedFile.originFileObj as File; | ||
|
|
||
| if (!file) { | ||
| Modal.error({ title: 'Помилка', content: 'Не вдалося отримати файл' }); | ||
| return; | ||
| } | ||
|
|
||
| const fileName = uploadedFile.name ?? ''; | ||
| const extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); | ||
| const base64 = await fileToBase64(file); | ||
|
|
||
| const imageToCreate: ImageCreate = { | ||
| title: values.title || fileName, | ||
| baseFormat: base64, | ||
| mimeType: file.type ?? 'image/jpeg', | ||
| extension: extension, | ||
| }; | ||
|
|
||
| const createdImage = await createImage(imageToCreate); | ||
| if (!createdImage) { | ||
| Modal.error({ title: 'Помилка', content: 'Не вдалося створити зображення' }); | ||
| return; | ||
| } | ||
| imgId = createdImage.id; | ||
| } | ||
|
|
||
| const fact: FactCreate = { | ||
| title: values.title, | ||
| factContent, | ||
| image, | ||
| }; | ||
| if (!imgId && !factToEdit) { | ||
| Modal.error({ title: 'Помилка', content: 'Зображення є обовʼязковим' }); | ||
| return; | ||
| } | ||
|
|
||
| factsStore.addFactToCreate(fact); | ||
| try { | ||
| if (factToEdit) { | ||
| const fact: Fact = { | ||
| id: factToEdit.id, | ||
| title: values.title, | ||
| factContent: values.mainText, | ||
| imageId: imgId!, | ||
| order: factToEdit.order, | ||
| imageDescription: values.imageDescription, | ||
| }; | ||
| await factsStore.updateFact(fact); | ||
| } else { | ||
| const fact: FactCreate = { | ||
| title: values.title, | ||
| factContent: values.mainText, | ||
| imageId: imgId!, | ||
| imageDescription: values.imageDescription, | ||
| streetcodeId: streetcodeId, | ||
| }; | ||
| await factsStore.createFact(fact); | ||
| } | ||
|
|
||
| form.resetFields(); | ||
| setModal('adminFacts', undefined, false); | ||
| } catch (error) { | ||
| console.log("Error: Fact has not been saved!"); | ||
| Modal.error({ | ||
| title: 'Помилка', | ||
| content: 'Не вдалося зберегти факт' | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <Modal | ||
| className="interestingFactsAdminModal" | ||
| open={adminFacts.isOpen} | ||
| onCancel={() => setModal('adminFacts')} | ||
| onCancel={() => { | ||
| form.resetFields(); | ||
| setModal('adminFacts', undefined, false); | ||
| }} | ||
| footer={null} | ||
| maskClosable | ||
| centered | ||
| closeIcon={<CancelBtn />} | ||
| > | ||
| <Form className="factForm" onFinish={onFinish}> | ||
| <Form | ||
| form={form} | ||
| className="factForm" | ||
| onFinish={onFinish} | ||
| > | ||
| <h2>Wow-Факт</h2> | ||
| <p>Заголовок</p> | ||
|
|
||
| <div className="inputBlock"> | ||
| <Form.Item name="title"> | ||
| <input /> | ||
| </Form.Item> | ||
| <p>Заголовок</p> | ||
| <div className="inputWithCounter"> | ||
| <Form.Item | ||
| name="title" | ||
| rules={[ | ||
| { required: true, message: 'Поле обовʼязкове' }, | ||
| { max: 68, message: 'Максимум 68 символів' } | ||
| ]} | ||
| > | ||
| <input maxLength={68} /> | ||
| </Form.Item> | ||
| <span className="inputCounter">{titleCount}/68</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="textareaBlock"> | ||
| <p>Основний текст</p> | ||
| <textarea value={factContent} maxLength={600} onChange={(e) => setFactContent(e.target.value)} /> | ||
| <Form.Item | ||
| name="mainText" | ||
| rules={[ | ||
| { required: true, message: 'Поле обовʼязкове' }, | ||
| { max: 600, message: 'Максимум 600 символів' } | ||
| ]} | ||
| > | ||
| <textarea maxLength={600} /> | ||
| </Form.Item> | ||
| <p className="characterCounter"> | ||
| {characterCount} | ||
| /600 | ||
| {mainTextCount}/600 | ||
| </p> | ||
| </div> | ||
| <p>Зображення:</p> | ||
| <FormItem | ||
| name="picture" | ||
| className="" | ||
| > | ||
| <Upload | ||
| multiple={false} | ||
| accept=".jpeg,.png,.jpg" | ||
| listType="picture-card" | ||
| maxCount={1} | ||
|
|
||
| <div className="uploadBlock"> | ||
| <p>Зображення:</p> | ||
| <FormItem | ||
| name="picture" | ||
| valuePropName="fileList" | ||
| getValueFromEvent={normFile} | ||
| rules={[ | ||
| { required: !factToEdit, message: 'Зображення обовʼязкове' } | ||
| ]} | ||
| > | ||
| <div className="upload"> | ||
| <InboxOutlined /> | ||
| <p>Виберіть чи перетягніть файл</p> | ||
| </div> | ||
| </Upload> | ||
| </FormItem> | ||
| <Button className="saveButton" htmlType="submit">Зберегти</Button> | ||
| <Upload | ||
| multiple={false} | ||
| accept=".jpeg,.png,.jpg,.webp" | ||
| listType="picture-card" | ||
| maxCount={1} | ||
| beforeUpload={() => false} | ||
| > | ||
| <div className="upload"> | ||
| <InboxOutlined /> | ||
| <p>Виберіть чи перетягніть файл</p> | ||
| </div> | ||
| </Upload> | ||
| </FormItem> | ||
| </div> | ||
|
|
||
| <div className="imageDescriptionBlock"> | ||
| <p>Підпис до фото</p> | ||
| <div className="inputWithCounter"> | ||
| <Form.Item | ||
| name="imageDescription" | ||
| rules={[ | ||
| { required: true, message: 'Поле обовʼязкове' }, | ||
| { max: 200, message: 'Максимум 200 символів' } | ||
| ]} | ||
| > | ||
| <input maxLength={200} /> | ||
| </Form.Item> | ||
| <span className="inputCounter">{imageDescCount}/200</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Button className="saveButton" htmlType="submit"> | ||
| {factToEdit ? 'Оновити' : 'Зберегти'} | ||
| </Button> | ||
| </Form> | ||
| </Modal> | ||
| ); | ||
| }; | ||
|
|
||
| export default observer(InterestingFactsModal); | ||
| export default observer(InterestingFactsModal); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.