New top-level tab positioned after Physical Exam Guide. Per-user
directory of hospital phone extensions and pagers — grouped by location
then type, searchable, soft-deleted.
Data:
- New table user_phone_extensions (id, user_id, location, name, number,
type CHECK (extension|pager), notes, trashed_at, timestamps).
Partial indexes on active vs trashed rows for fast filtering.
- Not PHI — hospital internal phone directory. Plaintext.
API (all user-scoped, all params validated):
- GET /api/extensions?trash=1&q=text — list active or trash, optional search
- POST /api/extensions — create
- PUT /api/extensions/:id — update (requires all three core fields)
- DELETE /api/extensions/:id — soft-delete (sets trashed_at)
- POST /api/extensions/:id/restore — un-trash
- DELETE /api/extensions/:id/purge — hard-delete (only if trashed)
All :id params parsed + validated (positive integer) before query.
All queries parameterized, every WHERE includes user_id scoping.
UI (public/js/extensions.js + components/extensions.html):
- Search bar with 200ms debounce, server-side LIKE on location/name/number/notes
- Add button expands inline form — location (with datalist of existing
locations for autocomplete), name/dept, number, type, optional notes
- Each entry renders as a card: big monospace number, dept, type badge,
edit + delete inline
- Grouped by location → type (Extensions / Pagers subheaders)
- Trash view: toggle shows trashed items with Restore + Purge actions
- Trash count badge on the Trash button updates after every delete/restore
- Delete requires confirm() dialog, then soft-delete (easy to undo)
- Purge from trash requires a second confirm() ("cannot be undone")
- Esc closes the form; form resets between Add and Edit
60 lines
3.6 KiB
HTML
60 lines
3.6 KiB
HTML
<div class="module-header">
|
|
<h2><i class="fas fa-phone-volume"></i> Pagers & Extensions</h2>
|
|
<p>Your personal directory of hospital phone extensions and pagers — searchable, edit-friendly, soft-delete</p>
|
|
</div>
|
|
|
|
<div class="card ext-toolbar">
|
|
<div style="display:flex;gap:10px;align-items:center;padding:12px 16px;flex-wrap:wrap;">
|
|
<div style="flex:1;min-width:220px;position:relative;">
|
|
<i class="fas fa-search" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--g400);font-size:13px;"></i>
|
|
<input type="search" id="ext-search" placeholder="Search by location, name, number..." autocomplete="off"
|
|
style="width:100%;padding:8px 10px 8px 32px;font-size:14px;border:1px solid var(--g300);border-radius:8px;">
|
|
</div>
|
|
<button id="ext-add-btn" class="btn-sm btn-primary"><i class="fas fa-plus"></i> Add</button>
|
|
<button id="ext-trash-btn" class="btn-sm btn-ghost"><i class="fas fa-trash-can"></i> Trash <span id="ext-trash-count" style="color:var(--g500);font-size:11px;"></span></button>
|
|
</div>
|
|
|
|
<div id="ext-form-wrap" class="hidden" style="border-top:1px solid var(--g100);padding:14px 16px;background:var(--g50);">
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;">
|
|
<div class="demo-field">
|
|
<label>Location</label>
|
|
<input type="text" id="ext-location" list="ext-location-list" placeholder="e.g., Main Hospital" maxlength="120">
|
|
<datalist id="ext-location-list"></datalist>
|
|
</div>
|
|
<div class="demo-field">
|
|
<label>Name / Department</label>
|
|
<input type="text" id="ext-name" placeholder="e.g., Nursery, 5B, On-call" maxlength="120">
|
|
</div>
|
|
<div class="demo-field">
|
|
<label>Number</label>
|
|
<input type="text" id="ext-number" placeholder="e.g., 5866" maxlength="40">
|
|
</div>
|
|
<div class="demo-field">
|
|
<label>Type</label>
|
|
<select id="ext-type">
|
|
<option value="extension">Extension</option>
|
|
<option value="pager">Pager</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:8px;">
|
|
<div class="demo-field">
|
|
<label>Notes (optional)</label>
|
|
<input type="text" id="ext-notes" placeholder="e.g., After hours: 5867" maxlength="500">
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:10px;display:flex;gap:6px;">
|
|
<button id="ext-save-btn" class="btn-sm btn-primary"><i class="fas fa-floppy-disk"></i> Save</button>
|
|
<button id="ext-cancel-btn" class="btn-sm btn-ghost">Cancel</button>
|
|
<span id="ext-form-status" style="font-size:12px;margin-left:6px;color:var(--g500);align-self:center;"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ext-mode-banner" class="hidden" style="margin:10px 0;padding:8px 12px;background:#fef3c7;border-left:4px solid #f59e0b;border-radius:6px;font-size:13px;color:#78350f;">
|
|
<i class="fas fa-trash-can"></i> Viewing trash. <button id="ext-back-active" class="btn-sm btn-ghost" style="margin-left:8px;">Back to active</button>
|
|
</div>
|
|
|
|
<div id="ext-list" style="margin-top:10px;">
|
|
<p style="text-align:center;color:#9ca3af;padding:40px;">Loading...</p>
|
|
</div>
|