From 0e7970d6e091774389c4cae21c0ba8b051c7cf9e Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Wed, 5 Aug 2026 16:09:46 +0330 Subject: [PATCH] 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. --- .../admin/components/AppointmentActions.tsx | 25 ++-- .../components/AppointmentFiltersModal.tsx | 11 +- assets/admin/components/DiscountTab.tsx | 9 +- assets/admin/components/FreeVisitPrice.tsx | 26 ++-- .../InsuranceServiceCategoriesCard.tsx | 23 ++-- .../admin/components/NewAppointmentDrawer.tsx | 11 +- .../admin/components/PatientsFilterModal.tsx | 12 +- .../components/ServiceInsuranceModal.tsx | 16 ++- .../admin/components/ServiceItemFormModal.tsx | 20 ++- .../paymentMethods/StatusToggle.tsx | 14 +-- .../resources/ResourceFormModal.tsx | 66 +++++----- .../resources/ResourceServicesPanel.tsx | 16 +-- .../resources/ResourceWorkingHoursPanel.tsx | 14 ++- .../components/schedule/ScheduleSection.tsx | 20 ++- .../components/ui/DoctorPermissionsModal.tsx | 33 ++--- assets/admin/components/ui/Switch.test.tsx | 63 ++++++++++ assets/admin/components/ui/Switch.tsx | 87 +++++++++++++ assets/admin/pages/AdminSubscriptionPage.tsx | 7 ++ assets/admin/pages/AppointmentCreatePage.tsx | 21 ++-- assets/admin/pages/AppointmentEditPage.tsx | 11 +- assets/admin/pages/CatalogCategoriesPage.tsx | 6 +- assets/admin/pages/ClinicDetailPage.tsx | 18 +-- assets/admin/pages/ClinicServicesPage.tsx | 10 +- assets/admin/pages/DoctorDetailPage.tsx | 15 ++- assets/admin/pages/MySecretariesPage.tsx | 18 +-- .../admin/pages/RecordNumberSettingsPage.tsx | 31 ++--- assets/admin/pages/RepresentationsPage.tsx | 13 +- assets/admin/pages/ResourcePoolsPage.tsx | 2 +- assets/admin/pages/ResourceTypesPage.test.tsx | 115 ++++++++++++++++++ assets/admin/pages/ResourceTypesPage.tsx | 110 +++++++++++------ assets/admin/pages/SecretariesPage.tsx | 16 +-- assets/admin/pages/SecretaryDetailPage.tsx | 15 +-- assets/admin/pages/SettingsPage.tsx | 8 +- assets/admin/pages/SkillsPage.tsx | 62 ++++++---- assets/admin/pages/SmsWalletPage.tsx | 23 ++-- assets/admin/pages/TagsSettingsPage.test.tsx | 3 +- assets/admin/pages/TagsSettingsPage.tsx | 10 +- assets/admin/styles.css | 4 +- 38 files changed, 634 insertions(+), 350 deletions(-) create mode 100644 assets/admin/components/ui/Switch.test.tsx create mode 100644 assets/admin/components/ui/Switch.tsx create mode 100644 assets/admin/pages/ResourceTypesPage.test.tsx diff --git a/assets/admin/components/AppointmentActions.tsx b/assets/admin/components/AppointmentActions.tsx index 41f5a570..e8c7deb4 100644 --- a/assets/admin/components/AppointmentActions.tsx +++ b/assets/admin/components/AppointmentActions.tsx @@ -30,6 +30,7 @@ import SearchableSelect from "./ui/SearchableSelect"; import ServiceSlotPicker from "./appointments/ServiceSlotPicker"; import type { PickedService, ServicePick } from "./appointments/ServiceSlotPicker"; import { useDoctorBookingServices } from "../hooks/useDoctorBookingServices"; +import Switch from './ui/Switch'; /** Row actions for the appointments table (Figma عملیات menu). */ type ModalKind = null | "info" | "move" | "transfer" | "replace"; @@ -1009,24 +1010,12 @@ export function ReplaceAppointmentModal({ marginBottom: 12, }} > - + {depositRequired && ( )} diff --git a/assets/admin/components/AppointmentFiltersModal.tsx b/assets/admin/components/AppointmentFiltersModal.tsx index 5354484f..89bffbd6 100644 --- a/assets/admin/components/AppointmentFiltersModal.tsx +++ b/assets/admin/components/AppointmentFiltersModal.tsx @@ -7,6 +7,7 @@ import type { Appointment } from '../types'; import Modal from './ui/Modal'; import SearchableSelect from './ui/SearchableSelect'; import { digitsOnly } from '../lib/utils'; +import Switch from './ui/Switch'; interface Option { uuid: string; name?: string } @@ -120,9 +121,13 @@ export default function AppointmentFiltersModal({ value, onApply, onClose }: {
وضعیت نوبت
{STATUS_OPTIONS.map(([v, l]) => ( - + toggleStatus(v)} + label={l} + /> ))}
diff --git a/assets/admin/components/DiscountTab.tsx b/assets/admin/components/DiscountTab.tsx index d5f7ad18..e9124dc9 100644 --- a/assets/admin/components/DiscountTab.tsx +++ b/assets/admin/components/DiscountTab.tsx @@ -13,6 +13,7 @@ import PriceInput from './ui/PriceInput'; import PersianDateInput from './ui/PersianDateInput'; import { digitsOnly } from '../lib/utils'; import { usePermissions } from '../hooks/usePermissions'; +import Switch from './ui/Switch'; const TYPE_LABELS: Record = { patient_tag: 'تگ بیمار', @@ -333,12 +334,8 @@ function RuleModal({ initial, onClose, onSaved }: { initial: DiscountRule | null
- - + set('combinable', v)} label="قابل ترکیب با سایر تخفیف‌ها" /> + set('active', v)} label="فعال" />
diff --git a/assets/admin/components/FreeVisitPrice.tsx b/assets/admin/components/FreeVisitPrice.tsx index 94262e80..e22df190 100644 --- a/assets/admin/components/FreeVisitPrice.tsx +++ b/assets/admin/components/FreeVisitPrice.tsx @@ -4,6 +4,7 @@ import { toast } from 'sonner'; import { api } from '../lib/api'; import { formatRial, rialToToman, tomanToRial } from '../lib/utils'; import { digitsOnly } from '../lib/utils'; +import Switch from './ui/Switch'; interface Pricing { free_visit_price_rials: number; require_visit_price: boolean } @@ -76,23 +77,14 @@ export default function FreeVisitPrice({ doctorUuid, readOnly = false }: { docto

{error}

)} - +
+ { setRequired(v); setError(''); }} + label="الزامی کردن هزینه ویزیت" + /> +

با فعال شدن این گزینه، وارد کردن هزینه ویزیت در تنظیمات، ثبت مراجعه (سرویس)، فاکتور سرویس و ثبت نوبت الزامی می‌شود و بدون آن امکان ذخیره وجود ندارد.

diff --git a/assets/admin/components/InsuranceServiceCategoriesCard.tsx b/assets/admin/components/InsuranceServiceCategoriesCard.tsx index e83ef2c3..e3697679 100644 --- a/assets/admin/components/InsuranceServiceCategoriesCard.tsx +++ b/assets/admin/components/InsuranceServiceCategoriesCard.tsx @@ -4,6 +4,7 @@ import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import { usePermissions } from '../hooks/usePermissions'; +import Switch from './ui/Switch'; interface ServiceCategoryRow { key: string; @@ -70,22 +71,14 @@ export default function InsuranceServiceCategoriesCard() {
{rows.map((row) => ( - + inline + checked={row.enabled} + disabled={!canUpdate || save.isPending} + onChange={() => toggle(row.key)} + label={row.label} + /> ))}
diff --git a/assets/admin/components/NewAppointmentDrawer.tsx b/assets/admin/components/NewAppointmentDrawer.tsx index 5744ef38..66ca01a0 100644 --- a/assets/admin/components/NewAppointmentDrawer.tsx +++ b/assets/admin/components/NewAppointmentDrawer.tsx @@ -11,6 +11,7 @@ import PriceInput from './ui/PriceInput'; import SearchableSelect from './ui/SearchableSelect'; import { WalletChargeLink } from './AppointmentActions'; import { tehranWallClockToUnix, tomanToRial, rialToToman, digitsOnly, sanitizeMobileInput } from '../lib/utils'; +import Switch from './ui/Switch'; interface Option { uuid: string; name?: string; full_name?: string } interface PatientRow { uuid: string; user_name?: string; user_mobile?: string; user_national_code?: string } @@ -342,10 +343,12 @@ export default function NewAppointmentDrawer({ doctorUuid, defaultDate, queryKey <>
بیعانه:
- +
{depositRequired && (
diff --git a/assets/admin/components/PatientsFilterModal.tsx b/assets/admin/components/PatientsFilterModal.tsx index 4af8c5b1..2543fc69 100644 --- a/assets/admin/components/PatientsFilterModal.tsx +++ b/assets/admin/components/PatientsFilterModal.tsx @@ -5,6 +5,7 @@ import type { ApiResponse } from '../lib/api'; import Modal from './ui/Modal'; import PersianDateInput from './ui/PersianDateInput'; import SearchableSelect from './ui/SearchableSelect'; +import Switch from './ui/Switch'; export interface PatientFilters { gender?: string; // male | female @@ -111,11 +112,12 @@ export default function PatientsFilterModal({ open, onClose, value, onApply }: {
- + set('has_debt', v)} + label="فقط پرونده‌های دارای بدهی" + />
diff --git a/assets/admin/components/ServiceInsuranceModal.tsx b/assets/admin/components/ServiceInsuranceModal.tsx index 9430a3a1..c3659d74 100644 --- a/assets/admin/components/ServiceInsuranceModal.tsx +++ b/assets/admin/components/ServiceInsuranceModal.tsx @@ -7,6 +7,7 @@ import { digitsOnly, parseUserNumberClamped } from '../lib/utils'; import Modal from './ui/Modal'; import PriceInput from './ui/PriceInput'; import type { ServiceItem } from '../types'; +import Switch from './ui/Switch'; interface TenantInsurance { uuid: string; @@ -111,15 +112,12 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser پوشش پیش‌فرض قرارداد: {contract.coverage_percent}٪
- + setDraft((d) => ({ ...d, covered: v }))} + ariaLabel="پوشش بیمه برای این سرویس" + /> {/* بدنه — فقط وقتی پوشش فعال است */} diff --git a/assets/admin/components/ServiceItemFormModal.tsx b/assets/admin/components/ServiceItemFormModal.tsx index 9055daaa..1cb18bf5 100644 --- a/assets/admin/components/ServiceItemFormModal.tsx +++ b/assets/admin/components/ServiceItemFormModal.tsx @@ -15,6 +15,7 @@ import { useServiceCategories } from '../hooks/useServiceCategories'; import Modal from './ui/Modal'; import PriceInput from './ui/PriceInput'; import SearchableSelect from './ui/SearchableSelect'; +import Switch from './ui/Switch'; const itemSchema = z.object({ name: z.string().min(1, 'نام سرویس الزامی است'), @@ -238,17 +239,14 @@ export default function ServiceItemFormModal({ item, sectionUuid, onClose, onMan - +
+ form.setValue('bookable', v, { shouldDirty: true })} + label="نمایش در نوبت‌دهی" + /> +
diff --git a/assets/admin/components/paymentMethods/StatusToggle.tsx b/assets/admin/components/paymentMethods/StatusToggle.tsx index 585a2e59..37484ab3 100644 --- a/assets/admin/components/paymentMethods/StatusToggle.tsx +++ b/assets/admin/components/paymentMethods/StatusToggle.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Switch from '../ui/Switch'; /** * سوییچ وضعیت فعال/غیرفعال — معادل MUI Switch مبدأ با دیزاین‌سیستم مقصد. @@ -16,16 +17,9 @@ export default function StatusToggle({ const label = active ? 'فعال' : 'غیرفعال'; return (
- + + + {label}
); diff --git a/assets/admin/components/resources/ResourceFormModal.tsx b/assets/admin/components/resources/ResourceFormModal.tsx index 51e8c43c..4e5657e3 100644 --- a/assets/admin/components/resources/ResourceFormModal.tsx +++ b/assets/admin/components/resources/ResourceFormModal.tsx @@ -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 (
- - setName(e.target.value)} placeholder="لیزر آلکساندرایت ۱" /> + + setName(e.target.value)} + placeholder="لیزر آلکساندرایت ۱" + />
@@ -134,14 +144,17 @@ export default function ResourceFormModal({ )}
- - setCapacity(e.target.value)} /> + {/* `numeric` به‌جای `type="number"`: ورودیِ عددیِ فارسی در `type="number"` + نامعتبر است و مرورگر رشتهٔ خالی می‌دهد — یعنی «۳» تایپ‌شده به ۰ می‌رسید. + `Input numeric` ارقام را زنده به لاتین برمی‌گرداند. */} + + setCapacity(e.target.value)} /> - - setSetupMinutes(e.target.value)} /> + + setSetupMinutes(e.target.value)} /> - - setCleanupMinutes(e.target.value)} /> + + setCleanupMinutes(e.target.value)} />
@@ -152,7 +165,7 @@ export default function ResourceFormModal({
- + ویژگی‌ها (اختیاری)
))} @@ -199,10 +213,13 @@ export default function ResourceFormModal({
- + {/* غیرفعال‌کردن نوبت‌های ثبت‌شده را لغو نمی‌کند؛ فقط از جستجوی وقتِ بعدی حذف می‌شود. پس این هشدار است نه مانع — ولی اپراتور باید بداند چند بیمار روی @@ -233,12 +250,3 @@ export default function ResourceFormModal({ ); } - -function Field({ label, children }: { label: string; children: React.ReactNode }) { - return ( -
- - {children} -
- ); -} diff --git a/assets/admin/components/resources/ResourceServicesPanel.tsx b/assets/admin/components/resources/ResourceServicesPanel.tsx index 1eceba51..ebbc0f0b 100644 --- a/assets/admin/components/resources/ResourceServicesPanel.tsx +++ b/assets/admin/components/resources/ResourceServicesPanel.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import SearchableSelect from '../ui/SearchableSelect'; +import Switch from '../ui/Switch'; import { formatRial } from '../../lib/utils'; import type { ClinicResource, ResourceServiceOffering, ServiceItemOption } from '../../types'; @@ -106,14 +107,13 @@ export default function ResourceServicesPanel({
{line.service_name} - + patch(index, { active: v })} + label="فعال" + ariaLabel={`فعال بودن سرویس ${line.service_name}`} + />