feat(secretary): enhance secretary management with name input and toggle activation feature

This commit is contained in:
hamed
2026-06-15 12:23:07 +03:30
parent fa6656df48
commit 95f5918118
3 changed files with 113 additions and 31 deletions
+82 -28
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { PlusIcon, PencilIcon, TrashIcon, IdentificationIcon, PhoneIcon } from '@heroicons/react/24/outline';
import { PlusIcon, PencilIcon, NoSymbolIcon, CheckCircleIcon, IdentificationIcon, PhoneIcon } from '@heroicons/react/24/outline';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -132,7 +132,8 @@ function PermissionsMatrix({
// ── Create form schema ─────────────────────────────────────────────────────
const createSchema = z.object({
mobile_number: z.string().min(10, 'شماره موبایل معتبر نیست'),
name: z.string().min(2, 'نام الزامی است'),
mobile_number: z.string().regex(/^09[0-9]{9}$/, 'شماره موبایل معتبر نیست'),
});
type CreateForm = z.infer<typeof createSchema>;
@@ -182,6 +183,7 @@ export default function MySecretariesPage() {
const secretaries = isClinic
? (clinicSecrData?.data ?? [])
: (doctorSecrData?.data ?? []);
const activeSecretaryCount = secretaries.filter((s) => s.is_active).length;
const isLoading = isClinic ? clinicSecrLoading : doctorSecrLoading;
const createForm = useForm<CreateForm>({ resolver: zodResolver(createSchema) });
@@ -211,10 +213,11 @@ export default function MySecretariesPage() {
onError: (e: any) => toast.error(e.message),
});
const deleteMutation = useMutation({
mutationFn: (uuid: string) => api.delete(`/api/v1/secretary/${uuid}`),
onSuccess: () => {
toast.success('منشی غیرفعال شد');
const toggleActiveMutation = useMutation({
mutationFn: ({ uuid, active }: { uuid: string; active: boolean }) =>
api.patch(`/api/v1/secretary/${uuid}`, { active }),
onSuccess: (_, { active }) => {
toast.success(active ? 'منشی فعال شد' : 'منشی غیرفعال شد');
setDeleteTarget(null);
qc.invalidateQueries({ queryKey: ['my-secretaries'] });
qc.invalidateQueries({ queryKey: ['my-secretaries-clinic'] });
@@ -298,8 +301,15 @@ export default function MySecretariesPage() {
<button className="btn sm" onClick={() => openEdit(s)} title="ویرایش دسترسی‌ها">
<PencilIcon style={{ width: 14 }} />
</button>
<button className="btn sm" onClick={() => setDeleteTarget(s)} title="غیرفعال‌سازی">
<TrashIcon style={{ width: 14 }} />
<button
className="btn sm"
onClick={() => setDeleteTarget(s)}
title={s.is_active ? 'غیرفعال‌سازی' : 'فعال‌سازی'}
>
{s.is_active
? <NoSymbolIcon style={{ width: 14 }} />
: <CheckCircleIcon style={{ width: 14, color: 'var(--success)' }} />
}
</button>
</div>
),
@@ -312,7 +322,7 @@ export default function MySecretariesPage() {
title="منشیان من"
description="مدیریت منشیان و دسترسی‌های آن‌ها"
action={
secretaries.length >= maxSecretaries ? (
activeSecretaryCount >= maxSecretaries ? (
<Link to="/admin/subscription" className="btn sm" style={{ textDecoration: 'none', opacity: 0.8 }} title={`حداکثر ${maxSecretaries} منشی مجاز است`}>
ارتقاء پنل برای افزودن منشی
</Link>
@@ -378,33 +388,74 @@ export default function MySecretariesPage() {
)}
{/* Modal افزودن منشی */}
<Modal open={createOpen} onClose={() => setCreateOpen(false)} title="افزودن منشی جدید">
<Modal
open={createOpen}
onClose={() => { setCreateOpen(false); createForm.reset(); }}
title="افزودن منشی جدید"
size="sm"
footer={
<>
<button type="button" className="btn ghost sm" onClick={() => { setCreateOpen(false); createForm.reset(); }}>
انصراف
</button>
<button
form="create-secretary-form"
type="submit"
className="btn primary sm"
disabled={createMutation.isPending}
>
{createMutation.isPending ? 'در حال افزودن...' : 'افزودن منشی'}
</button>
</>
}
>
{isClinic && selectedDoctorName && (
<div style={{ marginBottom: 12, padding: '8px 12px', background: 'var(--primary-subtle)', borderRadius: 8, fontSize: 13, color: 'var(--primary)' }}>
منشی برای دکتر {selectedDoctorName} اضافه میشود
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '9px 12px', marginBottom: 20,
background: 'var(--primary-subtle)', borderRadius: 8,
fontSize: 13, color: 'var(--primary)',
}}>
<IdentificationIcon style={{ width: 16, flexShrink: 0 }} />
منشی برای <strong>{selectedDoctorName}</strong> ثبت میشود
</div>
)}
<form onSubmit={createForm.handleSubmit((d) => createMutation.mutate(d))}>
<form id="create-secretary-form" onSubmit={createForm.handleSubmit((d) => createMutation.mutate(d))}>
<div className="field">
<label>نام و نام خانوادگی *</label>
<input
{...createForm.register('name')}
placeholder="مثال: سارا احمدی"
autoComplete="off"
autoFocus
style={createForm.formState.errors.name ? { borderColor: 'var(--danger)' } : undefined}
/>
{createForm.formState.errors.name && (
<span className="field-error">{createForm.formState.errors.name.message}</span>
)}
</div>
<div className="field" style={{ marginTop: 16 }}>
<label>شماره موبایل *</label>
<input
{...createForm.register('mobile_number')}
placeholder="09123456789"
dir="ltr"
inputMode="numeric"
maxLength={11}
autoComplete="off"
onChange={(e) => {
const digits = e.target.value.replace(/\D/g, '');
createForm.setValue('mobile_number', digits, { shouldValidate: createForm.formState.isSubmitted });
}}
style={createForm.formState.errors.mobile_number ? { borderColor: 'var(--danger)' } : undefined}
/>
{createForm.formState.errors.mobile_number && (
<span className="field-error">{createForm.formState.errors.mobile_number.message}</span>
)}
</div>
<p style={{ fontSize: 12.5, color: 'var(--text-3)', margin: '8px 0 16px' }}>
کاربری با این شماره در سیستم جستجو شده و به عنوان منشی اضافه میشود.
<p style={{ fontSize: 12.5, color: 'var(--text-3)', margin: '4px 0 0', lineHeight: 1.7 }}>
شماره باید با ۰۹ شروع شده و ۱۱ رقم باشد. اگر کاربری با این شماره در سیستم وجود داشته باشد، به عنوان منشی اضافه میشود.
</p>
<div style={{ display: 'flex', gap: 8 }}>
<button type="submit" className="btn primary" disabled={createMutation.isPending}>
{createMutation.isPending ? 'در حال افزودن...' : 'افزودن'}
</button>
<button type="button" className="btn" onClick={() => setCreateOpen(false)}>انصراف</button>
</div>
</form>
</Modal>
@@ -430,15 +481,18 @@ export default function MySecretariesPage() {
<PermissionsMatrix permissions={editPerms} onChange={setEditPerms} />
</Modal>
{/* Confirm غیرفعال‌سازی */}
{/* Confirm تغییر وضعیت منشی */}
<ConfirmDialog
open={!!deleteTarget}
title="غیرفعال‌سازی منشی"
message={`آیا مطمئن هستید که می‌خواهید منشی «${deleteTarget?.user_name}» را غیرفعال کنید؟`}
confirmLabel="غیرفعال‌سازی"
danger
loading={deleteMutation.isPending}
onConfirm={() => deleteTarget && deleteMutation.mutate(deleteTarget.uuid)}
title={deleteTarget?.is_active ? 'غیرفعال‌سازی منشی' : 'فعال‌سازی منشی'}
message={deleteTarget?.is_active
? `آیا مطمئن هستید که می‌خواهید منشی «${deleteTarget?.user_name}» را غیرفعال کنید؟`
: `آیا مطمئن هستید که می‌خواهید منشی «${deleteTarget?.user_name}» را دوباره فعال کنید؟`
}
confirmLabel={deleteTarget?.is_active ? 'غیرفعال‌سازی' : 'فعال‌سازی'}
danger={deleteTarget?.is_active}
loading={toggleActiveMutation.isPending}
onConfirm={() => deleteTarget && toggleActiveMutation.mutate({ uuid: deleteTarget.uuid, active: !deleteTarget.is_active })}
onCancel={() => setDeleteTarget(null)}
/>
</>