feat: replace checkboxes with Switch component for better UI consistency

- Updated DoctorDetailPage, MySecretariesPage, RecordNumberSettingsPage, RepresentationsPage, ResourcePoolsPage, ResourceTypesPage, SecretariesPage, SecretaryDetailPage, SettingsPage, SkillsPage, SmsWalletPage, and TagsSettingsPage to use the new Switch component instead of native checkboxes.
- Enhanced accessibility by ensuring the Switch component uses appropriate roles and labels.
- Added tests for the new Switch component to ensure functionality and accessibility compliance.
- Updated styles to accommodate the new Switch component design.
This commit is contained in:
hamed
2026-08-05 16:09:46 +03:30
parent f9bbdf1e7c
commit 0e7970d6e0
38 changed files with 634 additions and 350 deletions
@@ -1,7 +1,11 @@
import React, { useEffect, useState } from 'react';
import { XMarkIcon } from '@heroicons/react/24/outline';
import Modal from '../ui/Modal';
import { useQuery } from '@tanstack/react-query';
import SearchableSelect from '../ui/SearchableSelect';
import Field from '../ui/Field';
import Input from '../ui/Input';
import Switch from '../ui/Switch';
import { api, type ApiResponse } from '../../lib/api';
import type { ClinicResource, ResourcePayload, ResourceType } from '../../types';
import { useResourceDetail } from '../../hooks/useResources';
@@ -98,8 +102,14 @@ export default function ResourceFormModal({
return (
<Modal open={open} onClose={onClose} title={isEdit ? 'ویرایش منبع' : 'افزودن منبع'} size="lg">
<div style={{ display: 'grid', gap: 14 }}>
<Field label="نام منبع">
<input className="field" value={name} onChange={(e) => setName(e.target.value)} placeholder="لیزر آلکساندرایت ۱" />
<Field label="نام منبع" htmlFor="resource-name">
<Input
id="resource-name"
value={name}
autoFocus
onChange={(e) => setName(e.target.value)}
placeholder="لیزر آلکساندرایت ۱"
/>
</Field>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 12 }}>
@@ -134,14 +144,17 @@ export default function ResourceFormModal({
)}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
<Field label="ظرفیت هم‌زمان">
<input className="field" type="number" min={1} value={capacity} onChange={(e) => setCapacity(e.target.value)} />
{/* `numeric` به‌جای `type="number"`: ورودیِ عددیِ فارسی در `type="number"`
نامعتبر است و مرورگر رشتهٔ خالی می‌دهد — یعنی «۳» تایپ‌شده به ۰ می‌رسید.
`Input numeric` ارقام را زنده به لاتین برمی‌گرداند. */}
<Field label="ظرفیت هم‌زمان" htmlFor="resource-capacity">
<Input id="resource-capacity" numeric value={capacity} onChange={(e) => setCapacity(e.target.value)} />
</Field>
<Field label="آماده‌سازی (دقیقه)">
<input className="field" type="number" min={0} value={setupMinutes} onChange={(e) => setSetupMinutes(e.target.value)} />
<Field label="آماده‌سازی (دقیقه)" htmlFor="resource-setup">
<Input id="resource-setup" numeric value={setupMinutes} onChange={(e) => setSetupMinutes(e.target.value)} />
</Field>
<Field label="تمیزکاری (دقیقه)">
<input className="field" type="number" min={0} value={cleanupMinutes} onChange={(e) => setCleanupMinutes(e.target.value)} />
<Field label="تمیزکاری (دقیقه)" htmlFor="resource-cleanup">
<Input id="resource-cleanup" numeric value={cleanupMinutes} onChange={(e) => setCleanupMinutes(e.target.value)} />
</Field>
</div>
@@ -152,7 +165,7 @@ export default function ResourceFormModal({
<div style={{ display: 'grid', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<label style={{ fontSize: 12, color: 'var(--text-2)' }}>ویژگیها (اختیاری)</label>
<span className="cp-label">ویژگیها (اختیاری)</span>
<button
type="button"
className="btn secondary sm"
@@ -164,32 +177,33 @@ export default function ResourceFormModal({
{attributes.map((row, index) => (
<div key={index} style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input
className="field"
<Input
list="resource-attribute-keys"
value={row.key}
placeholder="gender"
aria-label={`کلید ویژگی ${formatNumber(index + 1)}`}
onChange={(e) =>
setAttributes((a) => a.map((r, i) => (i === index ? { ...r, key: e.target.value } : r)))
}
style={{ flex: 1 }}
/>
<input
className="field"
<Input
value={row.value}
placeholder="female"
aria-label={`مقدار ویژگی ${formatNumber(index + 1)}`}
onChange={(e) =>
setAttributes((a) => a.map((r, i) => (i === index ? { ...r, value: e.target.value } : r)))
}
style={{ flex: 1 }}
/>
{/* دکمهٔ فقط-آیکون → `mini-btn`، نه `btn secondary sm` با یک ✕ متنی */}
<button
type="button"
className="btn secondary sm"
className="mini-btn danger"
onClick={() => setAttributes((a) => a.filter((_, i) => i !== index))}
aria-label="حذف ویژگی"
aria-label={`حذف ویژگی ${formatNumber(index + 1)}`}
>
<XMarkIcon style={{ width: 16 }} />
</button>
</div>
))}
@@ -199,10 +213,13 @@ export default function ResourceFormModal({
</datalist>
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
<input type="checkbox" checked={active} onChange={(e) => setActive(e.target.checked)} />
منبع فعال است
</label>
<Switch
id="resource-active"
checked={active}
onChange={setActive}
label="منبع فعال است"
hint="منبع غیرفعال در جستجوی وقت و رزرو نوبت نمی‌آید."
/>
{/* غیرفعال‌کردن نوبت‌های ثبت‌شده را لغو نمی‌کند؛ فقط از جستجوی وقتِ بعدی حذف
می‌شود. پس این هشدار است نه مانع — ولی اپراتور باید بداند چند بیمار روی
@@ -233,12 +250,3 @@ export default function ResourceFormModal({
</Modal>
);
}
function Field({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div style={{ display: 'grid', gap: 6 }}>
<label style={{ fontSize: 12, color: 'var(--text-2)' }}>{label}</label>
{children}
</div>
);
}