Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { TrashIcon, PencilIcon } from '@heroicons/react/24/outline';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TrashIcon, PencilIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '../lib/api';
|
||||
import type { ApiResponse, PaginatedResponse } from '../lib/api';
|
||||
import type { Secretary, SecretaryPermissions } from '../types';
|
||||
import { formatDate, maskMobile } from '../lib/utils';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import DataTable, { Column } from '../components/ui/DataTable';
|
||||
import { ActiveBadge } from '../components/ui/StatusBadge';
|
||||
import Pagination from '../components/ui/Pagination';
|
||||
@@ -22,7 +20,6 @@ const DEFAULT_PERMISSIONS: SecretaryPermissions = {
|
||||
};
|
||||
|
||||
type PermSection = keyof SecretaryPermissions;
|
||||
type PermAction = string;
|
||||
|
||||
const PERMISSION_LABELS: Record<PermSection, { label: string; actions: { key: string; label: string }[] }> = {
|
||||
appointments: {
|
||||
@@ -76,14 +73,17 @@ function PermissionsMatrix({
|
||||
});
|
||||
};
|
||||
|
||||
const allActions = ['view', 'create', 'update', 'delete', 'cancel', 'update_status'];
|
||||
const actionHeaders = ['مشاهده', 'ایجاد', 'ویرایش', 'حذف', 'لغو', 'تغییر وضعیت'];
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table className="t">
|
||||
<thead>
|
||||
<tr className="text-right">
|
||||
<th className="pb-2 text-gray-500 font-medium">بخش</th>
|
||||
{['مشاهده', 'ایجاد', 'ویرایش', 'حذف', 'لغو', 'تغییر وضعیت'].map((h) => (
|
||||
<th key={h} className="pb-2 text-gray-500 font-medium text-center text-xs">{h}</th>
|
||||
<tr>
|
||||
<th>بخش</th>
|
||||
{actionHeaders.map((h) => (
|
||||
<th key={h} style={{ textAlign: 'center', fontSize: 12 }}>{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -91,22 +91,21 @@ function PermissionsMatrix({
|
||||
{(Object.keys(PERMISSION_LABELS) as PermSection[]).map((section) => {
|
||||
const config = PERMISSION_LABELS[section];
|
||||
const sectionPerms = permissions[section] as Record<string, boolean>;
|
||||
const allActions = ['view', 'create', 'update', 'delete', 'cancel', 'update_status'];
|
||||
return (
|
||||
<tr key={section} className="border-t border-gray-100">
|
||||
<td className="py-3 pr-0 text-slate-700 dark:text-slate-300 font-medium">{config.label}</td>
|
||||
<tr key={section}>
|
||||
<td><b>{config.label}</b></td>
|
||||
{allActions.map((action) => {
|
||||
const actionConfig = config.actions.find((a) => a.key === action);
|
||||
if (!actionConfig) {
|
||||
return <td key={action} className="text-center text-gray-200">—</td>;
|
||||
return <td key={action} style={{ textAlign: 'center', color: 'var(--border)' }}>—</td>;
|
||||
}
|
||||
return (
|
||||
<td key={action} className="text-center py-3">
|
||||
<td key={action} style={{ textAlign: 'center' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sectionPerms[action] ?? false}
|
||||
onChange={() => toggle(section, action)}
|
||||
className="w-4 h-4 accent-primary-600 cursor-pointer"
|
||||
style={{ width: 16, height: 16, accentColor: 'var(--primary)', cursor: 'pointer' }}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
@@ -165,16 +164,8 @@ export default function SecretariesPage() {
|
||||
};
|
||||
|
||||
const columns: Column<Secretary>[] = [
|
||||
{
|
||||
key: 'user_name',
|
||||
header: 'نام',
|
||||
render: (s) => <span className="font-medium text-slate-800 dark:text-slate-100">{s.user_name}</span>,
|
||||
},
|
||||
{
|
||||
key: 'mobile_number',
|
||||
header: 'موبایل',
|
||||
render: (s) => <span dir="ltr">{maskMobile(s.mobile_number)}</span>,
|
||||
},
|
||||
{ key: 'user_name', header: 'نام', render: (s) => <b>{s.user_name}</b> },
|
||||
{ key: 'mobile_number', header: 'موبایل', render: (s) => <span dir="ltr">{maskMobile(s.mobile_number)}</span> },
|
||||
{ key: 'doctor_name', header: 'پزشک', render: (s) => `دکتر ${s.doctor_name}` },
|
||||
{ key: 'is_active', header: 'وضعیت', render: (s) => <ActiveBadge active={s.is_active} /> },
|
||||
{ key: 'created_at', header: 'تاریخ ثبت', render: (s) => formatDate(s.created_at) },
|
||||
@@ -184,30 +175,39 @@ export default function SecretariesPage() {
|
||||
const total = data?.meta?.totalRecords ?? 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="منشیها"
|
||||
breadcrumbs={[{ label: 'داشبورد', to: '/admin/dashboard' }, { label: 'منشیها' }]}
|
||||
/>
|
||||
<div className="fade-in">
|
||||
<div className="card-title-row" style={{ marginBottom: 'var(--gap)' }}>
|
||||
<div>
|
||||
<h1 className="section-title">منشیها</h1>
|
||||
<div className="muted">{total} منشی ثبتشده</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="cp-card p-6">
|
||||
<div className="card">
|
||||
<div className="card-pad" style={{ paddingBottom: 0 }}>
|
||||
<div className="toolbar">
|
||||
<div className="field" style={{ minWidth: 240 }}>
|
||||
<MagnifyingGlassIcon style={{ width: 17, height: 17 }} />
|
||||
<input
|
||||
value={search}
|
||||
onChange={(e) => { setSearch(e.target.value); setPage(1); }}
|
||||
placeholder="جستجو بر اساس نام یا پزشک..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable<Secretary>
|
||||
columns={columns}
|
||||
data={items}
|
||||
loading={isLoading}
|
||||
searchValue={search}
|
||||
onSearchChange={(v) => { setSearch(v); setPage(1); }}
|
||||
searchPlaceholder="جستجو بر اساس نام یا پزشک..."
|
||||
emptyMessage="هیچ منشیای یافت نشد"
|
||||
actions={(sec) => (
|
||||
<>
|
||||
<button onClick={() => openEdit(sec)}
|
||||
className="cp-action-edit" title="ویرایش دسترسیها">
|
||||
<PencilIcon className="w-4 h-4" />
|
||||
<button onClick={() => openEdit(sec)} className="mini-btn" title="ویرایش دسترسیها">
|
||||
<PencilIcon style={{ width: 15, height: 15 }} />
|
||||
</button>
|
||||
<button onClick={() => setDeleteTarget(sec)}
|
||||
className="cp-action-delete" title="حذف">
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<button onClick={() => setDeleteTarget(sec)} className="mini-btn danger" title="حذف">
|
||||
<TrashIcon style={{ width: 15, height: 15 }} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -222,14 +222,11 @@ export default function SecretariesPage() {
|
||||
onClose={() => setEditTarget(null)}
|
||||
footer={
|
||||
<>
|
||||
<button onClick={() => setEditTarget(null)}
|
||||
className="cp-btn-secondary">
|
||||
لغو
|
||||
</button>
|
||||
<button onClick={() => setEditTarget(null)} className="btn ghost sm">لغو</button>
|
||||
<button
|
||||
onClick={() => editTarget && updatePermsMutation.mutate({ uuid: editTarget.uuid, permissions: editPerms })}
|
||||
disabled={updatePermsMutation.isPending}
|
||||
className="cp-btn-primary">
|
||||
className="btn primary sm">
|
||||
{updatePermsMutation.isPending ? 'در حال ذخیره...' : 'ذخیره دسترسیها'}
|
||||
</button>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user