`;
+}
+
+async function rpLoadPatientsForPicker() {
+ try {
+ rpPatientsCache = await Api.getPatients();
+ const select = document.getElementById('rp_patientSelect');
+ if (select) {
+ select.innerHTML = rpPatientsCache.map(p => ``).join('');
+ }
+ } catch { /* patient picker just won't populate; add flow will fail loudly instead */ }
+}
+
+function rpOpenAddModal() {
+ rpEditingRowId = null;
+ rpEditingPatientId = null;
+ const config = window.RESOURCE_PAGE_CONFIG;
+ document.getElementById('rp_modalTitle').textContent = `Add ${config.title.replace(/s$/, '')}`;
+ document.getElementById('rp_patientPickerRow').classList.remove('d-none');
+ document.getElementById('rp_fieldsBody').innerHTML = config.fields.map(f => rpFieldInputHtml(f, null)).join('');
+ rpAddModal.show();
+}
+
+function rpOpenEditModal(row) {
+ rpEditingRowId = row.id;
+ rpEditingPatientId = row.patientId;
+ const config = window.RESOURCE_PAGE_CONFIG;
+ document.getElementById('rp_modalTitle').textContent = `Edit ${config.title.replace(/s$/, '')} — ${row.patientName}`;
+ document.getElementById('rp_patientPickerRow').classList.add('d-none');
+ document.getElementById('rp_fieldsBody').innerHTML = config.fields.map(f => rpFieldInputHtml(f, row[f.key])).join('');
+ rpAddModal.show();
+}
+
+async function rpSubmitModal(e) {
+ e.preventDefault();
+ const config = window.RESOURCE_PAGE_CONFIG;
+ const payload = {};
+ for (const f of config.fields) {
+ const el = document.getElementById(`rp_field_${f.key}`);
+ let v;
+ if (f.type === 'checkbox') {
+ v = el.checked;
+ } else {
+ v = el.value;
+ if (f.type === 'number') v = v === '' ? null : parseFloat(v);
+ v = v === '' ? null : v;
+ }
+ payload[f.key] = v;
+ }
+
+ try {
+ if (rpEditingRowId) {
+ await config.update(rpEditingPatientId, rpEditingRowId, payload);
+ rpShowToast(`${config.title} updated.`);
+ } else {
+ const patientId = document.getElementById('rp_patientSelect').value;
+ if (!patientId) { rpShowToast('Select a patient first.', 'danger'); return; }
+ await config.add(patientId, payload);
+ rpShowToast(`${config.title} added.`);
+ }
+ rpAddModal.hide();
+ await rpLoadGrid();
+ } catch (err) {
+ rpShowToast(err.message, 'danger');
+ }
+}
+
+async function rpDeleteRow(row) {
+ const config = window.RESOURCE_PAGE_CONFIG;
+ if (!confirm(`Delete this ${config.title.replace(/s$/, '').toLowerCase()} for ${row.patientName}?`)) return;
+ try {
+ await config.remove(row.patientId, row.id);
+ rpShowToast('Deleted.');
+ await rpLoadGrid();
+ // The row's Delete button lives inside the popup modal — refresh (or
+ // close, if that was their last record) so it doesn't show stale data.
+ const container = document.getElementById('rp_gridBody');
+ const stillHasRows = (container._rpRows || []).some(r => r.patientId === row.patientId);
+ if (stillHasRows) rpOpenViewModal(row.patientId);
+ else if (rpViewModalInstance) rpViewModalInstance.hide();
+ } catch (err) {
+ rpShowToast(err.message, 'danger');
+ }
+}
+
+// Bootstrap "bg-*" classes cycled per tile so patients are visually easy to
+// tell apart at a glance — not tied to any data meaning, just variety.
+const RP_TILE_COLORS = ['primary', 'success', 'info', 'warning', 'danger', 'secondary'];
+
+// The pages ship a
... wrapper (from
+// before this was a tile grid). Swap that whole table out for a plain grid
+// div, once, so none of the 7 HTML pages that use this engine need editing.
+function rpEnsureTileContainer() {
+ const existing = document.getElementById('rp_gridBody');
+ if (existing.tagName.toLowerCase() === 'div') return existing;
+ const table = existing.closest('table');
+ const div = document.createElement('div');
+ div.id = 'rp_gridBody';
+ div.className = 'rp-tile-grid';
+ table.replaceWith(div);
+ return div;
+}
+
+let rpViewModalInstance = null;
+
+// The pages don't ship a "view details" modal in their HTML — build it once
+// and reuse it, same approach as rpEnsureTileContainer above. Scrollable so
+// patients with a lot of records don't blow out the page.
+function rpEnsureViewModal() {
+ let el = document.getElementById('rp_viewModal');
+ if (el) return el;
+ el = document.createElement('div');
+ el.className = 'modal fade';
+ el.id = 'rp_viewModal';
+ el.tabIndex = -1;
+ el.innerHTML = `
+