refactor(resource): drop the branch domain from resources

Resources never needed a branch: devices and rooms belong to the clinic
itself, and the picker always had exactly one option — a mandatory click
that decided nothing.

- `address_uuid` is now optional on resource and pool creation; when it is
  missing the environment's own address is used. Clients still sending it
  keep working.
- The panel no longer asks for or displays a branch anywhere: resource
  form, list column and filter, pool form and column, detail row, and the
  resource-first booking page.
- Availability no longer gates on `doctor_addresses.active`. That gate shut
  down every device of a clinic whose address row happened to be inactive,
  with a message no page in the panel could act on — no endpoint writes
  that column at all.

`address_id` stays on the resource: the timezone and the tenant pair are
derived from it. It is simply no longer the user's decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-03 14:57:47 +03:30
co-authored by Claude Opus 5
parent 05bb4a4ade
commit 581a553516
19 changed files with 246 additions and 117 deletions
+1 -16
View File
@@ -7,7 +7,6 @@ import SearchableSelect from '../components/ui/SearchableSelect';
import { ActiveBadge } from '../components/ui/StatusBadge';
import { useUrlState } from '../hooks/useUrlState';
import { usePermissions } from '../hooks/usePermissions';
import { useAddresses } from '../hooks/useAddresses';
import { useResources, useResourceTypes, useSkills } from '../hooks/useResources';
import ResourceFormModal from '../components/resources/ResourceFormModal';
import type { ClinicResource } from '../types';
@@ -29,17 +28,15 @@ const SUBJECT_LABEL: Record<string, string> = {
*/
export default function ResourcesPage() {
const [urlState, setUrlState] = useUrlState({
search: '', address: '', type: '', skill: '', status: '',
search: '', type: '', skill: '', status: '',
});
const { addresses } = useAddresses();
const { types } = useResourceTypes();
const { skills } = useSkills();
const { can } = usePermissions();
const canUpdate = can('appointment_settings', 'update');
const { resources, loading, create } = useResources({
address_uuid: urlState.address || undefined,
type_uuid: urlState.type || undefined,
skill_uuid: urlState.skill || undefined,
active: urlState.status || undefined,
@@ -67,7 +64,6 @@ export default function ResourcesPage() {
</div>
),
},
{ key: 'address_name', header: 'شعبه', render: (r) => <span style={{ fontSize: 13 }}>{r.address_name || '—'}</span> },
{
key: 'capacity',
header: 'ظرفیت هم‌زمان',
@@ -127,16 +123,6 @@ export default function ResourcesPage() {
emptyMessage="هیچ منبعی با این فیلترها یافت نشد"
headerExtra={
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginRight: 'auto' }}>
<div style={{ minWidth: 180 }}>
<SearchableSelect
options={addresses.map((b) => ({ value: b.uuid, label: b.name || 'بدون نام' }))}
value={urlState.address || null}
onChange={(v) => setUrlState({ address: v ? String(v) : '' })}
placeholder="همهٔ شعبه‌ها"
isClearable
height={36}
/>
</div>
<div style={{ minWidth: 170 }}>
<SearchableSelect
options={types.map((t) => ({ value: t.uuid, label: t.name }))}
@@ -185,7 +171,6 @@ export default function ResourcesPage() {
<ResourceFormModal
open={createOpen}
resource={null}
addresses={addresses}
types={types}
saving={create.isPending}
onClose={() => setCreateOpen(false)}