admin/app/(dashboard)/contact-messages/page.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

205 lines
6.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import type { PaginationListColumnType } from '@/types'
import PaginatedList from '@/components/PaginatedList'
import PageNavbar from '@/components/layouts/PageNavbar'
import Button from '@/components/formElements/Button'
import FileCheckIcon from '@/components/icons/FileCheckIcon'
import Modal from '@/components/modals/Modal'
import AdminTableActions from '@/components/ui/AdminTableActions'
import AdminTableViewButton from '@/components/ui/AdminTableViewButton'
import StatusChip from '@/components/ui/StatusChip'
import axiosInstance from '@/config/axios'
import useAdminMutation from '@/hooks/useAdminMutation'
import { coerceToString } from '@/helpers'
import { formatIranianMobile, formatPersianDate, truncateValue } from '@/lib/formatters'
import { getBooleanStatus } from '@/constants/status'
import { API_ROUTES } from '@/services/config'
interface ContactMessageRow {
id: string
fullName: string
mobile: string
subject: string
message: string
readAt?: string | null
readBy?: string | null
createdAt: string
[key: string]: unknown
}
const columns: PaginationListColumnType[] = [
{
field: 'fullName',
label: 'نام',
filterable: false,
sortable: false,
},
{
field: 'mobile',
label: 'شماره تماس',
filterable: false,
sortable: false,
},
{
field: 'subject',
label: 'موضوع',
filterable: false,
sortable: false,
},
{
field: 'message',
label: 'پیام',
filterable: false,
sortable: false,
},
{
field: 'readStatus',
label: 'وضعیت',
filterable: true,
sortable: false,
type: 'select',
filterItems: [
{ code: 'unread', name: 'خوانده‌نشده' },
{ code: 'read', name: 'خوانده‌شده' },
],
},
{
field: 'createdAt',
label: 'تاریخ ثبت',
filterable: false,
sortable: true,
},
{
field: 'actions',
label: 'عملیات',
},
]
const ContactMessagesPage = () => {
const { pendingId, runAction } = useAdminMutation({ url: API_ROUTES.CONTACT_MESSAGES.ADMIN_LIST })
const [selected, setSelected] = useState<ContactMessageRow | null>(null)
return (
<section className="h-full w-full text-right">
<PageNavbar pageTitle="پیام‌های تماس" />
<div className="admin-page-container">
<PaginatedList
columns={columns}
url={API_ROUTES.CONTACT_MESSAGES.ADMIN_LIST}
>
{{
fullName: (_row, cellValue) => coerceToString(cellValue),
mobile: (_row, cellValue) => <span dir="ltr">{formatIranianMobile(coerceToString(cellValue))}</span>,
subject: (_row, cellValue) => coerceToString(cellValue),
message: (row, cellValue) => {
const message = row as ContactMessageRow
const body = coerceToString(cellValue)
return (
<div className="flex flex-col gap-1">
<span className="text-sm">{truncateValue(body)}</span>
{body.length > 80 ? (
<Button
className="w-fit"
size="sm"
variant="light"
onClick={() => {
setSelected(message)
}}
>
نمایش کامل
</Button>
) : null}
</div>
)
},
readStatus: (row) => {
const message = row as ContactMessageRow
const { label, chipColor } = getBooleanStatus(Boolean(message.readAt), {
true: 'خوانده‌شده',
false: 'خوانده‌نشده',
})
return (
<StatusChip
chipColor={chipColor}
label={label}
/>
)
},
createdAt: (_row, cellValue) => formatPersianDate(cellValue),
actions: (row) => {
const message = row as ContactMessageRow
return (
<AdminTableActions>
<AdminTableViewButton
label="مشاهده پیام"
mode="open-detail"
onClick={() => {
setSelected(message)
}}
/>
{message.readAt ? null : (
<Button
iconOnly
aria-label="علامت‌گذاری به‌عنوان خوانده‌شده"
isLoading={pendingId === message.id}
size="sm"
variant="light"
onClick={() =>
void runAction(
message.id,
() => axiosInstance.patch(API_ROUTES.CONTACT_MESSAGES.ADMIN_READ(message.id)),
'پیام خوانده شد'
)
}
>
<FileCheckIcon className="size-5" />
</Button>
)}
</AdminTableActions>
)
},
}}
</PaginatedList>
</div>
<Modal
hideFooter
isOpen={Boolean(selected)}
size="lg"
title={selected?.subject ?? 'پیام تماس'}
onOpenChange={(open) => {
if (!open) setSelected(null)
}}
>
{selected ? (
<div className="space-y-4 text-sm">
<div>
<p className="font-bold">فرستنده</p>
<p className="mt-1">{selected.fullName}</p>
<p
className="mt-1 text-text-muted"
dir="ltr"
>
{formatIranianMobile(selected.mobile)}
</p>
</div>
<div>
<p className="font-bold">پیام</p>
<p className="mt-1 whitespace-pre-wrap leading-relaxed">{selected.message}</p>
</div>
<p className="text-text-muted">{formatPersianDate(selected.createdAt)}</p>
</div>
) : null}
</Modal>
</section>
)
}
export default ContactMessagesPage