Files
clinicpro/assets/admin/hooks/useElapsed.ts
T
hamedandClaude Opus 5 a63de2a52c fix(treatment): let the operator actually record an area
Starting a session created area records with no device, and the panel only ever
read the device it never set — so every "اتمام این ناحیه" came back 422 with
"دستگاه این ناحیه مشخص نیست". The backend tests passed because they sent
resource_uuid explicitly; from the UI the flow was unusable end to end.

The device now inherits from the appointment's resource, which the secretary
already chose at booking; asking the operator again is taking one decision
twice. The session screen offers a picker per area on top of that, because one
session really does run bikini on an alexandrite and underarms on a diode.

Treating without a device is allowed: botox is an injection, and requiring a
device would make clinics invent a fake resource per injection. Sending readings
with no device is still rejected — there would be no schema to validate against.

A protocol whose service has no ResourceServiceOffering rows now says so in the
tab where the manager is standing. It does not block booking: "no offering means
any resource" is a deliberate, tested rule. But silence meant the gap surfaced
only when the operator was already in front of a patient.

Also adds the live timer the spec asked for, and wires slot-suggestions into the
unbooked queue — the endpoint existed and tested green but no screen called it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 11:08:48 +03:30

41 lines
1.6 KiB
TypeScript

import { useEffect, useState } from 'react';
/**
* مدت سپری‌شده از یک لحظه، به‌صورت زنده.
*
* `startedAt` تایم‌استمپ **سرور** است و مبنا همان می‌ماند؛ اینجا فقط هر ثانیه دوباره
* رندر می‌شود. ساعت مرورگر ممکن است چند ثانیه جلو یا عقب باشد، ولی زمانِ ثبت‌شده
* همان است که سرور نوشته — این عدد فقط برای دیدن است، نه برای ذخیره.
*
* `null` یعنی هنوز شروع نشده؛ `finishedAt` که بیاید تایمر می‌ایستد.
*/
export function useElapsed(startedAt: number | null, finishedAt: number | null = null): string | null {
const [now, setNow] = useState(() => Math.floor(Date.now() / 1000));
const running = startedAt !== null && finishedAt === null;
useEffect(() => {
if (!running) return;
const id = setInterval(() => setNow(Math.floor(Date.now() / 1000)), 1000);
return () => clearInterval(id);
}, [running]);
if (startedAt === null) return null;
const seconds = Math.max(0, (finishedAt ?? now) - startedAt);
return formatDuration(seconds);
}
/** «۰۵:۳۲» یا «۱:۱۲:۰۴» — ساعت فقط وقتی واقعاً از یک ساعت گذشته باشد. */
export function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
const pad = (n: number) => String(n).padStart(2, '0');
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
}