admin/app/(dashboard)/notifications/_components/PushDeliveriesPanel.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

219 lines
7.3 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 useDisclosure from '@/hooks/useDisclosure'
import PaginatedList from '@/components/PaginatedList'
import Button from '@/components/formElements/Button'
import Modal from '@/components/modals/Modal'
import AdminTableViewButton from '@/components/ui/AdminTableViewButton'
import StatusChip from '@/components/ui/StatusChip'
import { APP_ROUTES, CONSUMER_ROUTES } from '@/constants/routes'
import { coerceToString, formatPersonName } from '@/helpers'
import { API_ROUTES } from '@/services/config'
import { formatIranianMobile, formatPersianDate, truncateValue } from '@/lib/formatters'
interface PushDeliveryRow {
id: string
notificationId: string
userId: string
subscriptionId: string
mobile: string
category: string
title: string
body: string
actionUrl: string | null
status: string
errorMessage: string | null
userAgent: string | null
displayedAt: string | null
openedAt: string | null
createdAt: string
recipient?: {
firstName: string | null
lastName: string | null
}
}
const columns: PaginationListColumnType[] = [
{ field: 'userId', label: 'گیرنده', filterable: true, sortable: false, type: 'text' },
{ field: 'mobile', label: 'موبایل', filterable: true, sortable: false, type: 'text' },
{ field: 'title', label: 'عنوان', filterable: false, sortable: false },
{
field: 'status',
label: 'ارسال',
filterable: true,
sortable: false,
type: 'select',
filterItems: [
{ code: 'sent', name: 'ارسال‌شده' },
{ code: 'failed', name: 'ناموفق' },
],
},
{ field: 'displayedAt', label: 'نمایش', filterable: false, sortable: false },
{ field: 'openedAt', label: 'بازشدن', filterable: false, sortable: false },
{ field: 'createdAt', label: 'تاریخ ثبت', filterable: false, sortable: true },
{ field: 'actions', label: 'عملیات' },
]
const getPushStatus = (row: PushDeliveryRow) => {
if (row.status === 'failed') {
return {
label: 'ناموفق',
chipColor: 'danger' as const,
description: row.errorMessage ?? undefined,
}
}
if (row.openedAt) {
return { label: 'باز شده', chipColor: 'success' as const }
}
if (row.displayedAt) {
return { label: 'نمایش داده شده', chipColor: 'warning' as const }
}
return { label: 'ارسال‌شده', chipColor: 'default' as const }
}
const PushDeliveriesPanel = () => {
const { isOpen, onOpen, onOpenChange } = useDisclosure()
const [activeDelivery, setActiveDelivery] = useState<PushDeliveryRow | null>(null)
const openDetails = (row: PushDeliveryRow) => {
setActiveDelivery(row)
onOpen()
}
return (
<>
<PaginatedList
columns={columns}
url={API_ROUTES.PUSH_DELIVERIES.ADMIN_LIST}
>
{{
userId: (row) => {
const delivery = row as unknown as PushDeliveryRow
const name = formatPersonName(delivery.recipient?.firstName, delivery.recipient?.lastName)
return (
<div className="flex flex-col gap-1">
{name !== '—' && <span className="text-sm">{name}</span>}
<span
className="text-text-muted text-xs"
dir="ltr"
>
{formatIranianMobile(delivery.mobile)}
</span>
</div>
)
},
mobile: (_row, cellValue) => <span dir="ltr">{formatIranianMobile(coerceToString(cellValue))}</span>,
title: (row, cellValue) => {
const delivery = row as unknown as PushDeliveryRow
return (
<div className="flex flex-col gap-1">
<span className="text-sm">{truncateValue(coerceToString(cellValue), 50)}</span>
<span className="text-text-muted text-xs">{truncateValue(delivery.body, 70)}</span>
</div>
)
},
status: (row) => {
const delivery = row as unknown as PushDeliveryRow
const status = getPushStatus(delivery)
return (
<StatusChip
chipColor={status.chipColor}
description={status.description}
label={status.label}
/>
)
},
displayedAt: (_row, cellValue) => formatPersianDate(cellValue),
openedAt: (_row, cellValue) => formatPersianDate(cellValue),
createdAt: (_row, cellValue) => formatPersianDate(cellValue),
actions: (row) => {
const delivery = row as unknown as PushDeliveryRow
return (
<div className="flex items-center gap-2">
<Button
size="sm"
variant="light"
onClick={() => {
openDetails(delivery)
}}
>
جزئیات
</Button>
<AdminTableViewButton
label="مشاهده کاربر"
mode="navigate"
to={APP_ROUTES.USER_DETAIL(delivery.userId)}
/>
</div>
)
},
}}
</PaginatedList>
<Modal
hideFooter
isOpen={isOpen}
size="2xl"
title="جزئیات تحویل پوش"
onOpenChange={onOpenChange}
>
<div className="space-y-4 text-sm leading-7">
<div>
<p className="font-semibold text-secondary-10">عنوان</p>
<p>{activeDelivery?.title ?? '—'}</p>
</div>
<div>
<p className="font-semibold text-secondary-10">متن اعلان</p>
<p className="whitespace-pre-wrap">{activeDelivery?.body ?? '—'}</p>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<p className="font-semibold text-secondary-10">مسیر مقصد</p>
<p dir="ltr">{activeDelivery?.actionUrl ?? CONSUMER_ROUTES.PROFILE_NOTIFICATIONS}</p>
</div>
<div>
<p className="font-semibold text-secondary-10">دسته</p>
<p>{activeDelivery?.category ?? '—'}</p>
</div>
<div>
<p className="font-semibold text-secondary-10">زمان نمایش</p>
<p>{formatPersianDate(activeDelivery?.displayedAt)}</p>
</div>
<div>
<p className="font-semibold text-secondary-10">زمان بازشدن</p>
<p>{formatPersianDate(activeDelivery?.openedAt)}</p>
</div>
</div>
<div>
<p className="font-semibold text-secondary-10">User-Agent</p>
<p
className="break-all text-xs text-secondary-30"
dir="ltr"
>
{activeDelivery?.userAgent ?? '—'}
</p>
</div>
{activeDelivery?.errorMessage ? (
<div>
<p className="font-semibold text-fourth-900">خطای تحویل</p>
<p className="whitespace-pre-wrap text-fourth-900">{activeDelivery.errorMessage}</p>
</div>
) : null}
</div>
</Modal>
</>
)
}
export default PushDeliveriesPanel