From 635bf3d2a85d2142d99f0b460face92f0494d2e0 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 31 Jul 2026 21:42:11 +0330 Subject: [PATCH] fix(admin): correct two design-system mismatches found by looking at the pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshotting the pages under dark mode and compact density (rather than trusting that design tokens were enough) turned up two mistakes repeated across every page this feature set added: - `.card` carries only the surface, border and radius — padding comes from the separate `.card-pad`. Fifteen cards were rendering with their content flush against the edges. - `.field` *is* the input box, a 40px-tall flex row. Wrapping a label plus a control in it produced a joined addon rather than a label above its field. `.field-block` is the label-above layout, and thirty-seven wrappers now use it. Both were invisible to type-checking and to the tests, which is exactly why the visual pass was worth running. Numbers in the new UI now go through formatNumber so they render as Persian digits, and the utilization page's header no longer repeats the sentence that appears under its filters verbatim. The QA driver gained a `--ui` flag: theme and density live in localStorage['clinicpro-ui'], so without seeding them dark mode and compact density cannot be screenshotted at all. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/qa-clinicpro/driver.mjs | 12 +++++++++- .../AppointmentSegmentsCard.test.tsx | 3 ++- .../components/AppointmentSegmentsCard.tsx | 7 +++--- .../admin/components/ResourceBlocksModal.tsx | 4 ++-- assets/admin/components/ServiceGroupsTab.tsx | 10 ++++---- .../admin/components/ServiceSegmentsTab.tsx | 6 ++--- assets/admin/pages/CancellationPolicyPage.tsx | 12 +++++----- assets/admin/pages/CourseProtocolsPage.tsx | 6 ++--- assets/admin/pages/PackagesPage.tsx | 10 ++++---- assets/admin/pages/PlanAccuracyPage.tsx | 4 ++-- assets/admin/pages/PolicyFormPage.tsx | 12 +++++----- assets/admin/pages/PriceListsPage.tsx | 8 +++---- assets/admin/pages/ResourceBookingPage.tsx | 24 +++++++++---------- assets/admin/pages/ResourceTypesPage.tsx | 2 +- .../admin/pages/ResourceUtilizationPage.tsx | 8 +++---- assets/admin/pages/TreatmentCoursePage.tsx | 6 ++--- .../taskes/task-07-hold-and-book/checklist.md | 4 ++-- .../checklist.md | 6 ++--- .../task-14-events-utilization/checklist.md | 16 ++++++------- 19 files changed, 86 insertions(+), 74 deletions(-) diff --git a/.claude/skills/qa-clinicpro/driver.mjs b/.claude/skills/qa-clinicpro/driver.mjs index cfacc455..5c870e92 100644 --- a/.claude/skills/qa-clinicpro/driver.mjs +++ b/.claude/skills/qa-clinicpro/driver.mjs @@ -227,9 +227,16 @@ async function withPage(url, opts, fn) { state: { token: access_token, refreshToken: refresh_token, isAuthenticated: true }, version: 0, }; + // `--ui '{"darkMode":true,"density":"compact"}'` تم را پیش از اولین رندر می‌کارد. + const ui = opts.ui + ? `localStorage.setItem('clinicpro-ui', ${JSON.stringify( + JSON.stringify({ state: JSON.parse(opts.ui), version: 0 }), + )});` + : ''; + await S('Runtime.evaluate', { expression: `localStorage.setItem('clinicpro-auth', ${JSON.stringify(JSON.stringify(auth))}); - localStorage.setItem('pwa-dismissed','1');`, + localStorage.setItem('pwa-dismissed','1');${ui}`, }); // Errors before this point belong to the login page, not the page under test. @@ -458,6 +465,9 @@ const opts = { wait: Number(flag('wait', 4000)), full: argv.includes('--full'), body: flag('body', null), + // تم و چگالی در `localStorage['clinicpro-ui']` می‌نشینند و بدون seed کردنشان + // دارک‌مود و حالت فشرده اصلاً قابل اسکرین‌شات نیستند. + ui: flag('ui', null), }; try { diff --git a/assets/admin/components/AppointmentSegmentsCard.test.tsx b/assets/admin/components/AppointmentSegmentsCard.test.tsx index 15d0cf5a..38a90dc6 100644 --- a/assets/admin/components/AppointmentSegmentsCard.test.tsx +++ b/assets/admin/components/AppointmentSegmentsCard.test.tsx @@ -37,7 +37,8 @@ describe('AppointmentSegmentsCard', () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('60 دقیقه')).toBeInTheDocument()); + // ارقام فارسی‌اند: ۲۰ + ۴۰ = ۶۰ + await waitFor(() => expect(screen.getByText('۶۰ دقیقه')).toBeInTheDocument()); }); /** ⭐ مدتی که بیمار روی صندلی نیست باید دیده شود، وگرنه «۹۰ دقیقه» گمراه‌کننده است. */ diff --git a/assets/admin/components/AppointmentSegmentsCard.tsx b/assets/admin/components/AppointmentSegmentsCard.tsx index e2dacf02..d9b793aa 100644 --- a/assets/admin/components/AppointmentSegmentsCard.tsx +++ b/assets/admin/components/AppointmentSegmentsCard.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { useAppointmentSegments } from '../hooks/useResourceBooking'; +import { formatNumber } from '../lib/utils'; const timeOf = (ts: number) => new Date(ts * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }); @@ -23,14 +24,14 @@ export default function AppointmentSegmentsCard({ appointmentUuid }: { appointme

بخش‌های نوبت

- {total} دقیقه + {formatNumber(total)} دقیقه
{segments.map((s) => (
{segments.map((s) => (
  • - {s.sequence} + {formatNumber(s.sequence)} {s.name} {timeOf(s.starts_at)} – {timeOf(s.ends_at)} diff --git a/assets/admin/components/ResourceBlocksModal.tsx b/assets/admin/components/ResourceBlocksModal.tsx index 0b930e8f..cf48301e 100644 --- a/assets/admin/components/ResourceBlocksModal.tsx +++ b/assets/admin/components/ResourceBlocksModal.tsx @@ -89,7 +89,7 @@ export default function ResourceBlocksModal({ resourceUuid, resourceName, onClos

    -
    +
    @@ -121,7 +121,7 @@ export default function ResourceBlocksModal({ resourceUuid, resourceName, onClos
    -
    +
    {/* ── گروه‌ها ───────────────────────────────────────────────────────── */} -
    +

    گروه‌های انتخاب

    @@ -71,7 +71,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation {canEdit && (
    -
    +
    -
    +
    {/* ── روابط ─────────────────────────────────────────────────────────── */} -
    +

    ناسازگاری و پیش‌نیاز

    @@ -299,7 +299,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
    {/* ── پیش‌نمایش زنده ────────────────────────────────────────────────── */} -
    +

    پیش‌نمایش انتخاب

    diff --git a/assets/admin/components/ServiceSegmentsTab.tsx b/assets/admin/components/ServiceSegmentsTab.tsx index a9189fd4..4c6ecc67 100644 --- a/assets/admin/components/ServiceSegmentsTab.tsx +++ b/assets/admin/components/ServiceSegmentsTab.tsx @@ -141,7 +141,7 @@ export default function ServiceSegmentsTab({ serviceUuid, canEdit }: Props) { return (
    -
    +

    بخش‌های نوبت

    @@ -378,9 +378,9 @@ export default function ServiceSegmentsTab({ serviceUuid, canEdit }: Props) {
    {/* ── نوار پیش‌نمایش زمانی ──────────────────────────────────────────── */} -
    +
    -
    +
    -
    +
    {loading && در حال بارگذاری…} -
    +
    -
    +
    {mode === 'percent' && ( -
    +
    +
    @@ -123,7 +123,7 @@ export default function CancellationPolicyPage() { اعتبار پکیج پس از لغو برمی‌گردد -
    +
    {!draft.uuid && ( -
    +
    )} -
    +
    ( -
    +
    {draft && (
    -
    +
    -
    +
    -
    +
    -
    +
    -
    +
    -
    -
    +
    +
    setDays(String(v ?? '30'))} options={RANGES} />
    diff --git a/assets/admin/pages/PolicyFormPage.tsx b/assets/admin/pages/PolicyFormPage.tsx index fda095ca..072271d8 100644 --- a/assets/admin/pages/PolicyFormPage.tsx +++ b/assets/admin/pages/PolicyFormPage.tsx @@ -72,7 +72,7 @@ export default function PolicyFormPage() { backTo="/admin/policies" /> -
    +
    -
    +
    -
    +
    -
    +
    {template?.inputs.map((input) => ( -
    +
    ) : ( <> -
    +
    {draft && (
    -
    +
    -
    +
    -
    +
    setDraft({ ...draft, valid_from: isoToUnix(iso) ?? draft.valid_from })} />
    -
    +
    -
    -
    +
    +
    -
    +
    -
    +
    {error && ( -
    +
    {error instanceof ApiError ? error.message : 'جستجوی وقت ناموفق بود'}
    )} {result && ( -
    +
    - مدت نوبت: {result.plan.total_minutes} دقیقه + مدت نوبت: {formatNumber(result.plan.total_minutes)} دقیقه - {result.plan.segments.length} بخش · {result.slots.length} وقت پیدا شد + {formatNumber(result.plan.segments.length)} بخش · {formatNumber(result.slots.length)} وقت پیدا شد
    @@ -215,7 +215,7 @@ export default function ResourceBookingPage() { )} {result && result.slots.length > 0 && ( -
    +
    @@ -262,7 +262,7 @@ export default function ResourceBookingPage() { )} {pickedSlot && ( -
    +

    {formatDate(pickedSlot.start)} · {timeOf(pickedSlot.start)} @@ -301,7 +301,7 @@ export default function ResourceBookingPage() { {/* در جابه‌جایی پزشک عوض نمی‌شود؛ پرسیدنش یعنی دعوت به تغییری که خواسته نشده. */} {!rebookUuid && ( -
    +
    - setName(e.target.value)} placeholder="دستگاه لیزر" /> + setName(e.target.value)} placeholder="دستگاه لیزر" />