Three screens, each reusing what already exists rather than inventing a parallel look. The treatment plan lives as a tab on the service page next to categories, because the course belongs to the service; the switch is the protocol's existence rather than a separate boolean that could disagree with the step list. Each step asks for days since the previous session, which is how the interval actually works and what the form should therefore say. The staff screens are the flow from the reference screenshots: today's sessions, then a session where each area is started, recorded and closed on its own. Field inputs are built from the schema the server sends per resource type, so a clinic adding an RF device sees its own form here without a code change. StatusBadge gains treatment session and area states rather than a second badge component sitting beside it, and the resource type modal grows a field editor so the operator form is configured where the device is defined. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
3.2 KiB
TypeScript
70 lines
3.2 KiB
TypeScript
import { Link } from 'react-router-dom';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { ClipboardDocumentListIcon } from '@heroicons/react/24/outline';
|
|
import { api } from '../lib/api';
|
|
import type { ApiResponse } from '../lib/api';
|
|
import PageHeader from '../components/ui/PageHeader';
|
|
import StatusBadge from '../components/ui/StatusBadge';
|
|
import { formatDate } from '../lib/utils';
|
|
import type { StaffTreatmentSession } from '../types';
|
|
|
|
/**
|
|
* «جلسات امروز من» — نقطهٔ ورود پرسنل به کار روز.
|
|
*
|
|
* فهرست از نوبتِ متصل به جلسه میآید نه از سررسید تخمینی: کارِ امروز چیزی است که
|
|
* برایش وقت گرفته شده.
|
|
*/
|
|
export default function StaffTreatmentSessionsPage() {
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['staff-treatment-sessions'],
|
|
queryFn: () => api.get<ApiResponse<StaffTreatmentSession[]>>('/api/v1/dashboard/staff/treatment-sessions'),
|
|
staleTime: 30_000,
|
|
});
|
|
|
|
const sessions = data?.data ?? [];
|
|
|
|
return (
|
|
<>
|
|
<PageHeader title="جلسات امروز من" />
|
|
|
|
{isLoading ? (
|
|
<div className="card card-pad" style={{ fontSize: 13, color: 'var(--text-3)' }}>در حال بارگذاری...</div>
|
|
) : sessions.length === 0 ? (
|
|
<div className="card card-pad" style={{ display: 'grid', gap: 8, justifyItems: 'center', padding: 40 }}>
|
|
<ClipboardDocumentListIcon style={{ width: 40, height: 40, color: 'var(--text-3)' }} />
|
|
<span style={{ fontSize: 13.5, color: 'var(--text-2)' }}>امروز جلسهای برای شما ثبت نشده است</span>
|
|
</div>
|
|
) : (
|
|
<div style={{ display: 'grid', gap: 12 }}>
|
|
{sessions.map((s) => {
|
|
const settled = s.areas?.filter((a) => a.status === 'completed' || a.status === 'skipped').length ?? 0;
|
|
const total = s.areas?.length ?? 0;
|
|
|
|
return (
|
|
<div key={s.uuid} className="card card-pad" style={{ display: 'grid', gap: 10 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
|
<strong style={{ fontSize: 14 }}>{s.service_name}</strong>
|
|
<StatusBadge type="treatment-session" value={s.status} />
|
|
<span style={{ fontSize: 12.5, color: 'var(--text-3)' }}>
|
|
جلسهٔ {s.session_number} از {s.total_sessions}
|
|
</span>
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', fontSize: 12.5, color: 'var(--text-2)' }}>
|
|
{s.appointment && <span>ساعت {new Date(s.appointment.slot_start * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })}</span>}
|
|
{s.appointment && <span>{formatDate(s.appointment.slot_start)}</span>}
|
|
{total > 0 && <span>{settled} از {total} ناحیه انجام شده</span>}
|
|
</div>
|
|
|
|
<Link to={`/admin/my-sessions/${s.uuid}`} className="btn primary sm" style={{ justifySelf: 'start' }}>
|
|
مشاهده جزئیات و انجام جلسه
|
|
</Link>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|