479 lines
23 KiB
Markdown
479 lines
23 KiB
Markdown
# Scheduling & Telemedicine UI
|
|
|
|
Reference for appointment scheduling, queue management, pre-visit workflows, telemedicine video consultations, multi-channel communication, and notification systems. Covers **Web (Bootstrap 5/Tabler + PHP)** and **Android (Jetpack Compose + Material 3)**.
|
|
|
|
---
|
|
|
|
## 1. Appointment Scheduling
|
|
|
|
### Provider Search & Discovery
|
|
|
|
**Filter criteria:** specialty, location, insurance accepted, language, gender, availability window.
|
|
|
|
Integrate `gis-mapping` skill for map-based provider search with Leaflet markers.
|
|
|
|
**Provider Card Layout:**
|
|
|
|
```
|
|
┌──────────────────────────────────────────────┐
|
|
│ [Photo] Dr. Jane Kimura, MD │
|
|
│ Cardiology | Speaks: EN, SW │
|
|
│ Rating: 4.8/5 (142 reviews) │
|
|
│ Accepts: BlueCross, Aetna, Medicare │
|
|
│ Next Available: Feb 24, 10:30 AM │
|
|
│ [Book Now] [View Profile] │
|
|
└──────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Web:** Tabler card grid with sidebar filter panel. Map view via Leaflet (see `gis-mapping`). Combine DataTable search + Flatpickr date range for availability filtering.
|
|
|
|
**Android:** `LazyVerticalGrid` of `ElevatedCard` composables. Filter via `ModalBottomSheet`. Map view using `AndroidView` with Leaflet WebView or Google Maps Compose.
|
|
|
|
### Calendar Views
|
|
|
|
```
|
|
DAY VIEW WEEK VIEW
|
|
┌────────────────────────┐ ┌─────┬─────┬─────┬─────┬─────┐
|
|
│ Mon Feb 24, 2026 │ │ Mon │ Tue │ Wed │ Thu │ Fri │
|
|
├────────────────────────┤ ├─────┼─────┼─────┼─────┼─────┤
|
|
│ 8:00 [BLUE] New Pt │ │ ██ │ ░░ │ ██ │ ░░ │ ░░ │
|
|
│ John D. - Intake │ │ ██ │ ██ │ ░░ │ ██ │ ░░ │
|
|
│ 8:30 ░░ Available ░░ │ │ ░░ │ ██ │ ██ │ ░░ │ ██ │
|
|
│ 9:00 [TEAL] Follow-up │ │ ██ │ ░░ │ ██ │ ██ │ ░░ │
|
|
│ Mary S. - Cardio │ │ ░░ │ ██ │ ░░ │ ░░ │ ██ │
|
|
│ 9:30 [AMBER] Urgent │ └─────┴─────┴─────┴─────┴─────┘
|
|
│ Bob K. - Pain │ Legend: ██ Booked ░░ Available
|
|
│ 10:00 [PURPLE] Proc. │ ▓▓ Blocked
|
|
│ Kim L. - Biopsy │
|
|
│ 10:30 [GREEN] Telemed │
|
|
│ Pat R. - Video │
|
|
└────────────────────────┘
|
|
|
|
MONTH VIEW PROVIDER SCHEDULE (Multi-Column)
|
|
┌───┬───┬───┬───┬───┬───┬───┐ ┌──────────┬──────────┬──────────┐
|
|
│Mo │Tu │We │Th │Fr │Sa │Su │ │ Dr.Smith │ Dr.Jones │ Dr.Kim │
|
|
├───┼───┼───┼───┼───┼───┼───┤ ├──────────┼──────────┼──────────┤
|
|
│ │ │ │ │ │ 1 │ 2 │ │ 8:00 ██ │ 8:00 ░░ │ 8:00 ██ │
|
|
│ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ │ 8:30 ░░ │ 8:30 ██ │ 8:30 ██ │
|
|
│(3)│(5)│(2)│(4)│(1)│ │ │ │ 9:00 ██ │ 9:00 ██ │ 9:00 ░░ │
|
|
│10 │11 │...│ │ │ │ │ │ 9:30 ░░ │ 9:30 ░░ │ 9:30 ██ │
|
|
└───┴───┴───┴───┴───┴───┴───┘ └──────────┴──────────┴──────────┘
|
|
(n) = appointment count Facility scheduler view
|
|
```
|
|
|
|
**Web:** FullCalendar.js integration with Tabler card wrapper. Use `eventColor` mapped to appointment type. Flatpickr for date navigation.
|
|
|
|
**Android:** Custom Compose calendar grid using `LazyVerticalGrid`. Material 3 `DatePicker` for navigation. Color via `MaterialTheme.colorScheme` extensions.
|
|
|
|
### Appointment Type Color Coding
|
|
|
|
| Type | Color | Hex | Icon |
|
|
|------|-------|-----|------|
|
|
| New Patient | Blue | `#2563EB` | `user-plus` |
|
|
| Follow-up | Teal | `#0F766E` | `refresh` |
|
|
| Urgent | Amber | `#D97706` | `alert-triangle` |
|
|
| Procedure | Purple | `#7C3AED` | `scissors` |
|
|
| Telemedicine | Green | `#059669` | `video` |
|
|
|
|
### Booking Flow (3 Steps)
|
|
|
|
```
|
|
Step 1 Step 2 Step 3
|
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
│ Select Provider │ │ Patient Info │ │ Confirmation │
|
|
│ ┌──────────────┐ │ │ Name: [........] │ │ Date: Feb 24 │
|
|
│ │ Dr. Smith │ │ │ DOB: [........] │ │ Time: 10:30 AM │
|
|
│ │ Cardiology │ │ │ Reason: [......] │ │ Dr: Smith │
|
|
│ └──────────────┘ │ │ Insurance: │ │ Loc: Main Clinic │
|
|
│ Date: [Feb 24 ] │ │ [BlueCross v] │ │ [Map Link] │
|
|
│ Time: [10:30 v] │ │ Upload card: [+] │ │ Reminders: │
|
|
│ │ │ │ │ [x]SMS [x]Email │
|
|
│ [Next ->] │ │ [<- Back] [Next] │ │ [ ]Push │
|
|
└──────────────────┘ └──────────────────┘ │ [Add to Calendar]│
|
|
│ [Confirm Booking]│
|
|
└──────────────────┘
|
|
```
|
|
|
|
### Rescheduling & Cancellation
|
|
|
|
- Require reason selection (dropdown: conflict, illness, provider request, other)
|
|
- Show alternative available slots immediately
|
|
- Enforce cancellation policy window (e.g., 24h before)
|
|
- Display fee warning if inside cancellation window
|
|
- Log all changes to appointment audit trail
|
|
|
|
---
|
|
|
|
## 2. Appointment Queue / Waiting Room
|
|
|
|
### Status Progression
|
|
|
|
```
|
|
Scheduled --> Checked-in --> In Exam Room --> With Provider --> Checkout
|
|
[Gray] [Blue] [Teal] [Green] [Purple]
|
|
```
|
|
|
|
### Queue Display
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────┐
|
|
│ Today's Queue - Main Clinic Feb 24, 2026 │
|
|
├────────┬──────────┬───────────┬──────────┬───────────┬──────────┤
|
|
│ Patient│ Sched. │ Status │ Wait │ Provider │ Actions │
|
|
├────────┼──────────┼───────────┼──────────┼───────────┼──────────┤
|
|
│ John D.│ 9:00 AM │ [With Dr] │ -- │ Dr.Smith │ │
|
|
│ Mary S.│ 9:30 AM │ [In Room] │ 12 min │ Dr.Smith │ [Move] │
|
|
│ Bob K. │ 10:00 AM │ [CheckedIn│ 25 min ! │ Dr.Jones │ [Room] │
|
|
│ Kim L. │ 10:30 AM │ [Sched] │ -- │ Dr.Jones │ [CheckIn]│
|
|
│ Pat R. │ 11:00 AM │ [Sched] │ -- │ Dr.Kim │ [NoShow] │
|
|
└────────┴──────────┴───────────┴──────────┴───────────┴──────────┘
|
|
! = Wait exceeds threshold (>20 min = amber, >40 min = red)
|
|
```
|
|
|
|
**Quick actions:** Check-in, Move to room, Mark no-show, Reschedule.
|
|
|
|
**Web:** DataTable with real-time updates (AJAX polling every 30s or WebSocket). Status badges as colored Tabler `badge` components. Countdown timer JS for wait column.
|
|
|
|
**Android:** `LazyColumn` with `AssistChip` status indicators. `SwipeRefresh` for pull-to-refresh. `WorkManager` for background status sync.
|
|
|
|
```kotlin
|
|
@Composable
|
|
fun QueueStatusChip(status: AppointmentStatus) {
|
|
val (color, label) = when (status) {
|
|
AppointmentStatus.SCHEDULED -> Pair(Color.Gray, "Scheduled")
|
|
AppointmentStatus.CHECKED_IN -> Pair(ClinicalBlue, "Checked In")
|
|
AppointmentStatus.IN_ROOM -> Pair(ClinicalTeal, "In Room")
|
|
AppointmentStatus.WITH_PROVIDER -> Pair(ClinicalGreen, "With Provider")
|
|
AppointmentStatus.CHECKOUT -> Pair(ClinicalPurple, "Checkout")
|
|
}
|
|
AssistChip(
|
|
onClick = {},
|
|
label = { Text(label, color = Color.White) },
|
|
colors = AssistChipDefaults.assistChipColors(containerColor = color)
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Pre-Visit Preparation
|
|
|
|
### Pre-Visit Questionnaire
|
|
|
|
- Digital form sent via SMS/email link (token-authenticated, HIPAA-compliant)
|
|
- Captures: chief complaint, symptom duration, current medications, allergies updates
|
|
- Auto-saves progress, mobile-optimized responsive layout
|
|
- Submission triggers status update on provider dashboard
|
|
|
|
### Required Documents Checklist
|
|
|
|
| Document | Status | Action |
|
|
|----------|--------|--------|
|
|
| Insurance Card (front/back) | Uploaded | [View] |
|
|
| Photo ID | Pending | [Upload] |
|
|
| Consent Forms | Signed | [View] |
|
|
| Prior Auth Letter | N/A | -- |
|
|
|
|
### Pre-Visit Summary for Providers
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ PRE-VISIT SUMMARY: John Doe, M, 45y │
|
|
│ MRN: 1234567 | Allergies: Penicillin │
|
|
├─────────────────────────────────────────────┤
|
|
│ [v] History Highlights │
|
|
│ - Hypertension (dx 2020), Type 2 DM │
|
|
│ - Coronary stent placed Jan 2025 │
|
|
│ [v] Current Medications │
|
|
│ - Metformin 500mg BID │
|
|
│ - Lisinopril 10mg QD │
|
|
│ - Aspirin 81mg QD │
|
|
│ [v] Recent Labs (Feb 10, 2026) │
|
|
│ - HbA1c: 7.2% (↑ from 6.8) │
|
|
│ - LDL: 110 mg/dL [Warning] │
|
|
│ [>] Chief Complaint (from questionnaire) │
|
|
│ "Increased fatigue, 3 weeks duration" │
|
|
│ [>] Open Orders & Pending Results │
|
|
│ [>] Last Vitals (Feb 10): BP 138/88, HR 78 │
|
|
└─────────────────────────────────────────────┘
|
|
[v] = expanded [>] = collapsed
|
|
```
|
|
|
|
**Web:** Collapsible Tabler accordion cards beside the schedule view. PHP renders server-side with AJAX expand/collapse.
|
|
|
|
**Android:** `ExpandableCard` composable with `AnimatedVisibility` for section toggle. Lazy-load sections from API.
|
|
|
|
---
|
|
|
|
## 4. Telemedicine Video Consultation
|
|
|
|
### Pre-Call Tech Check
|
|
|
|
Verify before connecting: camera, microphone, speaker, network speed. Show pass/fail indicators. Provide troubleshooting links for failures. Minimum bandwidth requirement: 1.5 Mbps.
|
|
|
|
### Video Call Interface
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Telemedicine Session - Dr. Smith & John Doe [00:12:34] │
|
|
├──────────────────────────────────────────┬──────────────────────┤
|
|
│ │ Patient: John Doe │
|
|
│ │ MRN: 1234567 │
|
|
│ MAIN VIDEO (Provider/Patient) │ Allergies: Penicillin│
|
|
│ ├──────────────────────┤
|
|
│ │ Vitals (last): │
|
|
│ ┌──────────┐ │ BP: 138/88 HR: 78 │
|
|
│ │ Self-View│ │ SpO2: 97% │
|
|
│ │ (small) │ ├──────────────────────┤
|
|
│ └──────────┘ │ Medications: │
|
|
│ │ - Metformin 500mg │
|
|
├──────────────────────────────────────────┤ - Lisinopril 10mg │
|
|
│ [Mute] [Video] [Share] [Chat] [Rx] [End]│ - Aspirin 81mg │
|
|
├──────────────────────────────────────────┼──────────────────────┤
|
|
│ TRANSCRIPT (AI-powered, real-time) │ Notes: │
|
|
│ Dr: "How has your fatigue been?" │ [........................]│
|
|
│ Pt: "Worse in the mornings, better by" │ [........................]│
|
|
│ Dr: "Any chest pain or shortness of—" │ [Save Note] │
|
|
│ >> KEY: HbA1c 7.2% flagged in context │ │
|
|
└──────────────────────────────────────────┴──────────────────────┘
|
|
```
|
|
|
|
### In-Call Actions
|
|
|
|
| Action | Web | Android |
|
|
|--------|-----|---------|
|
|
| Mute/Unmute | WebRTC `audioTrack.enabled` toggle | CameraX audio toggle |
|
|
| Video On/Off | WebRTC `videoTrack.enabled` toggle | CameraX video toggle |
|
|
| Screen Share | `getDisplayMedia()` API | MediaProjection API |
|
|
| Chat | WebSocket sidebar panel | `ModalBottomSheet` overlay |
|
|
| Share Rx | Open prescription modal | Navigate to Rx composable |
|
|
| End Call | `RTCPeerConnection.close()` | Release CameraX + WebRTC |
|
|
|
|
### Post-Call Workflow
|
|
|
|
1. **Auto-generated visit summary** -- AI transcription produces structured SOAP note draft
|
|
2. **Prescription issuance** -- Provider reviews/signs e-prescriptions
|
|
3. **Follow-up scheduling** -- Suggest next appointment with pre-filled context
|
|
4. **Patient satisfaction** -- 1-5 star rating + optional comment within 24h
|
|
|
|
### Accessibility Features
|
|
|
|
| Feature | Implementation |
|
|
|---------|---------------|
|
|
| Real-time captions | AI speech-to-text overlay, toggleable by patient/provider |
|
|
| Large text mode | `font-scale` multiplier (1.5x), affects all UI text |
|
|
| Low-bandwidth fallback | Auto-detect < 500kbps, switch to audio-only with profile photo |
|
|
| High-contrast mode | WCAG AAA contrast for all video overlay controls |
|
|
|
|
**Web:** WebRTC via `RTCPeerConnection`, HTML5 `<video>` elements, Tabler sidebar for patient context. Transcript via Web Speech API or HIPAA-compliant transcription service.
|
|
|
|
```javascript
|
|
// Web - Basic WebRTC setup (simplified)
|
|
const pc = new RTCPeerConnection(iceConfig);
|
|
const localStream = await navigator.mediaDevices.getUserMedia({
|
|
video: { width: 1280, height: 720 },
|
|
audio: { echoCancellation: true, noiseSuppression: true }
|
|
});
|
|
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
|
|
document.getElementById('localVideo').srcObject = localStream;
|
|
|
|
pc.ontrack = (event) => {
|
|
document.getElementById('remoteVideo').srcObject = event.streams[0];
|
|
};
|
|
```
|
|
|
|
**Android:** CameraX for local preview + WebRTC for transmission. Overlay composables for controls.
|
|
|
|
```kotlin
|
|
@Composable
|
|
fun TelemedicineCallScreen(sessionId: String, viewModel: TelemedViewModel) {
|
|
val callState by viewModel.callState.collectAsState()
|
|
|
|
Box(modifier = Modifier.fillMaxSize()) {
|
|
// Remote video (full screen)
|
|
AndroidView(factory = { ctx -> SurfaceViewRenderer(ctx).apply {
|
|
viewModel.attachRemoteRenderer(this)
|
|
}}, modifier = Modifier.fillMaxSize())
|
|
|
|
// Self-view (picture-in-picture)
|
|
AndroidView(factory = { ctx -> SurfaceViewRenderer(ctx).apply {
|
|
viewModel.attachLocalRenderer(this)
|
|
}}, modifier = Modifier
|
|
.size(120.dp, 160.dp)
|
|
.align(Alignment.BottomStart)
|
|
.padding(16.dp))
|
|
|
|
// Controls bar
|
|
TelemedControlBar(
|
|
isMuted = callState.isMuted,
|
|
isVideoOn = callState.isVideoOn,
|
|
onToggleMute = viewModel::toggleMute,
|
|
onToggleVideo = viewModel::toggleVideo,
|
|
onEndCall = viewModel::endCall,
|
|
modifier = Modifier.align(Alignment.BottomCenter)
|
|
)
|
|
|
|
// Side panel - patient context
|
|
if (callState.showSidePanel) {
|
|
PatientContextPanel(
|
|
patient = callState.patient,
|
|
modifier = Modifier.align(Alignment.CenterEnd).width(280.dp)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Multi-Channel Communication
|
|
|
|
### Supported Channels During Visit
|
|
|
|
| Channel | Use Case | Fallback |
|
|
|---------|----------|----------|
|
|
| Video call | Primary telemedicine consult | Audio call |
|
|
| Audio call | Low-bandwidth or patient preference | Secure chat |
|
|
| Secure chat | Quick questions, async follow-up | Async message |
|
|
| Document share | Lab results, imaging, Rx | Secure portal |
|
|
|
|
### Channel Switching
|
|
|
|
- Maintain session context when switching (e.g., video to audio)
|
|
- Transfer transcript and notes across channels
|
|
- Log channel changes in visit record
|
|
|
|
**Web:** Tabbed communication panel with three tabs: Video | Chat | Documents. WebSocket maintains session state across tab switches.
|
|
|
|
```html
|
|
<!-- Web - Tabler tabbed communication panel -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<ul class="nav nav-tabs card-header-tabs">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" data-bs-toggle="tab" href="#video-tab">Video</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#chat-tab">Chat</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#docs-tab">Documents</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card-body tab-content">
|
|
<div class="tab-pane active" id="video-tab"><!-- WebRTC video --></div>
|
|
<div class="tab-pane" id="chat-tab"><!-- Secure chat messages --></div>
|
|
<div class="tab-pane" id="docs-tab"><!-- Shared documents list --></div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Android:** `BottomSheet` with channel options. Shared `ViewModel` preserves context across channel composables.
|
|
|
|
---
|
|
|
|
## 6. Notification & Reminder System
|
|
|
|
### Reminder Schedule
|
|
|
|
| Timing | Channel | Content |
|
|
|--------|---------|---------|
|
|
| 48h before | Email | Full details + prep instructions + map/link |
|
|
| 24h before | SMS + Push | Date, time, provider, location/join link |
|
|
| 2h before | Push | "Your appointment is in 2 hours" + quick actions |
|
|
| At time | Push (telemedicine) | "Join now" deep-link to video session |
|
|
|
|
All timing is configurable per patient preference. Respect do-not-disturb hours.
|
|
|
|
### Reminder Content Template
|
|
|
|
```
|
|
Your appointment with Dr. Smith is on Feb 24 at 10:30 AM.
|
|
Location: Main Clinic, Room 204
|
|
Preparation: Please fast for 8 hours before your visit.
|
|
[View Details] [Reschedule] [Cancel]
|
|
```
|
|
|
|
### No-Show Follow-Up
|
|
|
|
- Auto-trigger outreach 15 min after missed appointment
|
|
- Sequence: Push notification -> SMS (30 min) -> Email (1h) -> Staff call (2h)
|
|
- Offer one-tap reschedule link in all communications
|
|
- Log no-show reason if patient responds
|
|
|
|
**Web:** Email templates (PHP Blade/Twig) + SMS via Twilio/Vonage API. Cron job triggers reminders at scheduled intervals.
|
|
|
|
```php
|
|
// PHP - Reminder scheduling
|
|
class AppointmentReminderService {
|
|
public function scheduleReminders(Appointment $appt): void {
|
|
$reminders = [
|
|
['offset' => '-48 hours', 'channel' => 'email'],
|
|
['offset' => '-24 hours', 'channel' => 'sms'],
|
|
['offset' => '-2 hours', 'channel' => 'push'],
|
|
];
|
|
foreach ($reminders as $r) {
|
|
ReminderQueue::create([
|
|
'appointment_id' => $appt->id,
|
|
'send_at' => Carbon::parse($appt->scheduled_at)->modify($r['offset']),
|
|
'channel' => $r['channel'],
|
|
'status' => 'pending',
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Android:** `WorkManager` for scheduled local notifications. Deep-link to appointment detail or telemedicine join screen.
|
|
|
|
```kotlin
|
|
class AppointmentReminderWorker(
|
|
context: Context, params: WorkerParameters
|
|
) : CoroutineWorker(context, params) {
|
|
|
|
override suspend fun doWork(): Result {
|
|
val appointmentId = inputData.getLong("appointment_id", -1)
|
|
val appointment = appointmentRepo.getById(appointmentId) ?: return Result.failure()
|
|
|
|
notificationManager.showAppointmentReminder(
|
|
title = "Upcoming Appointment",
|
|
body = "Your appointment with ${appointment.providerName} is in 2 hours",
|
|
deepLink = "healthapp://appointments/${appointment.id}"
|
|
)
|
|
return Result.success()
|
|
}
|
|
}
|
|
|
|
// Schedule reminders when appointment is booked
|
|
fun scheduleReminders(appointment: Appointment) {
|
|
val intervals = listOf(
|
|
Duration.ofHours(48) to "48h reminder",
|
|
Duration.ofHours(24) to "24h reminder",
|
|
Duration.ofHours(2) to "2h reminder"
|
|
)
|
|
intervals.forEach { (offset, tag) ->
|
|
val delay = Duration.between(
|
|
Instant.now(),
|
|
appointment.scheduledAt.toInstant().minus(offset)
|
|
)
|
|
if (delay.isNegative) return@forEach
|
|
val request = OneTimeWorkRequestBuilder<AppointmentReminderWorker>()
|
|
.setInitialDelay(delay.toMillis(), TimeUnit.MILLISECONDS)
|
|
.setInputData(workDataOf("appointment_id" to appointment.id))
|
|
.addTag("reminder_${appointment.id}_$tag")
|
|
.build()
|
|
WorkManager.getInstance(context).enqueue(request)
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Cross-Cutting Concerns
|
|
|
|
- **HIPAA:** All video/audio streams must be encrypted (TLS 1.3 + SRTP for WebRTC). Transcripts are PHI -- store encrypted, log access.
|
|
- **Audit:** Log every appointment create/update/cancel, every video session start/end, every document share.
|
|
- **Offline (Android):** Cache upcoming appointment list. Queue check-in actions for sync. Show "offline" indicator on telemedicine features.
|
|
- **Accessibility:** All calendar interactions keyboard-navigable. Screen reader announcements for status changes (`aria-live`). Touch targets minimum 48dp (Android) / 44px (Web).
|