23 KiB
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.
@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
- Auto-generated visit summary -- AI transcription produces structured SOAP note draft
- Prescription issuance -- Provider reviews/signs e-prescriptions
- Follow-up scheduling -- Suggest next appointment with pre-filled context
- 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.
// 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.
@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.
<!-- 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 | 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 - 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.
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).