-
Notifications
You must be signed in to change notification settings - Fork 1
[Feature] Publish draft news #34
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
Changes from all commits
6990878
ac6bd1b
9aa204f
3df410e
fe3cc60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import { Modal, ModalDialog, ModalClose, DialogTitle, DialogContent, DialogActions } from '@mui/joy'; | ||
| import { newsService } from '@services/newsService'; | ||
| import { ConfirmModal } from '@shared/components/ConfirmModal'; | ||
| 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,41 @@ 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 [publishOpen, setPublishOpen] = useState(false); | ||
| const [isDeleting, setIsDeleting] = useState(false); | ||
| const [isPublishing, setIsPublishing] = useState(false); | ||
|
|
||
| const handlePublishConfirm = async () => { | ||
| 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.publishedSuccessfully')); | ||
| setPublishOpen(false); | ||
| onPublished(); | ||
| } catch { | ||
| toast.error(t('news-create.createFailed')); | ||
| setPublishOpen(false); | ||
|
Comment on lines
+43
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use a publication-specific failure message. This branch runs after a draft publication attempt. Add a 🤖 Prompt for AI Agents |
||
| } finally { | ||
| setIsPublishing(false); | ||
| } | ||
| }; | ||
|
|
||
| const statusBadgeClass: Record<NewsStatus, string> = { | ||
| DRAFT: styles.badgeDraft, | ||
|
|
@@ -42,6 +70,22 @@ export default function NewsAdminRow({ news, onDeleted }: NewsAdminRowProps) { | |
| </td> | ||
| <td data-label={t('news.columnActions')}> | ||
| <div className={styles.actions}> | ||
| <button | ||
| type="button" | ||
| className={styles.publish} | ||
| style={{ visibility: news.status === 'DRAFT' ? 'visible' : 'hidden' }} | ||
| aria-label={t('news-create.publishNow')} | ||
| title={t('news-create.publishNow')} | ||
| disabled={isPublishing || news.status !== 'DRAFT'} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setPublishOpen(true); | ||
| }} | ||
| > | ||
| <Rocket size={18} /> | ||
| </button> | ||
|
|
||
| <button | ||
| type="button" | ||
| className={styles.edit} | ||
|
|
@@ -74,47 +118,37 @@ export default function NewsAdminRow({ news, onDeleted }: NewsAdminRowProps) { | |
| </td> | ||
| </tr> | ||
|
|
||
| <Modal open={deleteOpen} onClose={() => setDeleteOpen(false)}> | ||
| <ModalDialog> | ||
| <ModalClose /> | ||
| <DialogTitle>{t('news-delete.title')}</DialogTitle> | ||
| <DialogContent> | ||
| {t('news-delete.confirmDelete')} | ||
| </DialogContent> | ||
| <DialogActions> | ||
| <button | ||
| type="button" | ||
| className="btn-regular" | ||
| disabled={isDeleting} | ||
| onClick={() => { | ||
| 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)); | ||
| }} | ||
| > | ||
| {t('news-delete.confirmYes')} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="btn" | ||
| disabled={isDeleting} | ||
| onClick={() => setDeleteOpen(false)} | ||
| > | ||
| {t('news-delete.confirmNo')} | ||
| </button> | ||
| </DialogActions> | ||
| </ModalDialog> | ||
| </Modal> | ||
| <ConfirmModal | ||
| open={deleteOpen} | ||
| onClose={() => 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)); | ||
| }} | ||
| /> | ||
|
|
||
| <ConfirmModal | ||
| open={publishOpen} | ||
| onClose={() => setPublishOpen(false)} | ||
| title={t('news-create.publishTitle')} | ||
| message={t('news-create.confirmPublish')} | ||
| isLoading={isPublishing} | ||
| onConfirm={handlePublishConfirm} | ||
| /> | ||
| </> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not reload with the stale page value.
loadNews()captures the currentpage. If publication occurs on page 2 or later, Line 64 requests that old page beforesetPage(0)takes effect. The effect then starts a page-zero request. If the old request resolves last, it replaces the page-zero list with stale data.Trigger a reload through state so the effect performs one request with the new page value.
Proposed fix
🤖 Prompt for AI Agents