fix(admin): correct two design-system mismatches found by looking at the pages
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) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,8 @@ describe('AppointmentSegmentsCard', () => {
|
||||
|
||||
renderWithProviders(<AppointmentSegmentsCard appointmentUuid="a-1" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText('60 دقیقه')).toBeInTheDocument());
|
||||
// ارقام فارسیاند: ۲۰ + ۴۰ = ۶۰
|
||||
await waitFor(() => expect(screen.getByText('۶۰ دقیقه')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
/** ⭐ مدتی که بیمار روی صندلی نیست باید دیده شود، وگرنه «۹۰ دقیقه» گمراهکننده است. */
|
||||
|
||||
@@ -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
|
||||
<div className="bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-[var(--text)]">بخشهای نوبت</h3>
|
||||
<span className="text-sm text-[var(--text-2)]">{total} دقیقه</span>
|
||||
<span className="text-sm text-[var(--text-2)]">{formatNumber(total)} دقیقه</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 2, marginBottom: 14 }}>
|
||||
{segments.map((s) => (
|
||||
<div
|
||||
key={s.sequence}
|
||||
title={`${s.name} — ${s.duration_minutes} دقیقه`}
|
||||
title={`${s.name} — ${formatNumber(s.duration_minutes)} دقیقه`}
|
||||
style={{
|
||||
flex: s.duration_minutes,
|
||||
height: 10,
|
||||
@@ -45,7 +46,7 @@ export default function AppointmentSegmentsCard({ appointmentUuid }: { appointme
|
||||
<ol style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 13 }}>
|
||||
{segments.map((s) => (
|
||||
<li key={s.sequence} style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
||||
<span style={{ color: 'var(--text-3)', minWidth: 18 }}>{s.sequence}</span>
|
||||
<span style={{ color: 'var(--text-3)', minWidth: 18 }}>{formatNumber(s.sequence)}</span>
|
||||
<span style={{ flex: 1 }}>{s.name}</span>
|
||||
<span dir="ltr" style={{ color: 'var(--text-2)' }}>
|
||||
{timeOf(s.starts_at)} – {timeOf(s.ends_at)}
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function ResourceBlocksModal({ resourceUuid, resourceName, onClos
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className="field" style={{ minWidth: 170, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 170, margin: 0 }}>
|
||||
<label>روز</label>
|
||||
<PersianDateInput value={day} onChange={setDay} />
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@ export default function ResourceBlocksModal({ resourceUuid, resourceName, onClos
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ margin: 0 }}>
|
||||
<div className="field-block" style={{ margin: 0 }}>
|
||||
<label htmlFor="block-reason">دلیل</label>
|
||||
<input
|
||||
id="block-reason"
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
{/* ── گروهها ───────────────────────────────────────────────────────── */}
|
||||
<section className="card" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<section className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<h3 style={{ fontSize: 15, margin: 0 }}>گروههای انتخاب</h3>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
@@ -71,7 +71,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
|
||||
|
||||
{canEdit && (
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 240, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 240, margin: 0 }}>
|
||||
<label htmlFor="new-group">گروه تازه</label>
|
||||
<input
|
||||
id="new-group"
|
||||
@@ -175,7 +175,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ margin: 0 }}>
|
||||
<div className="field-block" style={{ margin: 0 }}>
|
||||
<label>آیتمهای این گروه</label>
|
||||
<SearchableSelect
|
||||
value={null}
|
||||
@@ -222,7 +222,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
|
||||
</section>
|
||||
|
||||
{/* ── روابط ─────────────────────────────────────────────────────────── */}
|
||||
<section className="card" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<section className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<h3 style={{ fontSize: 15, margin: 0 }}>ناسازگاری و پیشنیاز</h3>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
@@ -299,7 +299,7 @@ export default function ServiceGroupsTab({ serviceUuid, canEdit, initialRelation
|
||||
</section>
|
||||
|
||||
{/* ── پیشنمایش زنده ────────────────────────────────────────────────── */}
|
||||
<section className="card" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<section className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<h3 style={{ fontSize: 15, margin: 0 }}>پیشنمایش انتخاب</h3>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function ServiceSegmentsTab({ serviceUuid, canEdit }: Props) {
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<section className="card" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<section className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<h3 style={{ fontSize: 15, margin: 0 }}>بخشهای نوبت</h3>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
@@ -378,9 +378,9 @@ export default function ServiceSegmentsTab({ serviceUuid, canEdit }: Props) {
|
||||
</section>
|
||||
|
||||
{/* ── نوار پیشنمایش زمانی ──────────────────────────────────────────── */}
|
||||
<section className="card" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<section className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 220, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 220, margin: 0 }}>
|
||||
<label>شعبه برای پیشنمایش</label>
|
||||
<SearchableSelect
|
||||
value={branchUuid}
|
||||
|
||||
Reference in New Issue
Block a user