Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
import { useRef, useState } from 'react'
|
|
import dynamic from 'next/dynamic'
|
|
|
|
import type { PaginationListColumnType } from '@/types'
|
|
import { addToast } from '@/lib/toast'
|
|
import Button from '@/components/formElements/Button'
|
|
import PaginatedList, { type PaginatedListHandle } from '@/components/PaginatedList'
|
|
import PageNavbar from '@/components/layouts/PageNavbar'
|
|
import StatusChip from '@/components/ui/StatusChip'
|
|
import EditIconOutline from '@/components/icons/EditIconOutline'
|
|
import TrashIcon from '@/components/icons/TrashIcon'
|
|
import useAlertModal from '@/hooks/useAlertModal'
|
|
import { getFeaturedStatus, getPublishStatus } from '@/constants/status'
|
|
import { formatPersianDate } from '@/lib/formatters'
|
|
import { API_ROUTES } from '@/services/config'
|
|
import { type BlogArticle, DELETE_ARTICLE, GET_ARTICLE } from '@/services/blogArticles'
|
|
|
|
const ArticleFormModal = dynamic(() => import('@/app/(dashboard)/blog-articles/_components/ArticleFormModal'), { ssr: false })
|
|
|
|
interface ArticleRow {
|
|
id: number
|
|
title: string
|
|
slug: string
|
|
categoryName: string
|
|
isPublished: boolean
|
|
isFeatured: boolean
|
|
scheduledAt: string | null
|
|
updatedAt: string
|
|
[key: string]: unknown
|
|
}
|
|
|
|
const columns: PaginationListColumnType[] = [
|
|
{ field: 'title', label: 'عنوان', filterable: true, sortable: true, type: 'text' },
|
|
{ field: 'categoryName', label: 'دستهبندی', filterable: false, sortable: false },
|
|
{ field: 'isPublished', label: 'وضعیت', filterable: false, sortable: false },
|
|
{ field: 'isFeatured', label: 'ویژه', filterable: false, sortable: false },
|
|
{ field: 'updatedAt', label: 'آخرین بهروزرسانی', filterable: false, sortable: true },
|
|
{ field: 'actions', label: 'عملیات' },
|
|
]
|
|
|
|
const BlogArticlesAdminPage = () => {
|
|
const listRef = useRef<PaginatedListHandle>(null)
|
|
const { showAlert } = useAlertModal()
|
|
const [modalState, setModalState] = useState<{ article?: BlogArticle } | null>(null)
|
|
|
|
const openCreate = () => {
|
|
setModalState({})
|
|
}
|
|
|
|
const openEdit = async (id: number) => {
|
|
const result = await GET_ARTICLE(id)
|
|
|
|
if (!result.ok) return
|
|
setModalState({ article: result.data })
|
|
}
|
|
|
|
const handleDelete = (row: ArticleRow) => {
|
|
showAlert(`مقاله «${row.title}» حذف شود؟`, async () => {
|
|
const result = await DELETE_ARTICLE(row.id)
|
|
|
|
if (!result.ok) return
|
|
addToast({ title: 'مقاله حذف شد', color: 'success' })
|
|
listRef.current?.refresh()
|
|
})
|
|
}
|
|
|
|
return (
|
|
<section className="h-full w-full text-right">
|
|
<PageNavbar pageTitle="مقالات وبلاگ" />
|
|
<div className="admin-page-container space-y-4">
|
|
<PaginatedList
|
|
ref={listRef}
|
|
hasDynamicButton
|
|
columns={columns}
|
|
dynamicButtonText="مقاله جدید"
|
|
url={API_ROUTES.BLOG_ARTICLES.ADMIN_LIST}
|
|
urlParams={{ page: 1, pageSize: 20, sort: '-updatedAt', filters: {} }}
|
|
onDynamicButtonClick={openCreate}
|
|
>
|
|
{{
|
|
isPublished: (row) => {
|
|
const article = row as ArticleRow
|
|
|
|
return (
|
|
<StatusChip
|
|
{...getPublishStatus(article.isPublished, article.scheduledAt)}
|
|
description={article.scheduledAt ? formatPersianDate(article.scheduledAt) : undefined}
|
|
/>
|
|
)
|
|
},
|
|
isFeatured: (row) => {
|
|
const featured = getFeaturedStatus((row as ArticleRow).isFeatured)
|
|
|
|
return featured ? <StatusChip {...featured} /> : '—'
|
|
},
|
|
updatedAt: (_row, cellValue) => formatPersianDate(cellValue),
|
|
actions: (row) => {
|
|
const article = row as ArticleRow
|
|
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
iconOnly
|
|
aria-label="ویرایش مقاله"
|
|
size="sm"
|
|
variant="flat"
|
|
onClick={() => openEdit(article.id)}
|
|
>
|
|
<EditIconOutline className="size-4" />
|
|
</Button>
|
|
<Button
|
|
iconOnly
|
|
aria-label="حذف مقاله"
|
|
color="danger"
|
|
size="sm"
|
|
variant="flat"
|
|
onClick={() => {
|
|
handleDelete(article)
|
|
}}
|
|
>
|
|
<TrashIcon className="size-4 text-fourth-900" />
|
|
</Button>
|
|
</div>
|
|
)
|
|
},
|
|
}}
|
|
</PaginatedList>
|
|
</div>
|
|
|
|
{modalState ? (
|
|
<ArticleFormModal
|
|
article={modalState.article}
|
|
isOpen={modalState !== null}
|
|
onOpenChange={(open) => {
|
|
if (!open) setModalState(null)
|
|
}}
|
|
onSuccess={() => listRef.current?.refresh()}
|
|
/>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export default BlogArticlesAdminPage
|