From 69908781dd2461791db1800dd3144467691c2eb3 Mon Sep 17 00:00:00 2001 From: AnastasiaRakuta Date: Thu, 13 Aug 2026 12:08:12 +0200 Subject: [PATCH 1/5] Added publish icon for draft news --- public/i18n/en/admin.json | 4 +-- public/i18n/uk/admin.json | 4 +-- src/pages/admin/News/NewsAdminList.tsx | 7 ++++- src/pages/admin/News/NewsAdminRow.tsx | 43 ++++++++++++++++++++++++-- src/pages/admin/News/NewsForm.tsx | 2 +- src/shared/services/newsService.ts | 5 ++- 6 files changed, 54 insertions(+), 11 deletions(-) diff --git a/public/i18n/en/admin.json b/public/i18n/en/admin.json index ef96dee..aa97e14 100644 --- a/public/i18n/en/admin.json +++ b/public/i18n/en/admin.json @@ -60,9 +60,9 @@ "confirmNo": "No", "publishTitle": "Confirmation", "publishNow": "Publish now", - "saveAsDraft": "Save as draft", + "saveAsDraft": "Publish later", "confirmPublish": "Are you sure you want to publish this news?", - "confirmDraft": "Are you sure you want to save this news as a draft?", + "confirmDraft": "Are you sure you want to save this news as a draft? It will be saved in the dashboard and you can publish it later.", "createFailed": "Failed to create news. Please try again.", "createdAndPublished": "News successfully created and published!", "savedAsDraft": "News successfully saved as draft!", diff --git a/public/i18n/uk/admin.json b/public/i18n/uk/admin.json index 2d02609..00ab53d 100644 --- a/public/i18n/uk/admin.json +++ b/public/i18n/uk/admin.json @@ -60,9 +60,9 @@ "confirmNo": "Ні", "publishTitle": "Підтвердження", "publishNow": "Опублікувати зараз", - "saveAsDraft": "Зберегти як чернетку", + "saveAsDraft": "Опублікувати пізніше", "confirmPublish": "Ви впевнені, що хочете опублікувати цю новину?", - "confirmDraft": "Ви впевнені, що хочете зберегти цю новину як чернетку?", + "confirmDraft": "Ви впевнені, що хочете зберегти цю новину як чернетку? Вона буде збережена у кабінеті і ви зможете опублікувати її пізніше.", "createFailed": "Не вдалося створити новину. Будь ласка, спробуйте ще раз.", "createdAndPublished": "Новина успішно створена та опублікована!", "savedAsDraft": "Новина успішно збережена як чернетка!", diff --git a/src/pages/admin/News/NewsAdminList.tsx b/src/pages/admin/News/NewsAdminList.tsx index 55538e1..3cb1fe6 100644 --- a/src/pages/admin/News/NewsAdminList.tsx +++ b/src/pages/admin/News/NewsAdminList.tsx @@ -59,6 +59,11 @@ export default function NewsAdminList() { setPage(0); }; + const handlePublished = () => { + setPage(0); + loadNews(); + }; + const renderContent = () => { if (loading) { return

{t('news.loading')}

; @@ -80,7 +85,7 @@ export default function NewsAdminList() { {news.map(item => ( - + ))} diff --git a/src/pages/admin/News/NewsAdminRow.tsx b/src/pages/admin/News/NewsAdminRow.tsx index ed6156b..14f9b8e 100644 --- a/src/pages/admin/News/NewsAdminRow.tsx +++ b/src/pages/admin/News/NewsAdminRow.tsx @@ -1,7 +1,7 @@ import { Modal, ModalDialog, ModalClose, DialogTitle, DialogContent, DialogActions } from '@mui/joy'; import { newsService } from '@services/newsService'; import type { NewsAdminItem, NewsStatus } from '@shared/models/news'; -import { SquarePen, Trash2 } from 'lucide-react'; +import { SquarePen, Trash2, Rocket } from 'lucide-react'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -12,13 +12,40 @@ import styles from './NewsAdmin.module.scss'; type NewsAdminRowProps = { readonly news: NewsAdminItem; readonly onDeleted: (id: number) => void; + readonly onPublished: () => void; }; -export default function NewsAdminRow({ news, onDeleted }: NewsAdminRowProps) { +export default function NewsAdminRow({ news, onDeleted, onPublished }: NewsAdminRowProps) { const { t } = useTranslation('admin'); const navigate = useNavigate(); const [deleteOpen, setDeleteOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); + const [isPublishing, setIsPublishing] = useState(false); + + const handlePublish = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (isPublishing) return; + + setIsPublishing(true); + try { + const fullNews = await newsService.getNewsById(news.id); + const { data: files } = await newsService.getFilesByNewsId(news.id); + await newsService.updateNews({ + id: news.id, + title: fullNews.title, + content: fullNews.content, + publishNow: true, + fileIds: files.map(f => f.id) + }); + toast.success(t('news-create.createdAndPublished')); + onPublished(); + } catch { + toast.error(t('news-create.createFailed')); + } finally { + setIsPublishing(false); + } + }; const statusBadgeClass: Record = { DRAFT: styles.badgeDraft, @@ -42,6 +69,18 @@ export default function NewsAdminRow({ news, onDeleted }: NewsAdminRowProps) {
+ +
)} - + {!isEditMode ? ( + + ) : publishNow ? ( + + ) : ( +
+ + +
+ )} setOpen(false)}> From 9aa204f388ef99de2e925ec91898c6a4c64f5fc8 Mon Sep 17 00:00:00 2001 From: AnastasiaRakuta Date: Mon, 17 Aug 2026 11:39:28 +0200 Subject: [PATCH 3/5] Added modal window for draft news --- src/pages/admin/News/NewsAdmin.module.scss | 9 +++ src/pages/admin/News/NewsAdminRow.tsx | 89 ++++++++++------------ src/pages/admin/News/NewsForm.tsx | 33 ++------ src/shared/components/ConfirmModal.tsx | 55 +++++++++++++ 4 files changed, 114 insertions(+), 72 deletions(-) create mode 100644 src/shared/components/ConfirmModal.tsx diff --git a/src/pages/admin/News/NewsAdmin.module.scss b/src/pages/admin/News/NewsAdmin.module.scss index 0ea5600..a8b1172 100644 --- a/src/pages/admin/News/NewsAdmin.module.scss +++ b/src/pages/admin/News/NewsAdmin.module.scss @@ -85,6 +85,15 @@ } } + .publish { + color: #6b7280; + + &:hover { + color: #15803d; + background: #dcfce7; + } + } + .delete { color: #6b7280; diff --git a/src/pages/admin/News/NewsAdminRow.tsx b/src/pages/admin/News/NewsAdminRow.tsx index e27e0da..f97c3f3 100644 --- a/src/pages/admin/News/NewsAdminRow.tsx +++ b/src/pages/admin/News/NewsAdminRow.tsx @@ -1,4 +1,4 @@ -import { Modal, ModalDialog, ModalClose, DialogTitle, DialogContent, DialogActions } from '@mui/joy'; +import { ConfirmModal } from '@shared/components/ConfirmModal'; import { newsService } from '@services/newsService'; import type { NewsAdminItem, NewsStatus } from '@shared/models/news'; import { SquarePen, Trash2, Rocket } from 'lucide-react'; @@ -19,12 +19,11 @@ export default function NewsAdminRow({ news, onDeleted, onPublished }: NewsAdmin const { t } = useTranslation('admin'); const navigate = useNavigate(); const [deleteOpen, setDeleteOpen] = useState(false); + const [publishOpen, setPublishOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isPublishing, setIsPublishing] = useState(false); - const handlePublish = async (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); + const handlePublishConfirm = async () => { if (isPublishing) return; setIsPublishing(true); @@ -39,9 +38,11 @@ export default function NewsAdminRow({ news, onDeleted, onPublished }: NewsAdmin fileIds: files.map(f => f.id) }); toast.success(t('news-create.publishedSuccessfully')); + setPublishOpen(false); onPublished(); } catch { toast.error(t('news-create.createFailed')); + setPublishOpen(false); } finally { setIsPublishing(false); } @@ -71,12 +72,16 @@ export default function NewsAdminRow({ news, onDeleted, onPublished }: NewsAdmin
@@ -113,47 +118,37 @@ export default function NewsAdminRow({ news, onDeleted, onPublished }: NewsAdmin - setDeleteOpen(false)}> - - - {t('news-delete.title')} - - {t('news-delete.confirmDelete')} - - - - - - - + setDeleteOpen(false)} + title={t('news-delete.title')} + message={t('news-delete.confirmDelete')} + isLoading={isDeleting} + onConfirm={() => { + if (isDeleting) return; + setIsDeleting(true); + newsService.deleteNews(news.id) + .then(() => { + setDeleteOpen(false); + onDeleted(news.id); + toast.success(t('news-delete.deletedSuccessfully')); + }) + .catch(() => { + toast.error(t('news-delete.deletedFailed')); + setDeleteOpen(false); + }) + .finally(() => setIsDeleting(false)); + }} + /> + + setPublishOpen(false)} + title={t('news-create.publishTitle')} + message={t('news-create.confirmPublish')} + isLoading={isPublishing} + onConfirm={handlePublishConfirm} + /> ); } \ No newline at end of file diff --git a/src/pages/admin/News/NewsForm.tsx b/src/pages/admin/News/NewsForm.tsx index 91cd94f..839fe86 100644 --- a/src/pages/admin/News/NewsForm.tsx +++ b/src/pages/admin/News/NewsForm.tsx @@ -1,13 +1,6 @@ import { BackButton } from '@components/BackButton'; import Editor, { type EditorHandle } from '@components/Editor'; -import { - Modal, - ModalDialog, - DialogTitle, - DialogContent, - DialogActions, - ModalClose, -} from '@mui/joy'; +import { ConfirmModal } from '@shared/components/ConfirmModal'; import { newsService } from '@services/newsService'; import type { NewsDto } from '@shared/models/news'; import React, { useState, useRef, useEffect } from 'react'; @@ -247,23 +240,13 @@ const NewsForm: React.FC = () => { )} - setOpen(false)}> - - - {t('news-create.publishTitle')} - - {getDialogMessage()} - - - - - - - +
); }; diff --git a/src/shared/components/ConfirmModal.tsx b/src/shared/components/ConfirmModal.tsx new file mode 100644 index 0000000..606a85a --- /dev/null +++ b/src/shared/components/ConfirmModal.tsx @@ -0,0 +1,55 @@ +import { Modal, ModalDialog, ModalClose, DialogTitle, DialogContent, DialogActions } from '@mui/joy'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export interface ConfirmModalProps { + open: boolean; + onClose: () => void; + title: string; + message: string; + onConfirm: () => void; + confirmText?: string; + cancelText?: string; + isLoading?: boolean; +} + +export const ConfirmModal: React.FC = ({ + open, + onClose, + title, + message, + onConfirm, + confirmText, + cancelText, + isLoading = false, +}) => { + const { t } = useTranslation('admin'); + + return ( + + + + {title} + {message} + + + + + + + ); +}; From 3df410e3a7122b37c001dcb4a0130432fa7adc63 Mon Sep 17 00:00:00 2001 From: AnastasiaRakuta Date: Mon, 17 Aug 2026 11:49:06 +0200 Subject: [PATCH 4/5] Resolved sonar issues --- src/pages/admin/News/NewsAdminRow.tsx | 2 +- src/pages/admin/News/NewsForm.tsx | 70 +++++++++++++++----------- src/shared/components/ConfirmModal.tsx | 2 +- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/pages/admin/News/NewsAdminRow.tsx b/src/pages/admin/News/NewsAdminRow.tsx index f97c3f3..dc8617a 100644 --- a/src/pages/admin/News/NewsAdminRow.tsx +++ b/src/pages/admin/News/NewsAdminRow.tsx @@ -1,5 +1,5 @@ -import { ConfirmModal } from '@shared/components/ConfirmModal'; import { newsService } from '@services/newsService'; +import { ConfirmModal } from '@shared/components/ConfirmModal'; import type { NewsAdminItem, NewsStatus } from '@shared/models/news'; import { SquarePen, Trash2, Rocket } from 'lucide-react'; import { useState } from 'react'; diff --git a/src/pages/admin/News/NewsForm.tsx b/src/pages/admin/News/NewsForm.tsx index 839fe86..5b24b0a 100644 --- a/src/pages/admin/News/NewsForm.tsx +++ b/src/pages/admin/News/NewsForm.tsx @@ -1,7 +1,7 @@ import { BackButton } from '@components/BackButton'; import Editor, { type EditorHandle } from '@components/Editor'; -import { ConfirmModal } from '@shared/components/ConfirmModal'; import { newsService } from '@services/newsService'; +import { ConfirmModal } from '@shared/components/ConfirmModal'; import type { NewsDto } from '@shared/models/news'; import React, { useState, useRef, useEffect } from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -150,6 +150,45 @@ const NewsForm: React.FC = () => { return t('news-create.confirmDraft'); }; + const renderActionButtons = () => { + if (!isEditMode) { + return ( + + ); + } + + if (publishNow) { + return ( + + ); + } + + return ( +
+ + +
+ ); + }; + return (
{
)} - {!isEditMode ? ( - - ) : publishNow ? ( - - ) : ( -
- - -
- )} + {renderActionButtons()} = ({ isLoading = false, }) => { const { t } = useTranslation('admin'); - + return ( From fe3cc60f3b001c12084b6b00566460d236e84e2f Mon Sep 17 00:00:00 2001 From: AnastasiaRakuta Date: Mon, 17 Aug 2026 11:59:18 +0200 Subject: [PATCH 5/5] Resolved coderabbit issues --- src/pages/admin/News/NewsAdminList.tsx | 7 ++++--- src/shared/components/ConfirmModal.tsx | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/admin/News/NewsAdminList.tsx b/src/pages/admin/News/NewsAdminList.tsx index 3cb1fe6..3d63d53 100644 --- a/src/pages/admin/News/NewsAdminList.tsx +++ b/src/pages/admin/News/NewsAdminList.tsx @@ -21,6 +21,7 @@ export default function NewsAdminList() { const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [statuses, setStatuses] = useState([]); + const [reloadVersion, setReloadVersion] = useState(0); const loadNews = useCallback(async (signal?: AbortSignal) => { setLoading(true); @@ -45,7 +46,7 @@ export default function NewsAdminList() { loadNews(controller.signal); return () => controller.abort(); - }, [loadNews]); + }, [loadNews, reloadVersion]); const handleDeleted = (id: number) => { setNews(prev => prev.filter(n => n.id !== id)); @@ -60,8 +61,8 @@ export default function NewsAdminList() { }; const handlePublished = () => { - setPage(0); - loadNews(); + setPage(0); + setReloadVersion(version => version + 1); }; const renderContent = () => { diff --git a/src/shared/components/ConfirmModal.tsx b/src/shared/components/ConfirmModal.tsx index 25220b6..a7ca275 100644 --- a/src/shared/components/ConfirmModal.tsx +++ b/src/shared/components/ConfirmModal.tsx @@ -26,9 +26,9 @@ export const ConfirmModal: React.FC = ({ const { t } = useTranslation('admin'); return ( - + !isLoading && onClose()}> - + {title} {message}