- Integrated TourButton component into SettingsMenuPage, SkillsPage, SmsWalletPage, StaffPage, StaffSessionDetailPage, StaffTreatmentSessionsPage, SubscriptionPage, TagsSettingsPage, TreatmentCasesPage to enhance user onboarding experience. - Created new tour definitions for appointments, clinics, staff management, financial management, and patient management, ensuring comprehensive guidance for users navigating the admin panel. - Updated documentation to reflect the addition of tours and their implementation details.
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { toast } from 'sonner';
|
|
import { api } from '../lib/api';
|
|
import type { ApiResponse, PaginatedResponse } from '../lib/api';
|
|
import type { Blog } from '../types';
|
|
import { formatDate } from '../lib/utils';
|
|
import PageHeader from '../components/ui/PageHeader';
|
|
import DataTable, { Column } from '../components/ui/DataTable';
|
|
import Pagination from '../components/ui/Pagination';
|
|
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
|
|
|
const limit = 15;
|
|
const STATUS_LABEL: Record<string, string> = { draft: 'پیشنویس', published: 'منتشر شده', archived: 'آرشیو' };
|
|
|
|
export default function RepresentationBlogsPage() {
|
|
const navigate = useNavigate();
|
|
const qc = useQueryClient();
|
|
const [page, setPage] = useState(1);
|
|
const [deleteTarget, setDeleteTarget] = useState<Blog | null>(null);
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['rep-blogs', page],
|
|
queryFn: () => {
|
|
const params = new URLSearchParams({ page: String(page), limit: String(limit) });
|
|
return api.get<PaginatedResponse<Blog>>(`/api/v1/representation/blogs?${params}`);
|
|
},
|
|
});
|
|
|
|
const items = data?.data ?? [];
|
|
const total = data?.meta?.totalRecords ?? 0;
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (b: Blog) => api.delete<ApiResponse<null>>(`/api/v1/representation/blog/${b.uuid}`),
|
|
onSuccess: () => {
|
|
toast.success('مقاله حذف شد');
|
|
setDeleteTarget(null);
|
|
qc.invalidateQueries({ queryKey: ['rep-blogs'] });
|
|
},
|
|
onError: (err: Error) => toast.error(err.message),
|
|
});
|
|
|
|
const columns: Column<Blog>[] = [
|
|
{ key: 'title', header: 'عنوان', render: (b) => <span className="fw-600">{b.title}</span> },
|
|
{ key: 'status', header: 'وضعیت', render: (b) => <span className="badge gray"><span className="bdot" />{STATUS_LABEL[b.status] ?? b.status}</span> },
|
|
{ key: 'city', header: 'شهر', render: (b) => b.city?.name ?? '—' },
|
|
{ key: 'scheduled_at', header: 'زمانبندی', render: (b) => (b.scheduled_at ? formatDate(b.scheduled_at) : '—') },
|
|
{ key: 'created_at', header: 'تاریخ', render: (b) => formatDate(b.created_at) },
|
|
];
|
|
|
|
return (
|
|
<div className="fade-in">
|
|
<PageHeader
|
|
title="وبلاگ من"
|
|
tourId="representation-blogs"
|
|
description="مقالات مخصوص دامنه و برند شما."
|
|
action={<button className="cp-btn-primary" onClick={() => navigate('/admin/representation-blogs/new')}>مقالهٔ جدید</button>}
|
|
/>
|
|
|
|
<div className="card card-pad" style={{ marginTop: 'var(--gap)' }}>
|
|
<DataTable<Blog>
|
|
columns={columns}
|
|
data={items}
|
|
loading={isLoading}
|
|
emptyMessage="هنوز مقالهای ننوشتهاید"
|
|
actions={(b) => (
|
|
<div className="row-actions">
|
|
<button className="mini-btn" onClick={() => navigate(`/admin/representation-blogs/${b.uuid}/edit`)}>ویرایش</button>
|
|
<button className="mini-btn danger" onClick={() => setDeleteTarget(b)}>حذف</button>
|
|
</div>
|
|
)}
|
|
/>
|
|
<Pagination page={page} total={total} limit={limit} onPageChange={setPage} />
|
|
</div>
|
|
|
|
<ConfirmDialog
|
|
open={!!deleteTarget}
|
|
title="حذف مقاله"
|
|
message={`«${deleteTarget?.title ?? ''}» حذف شود؟`}
|
|
danger
|
|
loading={deleteMutation.isPending}
|
|
onConfirm={() => deleteTarget && deleteMutation.mutate(deleteTarget)}
|
|
onCancel={() => setDeleteTarget(null)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|