diff --git a/assets/admin/App.tsx b/assets/admin/App.tsx index ec3fd504..6ab43496 100644 --- a/assets/admin/App.tsx +++ b/assets/admin/App.tsx @@ -16,6 +16,7 @@ import PaymentsPage from './pages/PaymentsPage'; import PaymentDetailPage from './pages/PaymentDetailPage'; import SettlementsPage from './pages/SettlementsPage'; import RepresentationsPage from './pages/RepresentationsPage'; +import RepresentationDetailPage from './pages/RepresentationDetailPage'; import CommentsPage from './pages/CommentsPage'; import RatingsPage from './pages/RatingsPage'; import SmsPage from './pages/SmsPage'; @@ -81,6 +82,7 @@ export default function App() { {/* Representations */} } /> + } /> {/* Comments & Ratings */} } /> diff --git a/assets/admin/components/ui/StatusBadge.tsx b/assets/admin/components/ui/StatusBadge.tsx index 62ce5f94..7ba4d175 100644 --- a/assets/admin/components/ui/StatusBadge.tsx +++ b/assets/admin/components/ui/StatusBadge.tsx @@ -49,7 +49,7 @@ const paymentMap: Record = { const smsMap: Record = { draft: { color: 'gray', label: 'پیش‌نویس' }, - pending_approval: { color: 'yellow', label: 'در انتظار تأیید' }, + pending: { color: 'yellow', label: 'در انتظار تأیید' }, approved: { color: 'green', label: 'تأیید شده' }, rejected: { color: 'red', label: 'رد شده' }, }; diff --git a/assets/admin/lib/api.ts b/assets/admin/lib/api.ts index cf0b9c07..4b6025a0 100644 --- a/assets/admin/lib/api.ts +++ b/assets/admin/lib/api.ts @@ -66,11 +66,11 @@ export interface ApiResponse { export interface PaginatedResponse { success: boolean; - data: { - items: T[]; - total: number; - page: number; - limit: number; + data: T[]; + meta: { + totalRecords: number; + totalPages: number; + currentPage: number; }; errors: []; } diff --git a/assets/admin/pages/AppointmentsPage.tsx b/assets/admin/pages/AppointmentsPage.tsx index 1e9c52a9..f144d2c4 100644 --- a/assets/admin/pages/AppointmentsPage.tsx +++ b/assets/admin/pages/AppointmentsPage.tsx @@ -37,7 +37,7 @@ export default function AppointmentsPage() { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); if (statusFilter) params.set('status', statusFilter); - return api.get>(`/api/v1/appointments?${params}`); + return api.get>(`/api/v1/admin/appointments?${params}`); }, }); @@ -77,8 +77,8 @@ export default function AppointmentsPage() { { key: 'created_at', header: 'تاریخ ثبت', render: (a) => formatDate(a.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/BlogsPage.tsx b/assets/admin/pages/BlogsPage.tsx index b65a7bb5..e8a6f154 100644 --- a/assets/admin/pages/BlogsPage.tsx +++ b/assets/admin/pages/BlogsPage.tsx @@ -92,8 +92,8 @@ export default function BlogsPage() { { key: 'created_at', header: 'تاریخ ثبت', render: (b) => formatDate(b.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/CategoriesPage.tsx b/assets/admin/pages/CategoriesPage.tsx index a8879e94..1169d558 100644 --- a/assets/admin/pages/CategoriesPage.tsx +++ b/assets/admin/pages/CategoriesPage.tsx @@ -16,17 +16,16 @@ import Modal from '../components/ui/Modal'; const TABS: { key: CategoryBundle; label: string }[] = [ { key: 'state', label: 'استان‌ها' }, { key: 'city', label: 'شهرها' }, - { key: 'specialty', label: 'تخصص‌ها' }, - { key: 'doctor_service', label: 'خدمات پزشک' }, + { key: 'specially_doctor', label: 'تخصص‌ها' }, + { key: 'doctor_services', label: 'خدمات پزشک' }, { key: 'insurance_type', label: 'نوع بیمه' }, { key: 'supplementary_insurance', label: 'بیمه تکمیلی' }, { key: 'tag', label: 'تگ‌های بلاگ' }, ]; const schema = z.object({ - name: z.string().min(1, 'نام الزامی است'), - code: z.string().optional(), - parent_uuid: z.string().optional(), + label: z.string().min(1, 'نام الزامی است'), + parent_id: z.string().optional(), }); type FormData = z.infer; @@ -41,7 +40,7 @@ export default function CategoriesPage() { const { data, isLoading } = useQuery({ queryKey: ['categories', activeTab], queryFn: () => - api.get>(`/api/v1/categorys/${activeTab}`), + api.get>(`/api/v1/categorys/${activeTab}`), }); const { register, handleSubmit, reset, setValue, formState: { errors, isSubmitting } } = useForm({ @@ -50,7 +49,11 @@ export default function CategoriesPage() { const createMutation = useMutation({ mutationFn: (d: FormData) => - api.post>('/api/v1/category', { ...d, bundle: activeTab }), + api.post>('/api/v1/category', { + label: d.label, + bundle: activeTab, + ...(d.parent_id ? { parent_id: parseInt(d.parent_id) } : {}), + }), onSuccess: () => { toast.success('دسته‌بندی اضافه شد'); setAddOpen(false); @@ -61,8 +64,8 @@ export default function CategoriesPage() { }); const updateMutation = useMutation({ - mutationFn: ({ uuid, d }: { uuid: string; d: FormData }) => - api.patch>(`/api/v1/category/${uuid}`, d), + mutationFn: ({ id, d }: { id: number; d: FormData }) => + api.patch>(`/api/v1/category/${id}`, { label: d.label }), onSuccess: () => { toast.success('دسته‌بندی بروزرسانی شد'); setEditTarget(null); @@ -73,7 +76,7 @@ export default function CategoriesPage() { }); const deleteMutation = useMutation({ - mutationFn: (c: Category) => api.delete>(`/api/v1/category/${c.uuid}`), + mutationFn: (c: Category) => api.delete>(`/api/v1/category/${c.id}`), onSuccess: () => { toast.success('دسته‌بندی حذف شد'); setDeleteTarget(null); @@ -84,20 +87,34 @@ export default function CategoriesPage() { const openEdit = (c: Category) => { setEditTarget(c); - setValue('name', c.name); - setValue('code', c.code ?? ''); - setValue('parent_uuid', c.parent_uuid ?? ''); + setValue('label', c.label); }; - const allItems = data?.data ?? []; + const allItems = data?.data?.data ?? []; const filtered = search - ? allItems.filter((c) => c.name.includes(search)) + ? allItems.filter((c) => c.label.includes(search)) : allItems; const columns: Column[] = [ - { key: 'name', header: 'نام', render: (c) => {c.name} }, - { key: 'code', header: 'کد', render: (c) => c.code ? {c.code} : '—' }, - { key: 'parent_name', header: 'والد', render: (c) => c.parent_name ?? '—' }, + { key: 'label', header: 'نام', render: (c) => {c.label} }, + { + key: 'bundle', + header: 'نوع', + render: (c) => ( + {c.bundle} + ), + }, + { + key: 'status', + header: 'وضعیت', + render: (c) => ( + + {c.status === 1 ? 'فعال' : 'غیرفعال'} + + ), + }, ]; const handleTabChange = (tab: CategoryBundle) => { @@ -158,6 +175,7 @@ export default function CategoriesPage() {
+ {/* Add Modal */} t.key === activeTab)?.label}`} onClose={() => { setAddOpen(false); reset(); }} footer={ @@ -176,25 +194,21 @@ export default function CategoriesPage() {
createMutation.mutate(d))} className="space-y-4">
- - {errors.name &&

{errors.name.message}

} + {errors.label &&

{errors.label.message}

}
-
- - -
- {(activeTab === 'city') && ( + {activeTab === 'city' && (
- - شناسه استان +
)}
+ {/* Edit Modal */} { setEditTarget(null); reset(); }} footer={ @@ -211,18 +225,13 @@ export default function CategoriesPage() { } >
editTarget && updateMutation.mutate({ uuid: editTarget.uuid, d }))} + onSubmit={handleSubmit((d) => editTarget && updateMutation.mutate({ id: editTarget.id, d }))} className="space-y-4">
- - {errors.name &&

{errors.name.message}

} -
-
- - + {errors.label &&

{errors.label.message}

}
@@ -230,7 +239,7 @@ export default function CategoriesPage() { formatDate(c.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/CommentsPage.tsx b/assets/admin/pages/CommentsPage.tsx index eafb2510..5bc7413c 100644 --- a/assets/admin/pages/CommentsPage.tsx +++ b/assets/admin/pages/CommentsPage.tsx @@ -14,15 +14,15 @@ import ConfirmDialog from '../components/ui/ConfirmDialog'; const FILTERS = [ { value: '', label: 'همه' }, - { value: 'false', label: 'در انتظار تأیید' }, - { value: 'true', label: 'تأییدشده' }, + { value: 'pending', label: 'در انتظار تأیید' }, + { value: 'approved', label: 'تأییدشده' }, ]; export default function CommentsPage() { const qc = useQueryClient(); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); - const [approvedFilter, setApprovedFilter] = useState('false'); + const [approvedFilter, setApprovedFilter] = useState('pending'); const [deleteTarget, setDeleteTarget] = useState(null); const limit = 15; @@ -31,13 +31,13 @@ export default function CommentsPage() { queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); - if (approvedFilter !== '') params.set('is_approved', approvedFilter); - return api.get>(`/api/v1/comments?${params}`); + if (approvedFilter !== '') params.set('status', approvedFilter); + return api.get>(`/api/v1/admin/comments?${params}`); }, }); const approveMutation = useMutation({ - mutationFn: (c: Comment) => api.patch>(`/api/v1/comment/${c.uuid}/approve`, {}), + mutationFn: (c: Comment) => api.post>(`/api/v1/admin/comment/${c.uuid}/approve`, {}), onSuccess: () => { toast.success('نظر تأیید شد'); qc.invalidateQueries({ queryKey: ['comments'] }); @@ -78,8 +78,8 @@ export default function CommentsPage() { { key: 'created_at', header: 'تاریخ', render: (c) => formatDate(c.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/DashboardPage.tsx b/assets/admin/pages/DashboardPage.tsx index 0b602e09..82f17337 100644 --- a/assets/admin/pages/DashboardPage.tsx +++ b/assets/admin/pages/DashboardPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useQuery } from '@tanstack/react-query'; import { UserGroupIcon, HeartIcon, @@ -7,34 +8,55 @@ import { CreditCardIcon, ChatBubbleLeftEllipsisIcon, BanknotesIcon, - ArrowTrendingUpIcon, + ArrowPathIcon, } from '@heroicons/react/24/outline'; +import { api } from '../lib/api'; +import type { ApiResponse } from '../lib/api'; +import { formatNumber, formatRial } from '../lib/utils'; + +interface DashboardStats { + total_users: number; + active_doctors: number; + total_doctors: number; + total_clinics: number; + today_appointments: number; + total_appointments: number; + today_payments_count: number; + today_payments_amount: number; + total_payments_amount: number; + pending_comments: number; + pending_settlements: number; +} interface StatCardProps { label: string; value: string | number; - trend?: string; - trendUp?: boolean; + sub?: string; icon: React.ElementType; iconBg: string; iconColor: string; + loading?: boolean; } -function StatCard({ label, value, trend, trendUp, icon: Icon, iconBg, iconColor }: StatCardProps) { +function StatCard({ label, value, sub, icon: Icon, iconBg, iconColor, loading }: StatCardProps) { return (
-
+

{label}

-

{value}

- {trend && ( -
- - {trend} -
+ {loading ? ( +
+ ) : ( +

{value}

+ )} + {sub && !loading && ( +

{sub}

+ )} + {sub && loading && ( +
)}
-
+
@@ -42,79 +64,106 @@ function StatCard({ label, value, trend, trendUp, icon: Icon, iconBg, iconColor ); } -const stats: StatCardProps[] = [ - { - label: 'کل کاربران', - value: '۱۲,۴۸۴', - trend: '۸٪ نسبت به ماه قبل', - trendUp: true, - icon: UserGroupIcon, - iconBg: 'bg-violet-100', - iconColor: 'text-violet-600', - }, - { - label: 'پزشکان فعال', - value: '۱,۲۸۴', - trend: '۱۲٪ نسبت به ماه قبل', - trendUp: true, - icon: HeartIcon, - iconBg: 'bg-emerald-100', - iconColor: 'text-emerald-600', - }, - { - label: 'کلینیک‌ها', - value: '۳۲۱', - trend: '۴٪ نسبت به ماه قبل', - trendUp: false, - icon: BuildingOffice2Icon, - iconBg: 'bg-blue-100', - iconColor: 'text-blue-600', - }, - { - label: 'نوبت‌های امروز', - value: '۱۴۸', - trend: '۲۳٪ نسبت به دیروز', - trendUp: true, - icon: CalendarDaysIcon, - iconBg: 'bg-orange-100', - iconColor: 'text-orange-600', - }, - { - label: 'پرداخت‌های امروز', - value: '۴۸,۲۰۰,۰۰۰', - trend: '۱۵٪ نسبت به دیروز', - trendUp: true, - icon: CreditCardIcon, - iconBg: 'bg-pink-100', - iconColor: 'text-pink-600', - }, - { - label: 'نظرات در انتظار', - value: '۱۲', - icon: ChatBubbleLeftEllipsisIcon, - iconBg: 'bg-yellow-100', - iconColor: 'text-yellow-600', - }, - { - label: 'درخواست تسویه', - value: '۵', - icon: BanknotesIcon, - iconBg: 'bg-teal-100', - iconColor: 'text-teal-600', - }, -]; - export default function DashboardPage() { + const { data, isLoading, isError, refetch } = useQuery({ + queryKey: ['dashboard-stats'], + queryFn: () => api.get>('/api/v1/admin/dashboard/stats'), + staleTime: 60_000, + }); + + const stats: DashboardStats | undefined = (data?.data as any)?.data ?? data?.data; + + const fn = (n: number | undefined) => (n !== undefined ? formatNumber(n) : '—'); + + const cards: StatCardProps[] = [ + { + label: 'کل کاربران', + value: fn(stats?.total_users), + icon: UserGroupIcon, + iconBg: 'bg-violet-100', + iconColor: 'text-violet-600', + }, + { + label: 'پزشکان فعال', + value: fn(stats?.active_doctors), + sub: stats ? `از ${formatNumber(stats.total_doctors)} پزشک` : undefined, + icon: HeartIcon, + iconBg: 'bg-emerald-100', + iconColor: 'text-emerald-600', + }, + { + label: 'کلینیک‌ها', + value: fn(stats?.total_clinics), + icon: BuildingOffice2Icon, + iconBg: 'bg-blue-100', + iconColor: 'text-blue-600', + }, + { + label: 'نوبت‌های امروز', + value: fn(stats?.today_appointments), + sub: stats ? `مجموع: ${formatNumber(stats.total_appointments)} نوبت` : undefined, + icon: CalendarDaysIcon, + iconBg: 'bg-orange-100', + iconColor: 'text-orange-600', + }, + { + label: 'درآمد امروز', + value: stats?.today_payments_amount !== undefined ? formatRial(stats.today_payments_amount) : '—', + sub: stats ? `${formatNumber(stats.today_payments_count)} تراکنش` : undefined, + icon: CreditCardIcon, + iconBg: 'bg-pink-100', + iconColor: 'text-pink-600', + }, + { + label: 'کل درآمد', + value: stats?.total_payments_amount !== undefined ? formatRial(stats.total_payments_amount) : '—', + icon: CreditCardIcon, + iconBg: 'bg-indigo-100', + iconColor: 'text-indigo-600', + }, + { + label: 'نظرات در انتظار', + value: fn(stats?.pending_comments), + icon: ChatBubbleLeftEllipsisIcon, + iconBg: 'bg-yellow-100', + iconColor: 'text-yellow-600', + }, + { + label: 'درخواست‌های تسویه', + value: fn(stats?.pending_settlements), + icon: BanknotesIcon, + iconBg: 'bg-teal-100', + iconColor: 'text-teal-600', + }, + ]; + return (
-
-

داشبورد

-

خلاصه وضعیت سیستم

+
+
+

داشبورد

+

خلاصه وضعیت سیستم

+
+ {isError && ( + + )}
+ {isError && ( +
+ خطا در دریافت اطلاعات — اطلاعات نمایش داده شده ممکن است به‌روز نباشند. +
+ )} +
- {stats.map((stat) => ( - + {cards.map((card) => ( + ))}
diff --git a/assets/admin/pages/DoctorsPage.tsx b/assets/admin/pages/DoctorsPage.tsx index 9275df12..7ed5e022 100644 --- a/assets/admin/pages/DoctorsPage.tsx +++ b/assets/admin/pages/DoctorsPage.tsx @@ -93,8 +93,8 @@ export default function DoctorsPage() { }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/PaymentsPage.tsx b/assets/admin/pages/PaymentsPage.tsx index 0a31eab4..1eb42f74 100644 --- a/assets/admin/pages/PaymentsPage.tsx +++ b/assets/admin/pages/PaymentsPage.tsx @@ -32,7 +32,7 @@ export default function PaymentsPage() { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); if (statusFilter) params.set('status', statusFilter); - return api.get>(`/api/v1/payments?${params}`); + return api.get>(`/api/v1/admin/payments?${params}`); }, }); @@ -78,8 +78,8 @@ export default function PaymentsPage() { }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/RatingsPage.tsx b/assets/admin/pages/RatingsPage.tsx index ae372be5..50850459 100644 --- a/assets/admin/pages/RatingsPage.tsx +++ b/assets/admin/pages/RatingsPage.tsx @@ -37,7 +37,7 @@ export default function RatingsPage() { queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); - return api.get>(`/api/v1/rates?${params}`); + return api.get>(`/api/v1/admin/rates?${params}`); }, }); @@ -61,8 +61,8 @@ export default function RatingsPage() { { key: 'created_at', header: 'تاریخ', render: (r) => formatDate(r.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/RepresentationDetailPage.tsx b/assets/admin/pages/RepresentationDetailPage.tsx new file mode 100644 index 00000000..88709991 --- /dev/null +++ b/assets/admin/pages/RepresentationDetailPage.tsx @@ -0,0 +1,269 @@ +import React, { useState } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { PencilIcon, TrashIcon, CheckCircleIcon, XCircleIcon } from '@heroicons/react/24/outline'; +import { toast } from 'sonner'; +import { api } from '../lib/api'; +import type { ApiResponse } from '../lib/api'; +import type { Representation } from '../types'; +import { formatDate, formatNumber } from '../lib/utils'; +import PageHeader from '../components/ui/PageHeader'; +import { ActiveBadge } from '../components/ui/StatusBadge'; +import ConfirmDialog from '../components/ui/ConfirmDialog'; +import Modal from '../components/ui/Modal'; + +function DetailRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value ?? '—'} +
+ ); +} + +export default function RepresentationDetailPage() { + const { uuid } = useParams<{ uuid: string }>(); + const navigate = useNavigate(); + const qc = useQueryClient(); + const [deleteOpen, setDeleteOpen] = useState(false); + const [editOpen, setEditOpen] = useState(false); + const [formData, setFormData] = useState({ full_name: '', city: '', mobile_number: '', commission_percent: '' }); + + const { data, isLoading } = useQuery({ + queryKey: ['representation', uuid], + queryFn: () => api.get>(`/api/v1/representation/${uuid}`), + enabled: !!uuid, + }); + + const rep: Representation | undefined = (data?.data as any)?.data ?? data?.data; + + const updateMutation = useMutation({ + mutationFn: (d: Partial) => + api.patch>(`/api/v1/representation/${uuid}`, d), + onSuccess: () => { + toast.success('اطلاعات بروزرسانی شد'); + setEditOpen(false); + qc.invalidateQueries({ queryKey: ['representation', uuid] }); + qc.invalidateQueries({ queryKey: ['representations'] }); + }, + onError: (err: Error) => toast.error(err.message), + }); + + const toggleActiveMutation = useMutation({ + mutationFn: () => + api.patch>(`/api/v1/representation/${uuid}`, { + active: !(rep?.active ?? rep?.is_active), + }), + onSuccess: () => { + toast.success('وضعیت تغییر کرد'); + qc.invalidateQueries({ queryKey: ['representation', uuid] }); + }, + onError: (err: Error) => toast.error(err.message), + }); + + const deleteMutation = useMutation({ + mutationFn: () => api.delete>(`/api/v1/representation/${uuid}`), + onSuccess: () => { + toast.success('نماینده حذف شد'); + navigate('/admin/representations'); + }, + onError: (err: Error) => toast.error(err.message), + }); + + const openEdit = () => { + if (!rep) return; + setFormData({ + full_name: rep.full_name ?? rep.domain ?? '', + city: rep.city ?? '', + mobile_number: rep.mobile_number ?? '', + commission_percent: String(rep.commission_percent), + }); + setEditOpen(true); + }; + + const isActive = rep?.active ?? rep?.is_active ?? false; + + if (isLoading) { + return ( +
+ +
+
+ {[1, 2, 3, 4, 5].map((i) => ( +
+ ))} +
+
+
+ ); + } + + if (!rep) { + return ( +
+ +
+ نماینده‌ای با این شناسه یافت نشد. +
+
+ ); + } + + const name = rep.full_name ?? rep.domain ?? '—'; + + return ( +
+ + + + +
+ } + /> + +
+ {/* Basic Info */} +
+

اطلاعات پایه

+ + {rep.mobile_number} + : null + } /> + + + } /> + +
+ + {/* Bank Account */} +
+

اطلاعات بانکی

+ {rep.bank_account ? ( + <> + {rep.bank_account.card} + : null + } /> + + {rep.bank_account.iban} + : null + } /> + + ) : ( +

اطلاعات بانکی ثبت نشده

+ )} +
+
+ + {/* Edit Modal */} + setEditOpen(false)} + footer={ + <> + + + + } + > +
+
+ + setFormData((p) => ({ ...p, full_name: e.target.value }))} + className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" /> +
+
+ + setFormData((p) => ({ ...p, city: e.target.value }))} + className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" /> +
+
+ + setFormData((p) => ({ ...p, mobile_number: e.target.value }))} + dir="ltr" className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" /> +
+
+ + setFormData((p) => ({ ...p, commission_percent: e.target.value }))} + type="number" min="0" max="100" dir="ltr" + className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" /> +
+
+
+ + deleteMutation.mutate()} + onCancel={() => setDeleteOpen(false)} + /> +
+ ); +} diff --git a/assets/admin/pages/RepresentationsPage.tsx b/assets/admin/pages/RepresentationsPage.tsx index d4f67555..a4e48e93 100644 --- a/assets/admin/pages/RepresentationsPage.tsx +++ b/assets/admin/pages/RepresentationsPage.tsx @@ -38,7 +38,7 @@ export default function RepresentationsPage() { queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); - return api.get>(`/api/v1/representations?${params}`); + return api.get>(`/api/v1/admin/representations?${params}`); }, }); @@ -79,14 +79,14 @@ export default function RepresentationsPage() { { key: 'wallet_balance', header: 'موجودی کیف‌پول', - render: (r) => formatRial(r.wallet_balance), + render: (r) => formatRial(r.wallet_balance ?? 0), }, - { key: 'is_active', header: 'وضعیت', render: (r) => }, + { key: 'is_active', header: 'وضعیت', render: (r) => }, { key: 'created_at', header: 'تاریخ ثبت', render: (r) => formatDate(r.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/SecretariesPage.tsx b/assets/admin/pages/SecretariesPage.tsx index 0f8bdb0c..e3d0bfeb 100644 --- a/assets/admin/pages/SecretariesPage.tsx +++ b/assets/admin/pages/SecretariesPage.tsx @@ -134,13 +134,13 @@ export default function SecretariesPage() { queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); - return api.get>(`/api/v1/secretaries?${params}`); + return api.get>(`/api/v1/admin/secretaries?${params}`); }, }); const updatePermsMutation = useMutation({ mutationFn: ({ uuid, permissions }: { uuid: string; permissions: SecretaryPermissions }) => - api.patch>(`/api/v1/secretary/${uuid}/permissions`, { permissions }), + api.patch>(`/api/v1/secretary/${uuid}`, { permissions }), onSuccess: () => { toast.success('دسترسی‌ها بروزرسانی شد'); setEditTarget(null); @@ -180,8 +180,8 @@ export default function SecretariesPage() { { key: 'created_at', header: 'تاریخ ثبت', render: (s) => formatDate(s.created_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/SettlementsPage.tsx b/assets/admin/pages/SettlementsPage.tsx index 40ad668f..a427ef78 100644 --- a/assets/admin/pages/SettlementsPage.tsx +++ b/assets/admin/pages/SettlementsPage.tsx @@ -36,13 +36,13 @@ export default function SettlementsPage() { queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (statusFilter) params.set('status', statusFilter); - return api.get>(`/api/v1/settlements?${params}`); + return api.get>(`/api/v1/admin/settlements?${params}`); }, }); const approveMutation = useMutation({ mutationFn: (s: Settlement) => - api.patch>(`/api/v1/settlement/${s.uuid}/approve`, {}), + api.post>(`/api/v1/settlement/${s.uuid}/approve`, {}), onSuccess: () => { toast.success('تسویه تأیید شد'); setApproveTarget(null); @@ -53,7 +53,7 @@ export default function SettlementsPage() { const rejectMutation = useMutation({ mutationFn: ({ s, reason }: { s: Settlement; reason: string }) => - api.patch>(`/api/v1/settlement/${s.uuid}/reject`, { reason }), + api.post>(`/api/v1/settlement/${s.uuid}/reject`, { reason }), onSuccess: () => { toast.success('تسویه رد شد'); setRejectTarget(null); @@ -72,8 +72,8 @@ export default function SettlementsPage() { { key: 'requested_at', header: 'تاریخ درخواست', render: (s) => formatDate(s.requested_at) }, ]; - const items = data?.data?.items ?? []; - const total = data?.data?.total ?? 0; + const items = data?.data ?? []; + const total = data?.meta?.totalRecords ?? 0; return (
diff --git a/assets/admin/pages/SmsPage.tsx b/assets/admin/pages/SmsPage.tsx index 90597a22..d522861b 100644 --- a/assets/admin/pages/SmsPage.tsx +++ b/assets/admin/pages/SmsPage.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { CheckIcon, XMarkIcon, TrashIcon, PlusIcon, PencilIcon } from '@heroicons/react/24/outline'; +import { CheckIcon, XMarkIcon, TrashIcon, PlusIcon } from '@heroicons/react/24/outline'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -18,8 +18,7 @@ import Modal from '../components/ui/Modal'; const templateSchema = z.object({ name: z.string().min(2, 'نام قالب الزامی است'), - category: z.string().min(1, 'دسته‌بندی الزامی است'), - content: z.string().min(5, 'متن قالب الزامی است'), + body: z.string().min(5, 'متن قالب الزامی است'), }); type TemplateFormData = z.infer; @@ -38,7 +37,9 @@ export default function SmsPage() { const sampleTemplatesQuery = useQuery({ queryKey: ['sms-samples', page], queryFn: () => - api.get>(`/api/v1/sms/sample-templates?page=${page}&limit=${limit}`), + api.get>( + `/api/v1/admin/sms/sample-templates?status=approved&page=${page}&limit=${limit}`, + ), enabled: activeTab === 'samples', }); @@ -46,7 +47,7 @@ export default function SmsPage() { queryKey: ['sms-pending', page], queryFn: () => api.get>( - `/api/v1/sms/templates?status=pending_approval&page=${page}&limit=${limit}`, + `/api/v1/admin/sms/sample-templates?status=pending&page=${page}&limit=${limit}`, ), enabled: activeTab === 'pending', }); @@ -54,7 +55,7 @@ export default function SmsPage() { const logsQuery = useQuery({ queryKey: ['sms-logs', page], queryFn: () => - api.get>(`/api/v1/sms/logs?page=${page}&limit=${limit}`), + api.get>(`/api/v1/admin/sms/logs?page=${page}&limit=${limit}`), enabled: activeTab === 'logs', }); @@ -64,7 +65,7 @@ export default function SmsPage() { const createMutation = useMutation({ mutationFn: (d: TemplateFormData) => - api.post>('/api/v1/sms/sample-template', d), + api.post>('/api/v1/sms/template', d), onSuccess: () => { toast.success('قالب اضافه شد'); setAddOpen(false); @@ -76,7 +77,7 @@ export default function SmsPage() { const approveMutation = useMutation({ mutationFn: (t: SmsTemplate) => - api.patch>(`/api/v1/sms/templates/${t.uuid}/approve`, {}), + api.post>(`/api/v1/admin/sms/template/${t.uuid}/approve`, {}), onSuccess: () => { toast.success('قالب تأیید شد'); qc.invalidateQueries({ queryKey: ['sms-pending'] }); @@ -86,7 +87,7 @@ export default function SmsPage() { const rejectMutation = useMutation({ mutationFn: ({ t, reason }: { t: SmsTemplate; reason: string }) => - api.patch>(`/api/v1/sms/templates/${t.uuid}/reject`, { reason }), + api.post>(`/api/v1/admin/sms/template/${t.uuid}/reject`, { reason }), onSuccess: () => { toast.success('قالب رد شد'); setRejectTarget(null); @@ -110,29 +111,23 @@ export default function SmsPage() { const templateColumns: Column[] = [ { key: 'name', header: 'نام', render: (t) => {t.name} }, { - key: 'category', - header: 'دسته‌بندی', - render: (t) => ( - {t.category} - ), - }, - { - key: 'content', + key: 'body', header: 'محتوا', - render: (t) => {t.content}, + render: (t) => {t.body}, }, { key: 'status', header: 'وضعیت', render: (t) => }, { key: 'created_at', header: 'تاریخ', render: (t) => formatDate(t.created_at) }, ]; - const pendingColumns: Column[] = [ - ...templateColumns.filter((c) => c.key !== 'status'), - { key: 'owner_name', header: 'ارسال‌کننده' }, - ]; + const pendingColumns: Column[] = templateColumns.filter((c) => c.key !== 'status'); const logColumns: Column[] = [ { key: 'recipient', header: 'گیرنده', render: (l) => {l.recipient} }, - { key: 'message', header: 'پیام', render: (l) => {l.message} }, + { + key: 'message', + header: 'پیام', + render: (l) => {l.message}, + }, { key: 'status', header: 'وضعیت', @@ -196,21 +191,19 @@ export default function SmsPage() { <> columns={templateColumns} - data={sampleTemplatesQuery.data?.data?.items ?? []} + data={sampleTemplatesQuery.data?.data ?? []} loading={sampleTemplatesQuery.isLoading} emptyMessage="هیچ قالبی یافت نشد" actions={(t) => ( - <> - - + )} /> @@ -221,7 +214,7 @@ export default function SmsPage() { <> columns={pendingColumns} - data={pendingTemplatesQuery.data?.data?.items ?? []} + data={pendingTemplatesQuery.data?.data ?? []} loading={pendingTemplatesQuery.isLoading} emptyMessage="هیچ قالبی در انتظار تأیید نیست" actions={(t) => ( @@ -239,7 +232,7 @@ export default function SmsPage() { /> @@ -250,13 +243,13 @@ export default function SmsPage() { <> columns={logColumns} - data={logsQuery.data?.data?.items ?? []} + data={logsQuery.data?.data ?? []} loading={logsQuery.isLoading} emptyMessage="لاگی یافت نشد" /> @@ -286,17 +279,11 @@ export default function SmsPage() { className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" /> {errors.name &&

{errors.name.message}

}
-
- - - {errors.category &&

{errors.category.message}

} -
-