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>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
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)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user