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}
|
||||
|
||||
@@ -49,10 +49,10 @@ export default function CancellationPolicyPage() {
|
||||
backTo="/admin/settings-menu"
|
||||
/>
|
||||
|
||||
<div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 640 }}>
|
||||
<div className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 640 }}>
|
||||
{loading && <span style={{ fontSize: 13, color: 'var(--text-3)' }}>در حال بارگذاری…</span>}
|
||||
|
||||
<div className="field" style={{ maxWidth: 220 }}>
|
||||
<div className="field-block" style={{ maxWidth: 220 }}>
|
||||
<label htmlFor="cp-window">پنجرهٔ لغو رایگان (ساعت)</label>
|
||||
<input
|
||||
id="cp-window"
|
||||
@@ -67,7 +67,7 @@ export default function CancellationPolicyPage() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ maxWidth: 260 }}>
|
||||
<div className="field-block" style={{ maxWidth: 260 }}>
|
||||
<label>نوع جریمه پس از پنجرهٔ رایگان</label>
|
||||
<SearchableSelect
|
||||
value={mode}
|
||||
@@ -77,7 +77,7 @@ export default function CancellationPolicyPage() {
|
||||
</div>
|
||||
|
||||
{mode === 'percent' && (
|
||||
<div className="field" style={{ maxWidth: 200 }}>
|
||||
<div className="field-block" style={{ maxWidth: 200 }}>
|
||||
<label htmlFor="cp-percent">درصد جریمه</label>
|
||||
<input
|
||||
id="cp-percent"
|
||||
@@ -95,7 +95,7 @@ export default function CancellationPolicyPage() {
|
||||
)}
|
||||
|
||||
{mode === 'fixed' && (
|
||||
<div className="field" style={{ maxWidth: 260 }}>
|
||||
<div className="field-block" style={{ maxWidth: 260 }}>
|
||||
<label>مبلغ جریمه</label>
|
||||
<PriceInput value={value} onChange={setValue} suffix="ریال" />
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@ export default function CancellationPolicyPage() {
|
||||
اعتبار پکیج پس از لغو برمیگردد
|
||||
</label>
|
||||
|
||||
<div className="field" style={{ maxWidth: 220 }}>
|
||||
<div className="field-block" style={{ maxWidth: 220 }}>
|
||||
<label htmlFor="cp-threshold">آستانهٔ عدم حضور</label>
|
||||
<input
|
||||
id="cp-threshold"
|
||||
|
||||
@@ -219,7 +219,7 @@ export default function CourseProtocolsPage() {
|
||||
{draft && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{!draft.uuid && (
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label>سرویس</label>
|
||||
<SearchableSelect
|
||||
value={draft.service_uuid}
|
||||
@@ -231,7 +231,7 @@ export default function CourseProtocolsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="field" style={{ maxWidth: 200 }}>
|
||||
<div className="field-block" style={{ maxWidth: 200 }}>
|
||||
<label htmlFor="cp-sessions">تعداد جلسه</label>
|
||||
<input
|
||||
id="cp-sessions"
|
||||
@@ -254,7 +254,7 @@ export default function CourseProtocolsPage() {
|
||||
['ideal_days', 'ایدهآل (روز)'],
|
||||
['max_days', 'حداکثر (روز)'],
|
||||
] as const).map(([key, label]) => (
|
||||
<div className="field" key={key} style={{ maxWidth: 150 }}>
|
||||
<div className="field-block" key={key} style={{ maxWidth: 150 }}>
|
||||
<label htmlFor={`cp-${key}`}>{label}</label>
|
||||
<input
|
||||
id={`cp-${key}`}
|
||||
|
||||
@@ -189,7 +189,7 @@ export default function PackagesPage() {
|
||||
>
|
||||
{draft && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="pkg-name">نام پکیج</label>
|
||||
<input
|
||||
id="pkg-name"
|
||||
@@ -200,7 +200,7 @@ export default function PackagesPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="pkg-sessions">تعداد جلسه</label>
|
||||
<input
|
||||
id="pkg-sessions"
|
||||
@@ -212,7 +212,7 @@ export default function PackagesPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label>قیمت</label>
|
||||
<PriceInput
|
||||
value={draft.price_rials}
|
||||
@@ -221,7 +221,7 @@ export default function PackagesPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="pkg-validity">اعتبار (روز)</label>
|
||||
<input
|
||||
id="pkg-validity"
|
||||
@@ -236,7 +236,7 @@ export default function PackagesPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label>سرویسهای پوششدادهشده</label>
|
||||
<SearchableSelect
|
||||
value={null}
|
||||
|
||||
@@ -100,8 +100,8 @@ export default function PlanAccuracyPage() {
|
||||
backTo="/admin/settings-menu"
|
||||
/>
|
||||
|
||||
<div className="card" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 200, margin: 0 }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="field-block" style={{ minWidth: 200, margin: 0 }}>
|
||||
<label>بازه</label>
|
||||
<SearchableSelect value={days} onChange={(v) => setDays(String(v ?? '30'))} options={RANGES} />
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function PolicyFormPage() {
|
||||
backTo="/admin/policies"
|
||||
/>
|
||||
|
||||
<div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<div className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -90,7 +90,7 @@ export default function PolicyFormPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="policy-name">نام قانون</label>
|
||||
<input
|
||||
id="policy-name"
|
||||
@@ -104,7 +104,7 @@ export default function PolicyFormPage() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ maxWidth: 200 }}>
|
||||
<div className="field-block" style={{ maxWidth: 200 }}>
|
||||
<label htmlFor="policy-priority">اولویت</label>
|
||||
<input
|
||||
id="policy-priority"
|
||||
@@ -120,7 +120,7 @@ export default function PolicyFormPage() {
|
||||
|
||||
{mode === 'template' ? (
|
||||
<>
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label>الگو</label>
|
||||
<SearchableSelect
|
||||
value={templateKey}
|
||||
@@ -137,7 +137,7 @@ export default function PolicyFormPage() {
|
||||
</div>
|
||||
|
||||
{template?.inputs.map((input) => (
|
||||
<div className="field" key={input.key} style={{ maxWidth: 260 }}>
|
||||
<div className="field-block" key={input.key} style={{ maxWidth: 260 }}>
|
||||
<label htmlFor={`tpl-${input.key}`}>{input.label}</label>
|
||||
<input
|
||||
id={`tpl-${input.key}`}
|
||||
@@ -153,7 +153,7 @@ export default function PolicyFormPage() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="field" style={{ maxWidth: 260 }}>
|
||||
<div className="field-block" style={{ maxWidth: 260 }}>
|
||||
<label>دسته</label>
|
||||
<SearchableSelect
|
||||
value={category}
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function PriceListsPage() {
|
||||
>
|
||||
{draft && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="pl-name">نام لیست</label>
|
||||
<input
|
||||
id="pl-name"
|
||||
@@ -249,7 +249,7 @@ export default function PriceListsPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label>شعبه</label>
|
||||
<SearchableSelect
|
||||
value={draft.address_uuid ?? ''}
|
||||
@@ -265,14 +265,14 @@ export default function PriceListsPage() {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 180 }}>
|
||||
<div className="field-block" style={{ minWidth: 180 }}>
|
||||
<label>از تاریخ</label>
|
||||
<PersianDateInput
|
||||
value={unixToIso(draft.valid_from)}
|
||||
onChange={(iso) => setDraft({ ...draft, valid_from: isoToUnix(iso) ?? draft.valid_from })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field" style={{ minWidth: 180 }}>
|
||||
<div className="field-block" style={{ minWidth: 180 }}>
|
||||
<label>تا تاریخ</label>
|
||||
<PersianDateInput
|
||||
value={unixToIso(draft.valid_to)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import HoldCountdown from '../components/HoldCountdown';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { formatDate, formatNumber } from '../lib/utils';
|
||||
import { useBranches } from '../hooks/useBranches';
|
||||
import { useAllServiceItems } from '../hooks/useServiceCatalog';
|
||||
import {
|
||||
@@ -140,8 +140,8 @@ export default function ResourceBookingPage() {
|
||||
backTo={rebookUuid ? `/admin/appointments/${rebookUuid}` : '/admin/appointments'}
|
||||
/>
|
||||
|
||||
<div className="card" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className="field" style={{ minWidth: 220, margin: 0 }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className="field-block" style={{ minWidth: 220, margin: 0 }}>
|
||||
<label>خدمت</label>
|
||||
<SearchableSelect
|
||||
value={serviceUuid}
|
||||
@@ -154,7 +154,7 @@ export default function ResourceBookingPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ minWidth: 200, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 200, margin: 0 }}>
|
||||
<label>شعبه</label>
|
||||
<SearchableSelect
|
||||
value={branchUuid}
|
||||
@@ -167,7 +167,7 @@ export default function ResourceBookingPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ minWidth: 160, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 160, margin: 0 }}>
|
||||
<label>بازه</label>
|
||||
<SearchableSelect
|
||||
value={days}
|
||||
@@ -191,19 +191,19 @@ export default function ResourceBookingPage() {
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="card" style={{ marginBottom: 16, fontSize: 13, color: 'var(--danger)' }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, fontSize: 13, color: 'var(--danger)' }}>
|
||||
{error instanceof ApiError ? error.message : 'جستجوی وقت ناموفق بود'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<div className="card" style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', fontSize: 14 }}>
|
||||
<span>
|
||||
مدت نوبت: <strong>{result.plan.total_minutes}</strong> دقیقه
|
||||
مدت نوبت: <strong>{formatNumber(result.plan.total_minutes)}</strong> دقیقه
|
||||
</span>
|
||||
<span style={{ color: 'var(--text-2)' }}>
|
||||
{result.plan.segments.length} بخش · {result.slots.length} وقت پیدا شد
|
||||
{formatNumber(result.plan.segments.length)} بخش · {formatNumber(result.slots.length)} وقت پیدا شد
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -215,7 +215,7 @@ export default function ResourceBookingPage() {
|
||||
)}
|
||||
|
||||
{result && result.slots.length > 0 && (
|
||||
<div style={{ overflowX: 'auto' }} className="card">
|
||||
<div style={{ overflowX: 'auto' }} className="card card-pad">
|
||||
<table style={{ width: '100%', fontSize: 13, borderCollapse: 'collapse', minWidth: 520 }}>
|
||||
<thead>
|
||||
<tr style={{ color: 'var(--text-3)', textAlign: 'right' }}>
|
||||
@@ -262,7 +262,7 @@ export default function ResourceBookingPage() {
|
||||
)}
|
||||
|
||||
{pickedSlot && (
|
||||
<div className="card" style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div className="card card-pad" style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||
<h3 style={{ fontSize: 15, margin: 0 }}>
|
||||
{formatDate(pickedSlot.start)} · <span dir="ltr">{timeOf(pickedSlot.start)}</span>
|
||||
@@ -301,7 +301,7 @@ export default function ResourceBookingPage() {
|
||||
|
||||
{/* در جابهجایی پزشک عوض نمیشود؛ پرسیدنش یعنی دعوت به تغییری که خواسته نشده. */}
|
||||
{!rebookUuid && (
|
||||
<div className="field" style={{ maxWidth: 280, margin: 0 }}>
|
||||
<div className="field-block" style={{ maxWidth: 280, margin: 0 }}>
|
||||
<label>پزشک نوبت</label>
|
||||
<SearchableSelect
|
||||
value={doctorUuid}
|
||||
|
||||
@@ -163,7 +163,7 @@ function TypeModal({
|
||||
|
||||
<div style={{ display: 'grid', gap: 6 }}>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-2)' }}>نام نمایشی</label>
|
||||
<input className="field" value={name} onChange={(e) => setName(e.target.value)} placeholder="دستگاه لیزر" />
|
||||
<input className="field-block" value={name} onChange={(e) => setName(e.target.value)} placeholder="دستگاه لیزر" />
|
||||
</div>
|
||||
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
|
||||
|
||||
@@ -100,12 +100,12 @@ export default function ResourceUtilizationPage() {
|
||||
<div className="fade-in">
|
||||
<PageHeader
|
||||
title="بهرهوری منابع"
|
||||
description="فاصلهٔ «اشغال» و «کار مفید» نشان میدهد بخشهای نوبت درست تعریف شدهاند یا نه."
|
||||
description="چقدر از ظرفیت هر منبع در بازهٔ انتخابشده واقعاً استفاده شده است."
|
||||
backTo="/admin/settings-menu"
|
||||
/>
|
||||
|
||||
<div className="card" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 220, margin: 0 }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="field-block" style={{ minWidth: 220, margin: 0 }}>
|
||||
<label>شعبه</label>
|
||||
<SearchableSelect
|
||||
value={branchUuid}
|
||||
@@ -115,7 +115,7 @@ export default function ResourceUtilizationPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ minWidth: 180, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 180, margin: 0 }}>
|
||||
<label>بازه</label>
|
||||
<SearchableSelect value={days} onChange={(v) => setDays(String(v ?? '7'))} options={RANGES} />
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function TreatmentCoursePage() {
|
||||
/>
|
||||
|
||||
{course && (
|
||||
<div className="card" style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div className="card card-pad" style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontSize: 15 }}>
|
||||
جلسهٔ <strong>{course.progress.completed}</strong> از {course.progress.total} انجام شده
|
||||
@@ -116,7 +116,7 @@ export default function TreatmentCoursePage() {
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
|
||||
<div className="field" style={{ minWidth: 220, margin: 0 }}>
|
||||
<div className="field-block" style={{ minWidth: 220, margin: 0 }}>
|
||||
<label>شعبه برای پیشنهاد وقت</label>
|
||||
<SearchableSelect
|
||||
value={branchUuid}
|
||||
@@ -180,7 +180,7 @@ export default function TreatmentCoursePage() {
|
||||
setAbandoning(false);
|
||||
}}
|
||||
>
|
||||
<div className="field">
|
||||
<div className="field-block">
|
||||
<label htmlFor="abandon-reason">دلیل</label>
|
||||
<input
|
||||
id="abandon-reason"
|
||||
|
||||
Reference in New Issue
Block a user