feat: appointment card colors by status, MassHealth badge, date nav controls
- Default card color: light (bg-slate-100 / dark text) - Blue card when procedures selected, gray when claim has a number - Status badge: green/red from appointment or patient-level MassHealth status - Solid dot badge (removed ring), overflow-visible to prevent corner clipping - Date nav: << < [today circle] > >> for week/day jumps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ export function PatientStatusBadge({
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
aria-label={`Patient status: ${label}`}
|
||||
className={`inline-block rounded-full ring-2 ring-white shadow ${className}`}
|
||||
className={`inline-block rounded-full shadow ${className}`}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { addDays, startOfToday, addMinutes } from "date-fns";
|
||||
import { addDays, addWeeks, startOfToday, addMinutes } from "date-fns";
|
||||
import {
|
||||
parseLocalDate,
|
||||
formatLocalDate,
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
Plus,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
Move,
|
||||
Trash2,
|
||||
CreditCard,
|
||||
@@ -75,6 +77,10 @@ interface ScheduledAppointment {
|
||||
patientId: number;
|
||||
patientName: string;
|
||||
eligibilityStatus: PatientStatus;
|
||||
patientStatus: PatientStatus;
|
||||
patientInsuranceProvider: string | null;
|
||||
hasProcedures: boolean;
|
||||
hasClaimWithNumber: boolean;
|
||||
staffId: number;
|
||||
date: string | Date;
|
||||
startTime: string | Date;
|
||||
@@ -83,6 +89,23 @@ interface ScheduledAppointment {
|
||||
type: string;
|
||||
}
|
||||
|
||||
function appointmentCardColor(apt: ScheduledAppointment): string {
|
||||
if (apt.hasClaimWithNumber) return "bg-gray-500 text-white";
|
||||
if (apt.hasProcedures) return "bg-blue-500 text-white";
|
||||
return "bg-slate-100 text-gray-700 border-slate-300";
|
||||
}
|
||||
|
||||
function resolveAppointmentBadgeStatus(apt: ScheduledAppointment): PatientStatus {
|
||||
if (apt.eligibilityStatus === "ACTIVE") return "ACTIVE";
|
||||
if (apt.eligibilityStatus === "INACTIVE") return "INACTIVE";
|
||||
const isMassHealth = apt.patientInsuranceProvider?.toLowerCase().includes("masshealth");
|
||||
if (apt.eligibilityStatus === "UNKNOWN" && isMassHealth) {
|
||||
if (apt.patientStatus === "ACTIVE") return "ACTIVE";
|
||||
if (apt.patientStatus === "INACTIVE") return "INACTIVE";
|
||||
}
|
||||
return apt.eligibilityStatus ?? "UNKNOWN";
|
||||
}
|
||||
|
||||
// Define a unique ID for the appointment context menu
|
||||
const APPOINTMENT_CONTEXT_MENU_ID = "appointment-context-menu";
|
||||
|
||||
@@ -454,6 +477,8 @@ export default function AppointmentsPage() {
|
||||
: "Unknown Patient";
|
||||
|
||||
const eligibilityStatus = (apt as any).eligibilityStatus as PatientStatus;
|
||||
const patientStatus = (patient as any)?.status as PatientStatus ?? "UNKNOWN";
|
||||
const patientInsuranceProvider = (patient as any)?.insuranceProvider as string | null ?? null;
|
||||
|
||||
const staffId = Number(apt.staffId ?? 1);
|
||||
|
||||
@@ -470,6 +495,10 @@ export default function AppointmentsPage() {
|
||||
...apt,
|
||||
patientName,
|
||||
eligibilityStatus,
|
||||
patientStatus,
|
||||
patientInsuranceProvider,
|
||||
hasProcedures: !!(apt as any).hasProcedures,
|
||||
hasClaimWithNumber: !!(apt as any).hasClaimWithNumber,
|
||||
staffId,
|
||||
status: apt.status ?? null,
|
||||
date: formatLocalDate(apt.date),
|
||||
@@ -649,7 +678,7 @@ export default function AppointmentsPage() {
|
||||
return (
|
||||
<div
|
||||
ref={drag as unknown as React.RefObject<HTMLDivElement>} // Type assertion to make TypeScript happy
|
||||
className={`${staff.color} border border-white shadow-md text-white rounded p-1 text-xs h-full overflow-hidden cursor-move relative ${
|
||||
className={`${appointmentCardColor(appointment)} border shadow-md rounded p-1 text-xs h-full overflow-visible cursor-move relative ${
|
||||
isDragging ? "opacity-50" : "opacity-100"
|
||||
}`}
|
||||
style={{ fontWeight: 500 }}
|
||||
@@ -668,7 +697,7 @@ export default function AppointmentsPage() {
|
||||
onContextMenu={(e) => handleContextMenu(e, appointment.id ?? 0)}
|
||||
>
|
||||
<PatientStatusBadge
|
||||
status={appointment.eligibilityStatus ?? "UNKNOWN"}
|
||||
status={resolveAppointmentBadgeStatus(appointment)}
|
||||
className="pointer-events-auto" // ensure tooltip works
|
||||
size={30} // bump size up from 10 → 14
|
||||
/>
|
||||
@@ -1247,24 +1276,52 @@ export default function AppointmentsPage() {
|
||||
<div className="w-full overflow-x-auto bg-white rounded-md shadow">
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
{/* << prev week */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
title="Previous week"
|
||||
onClick={() => setSelectedDate(addWeeks(selectedDate, -1))}
|
||||
>
|
||||
<ChevronsLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
{/* < prev day */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
title="Previous day"
|
||||
onClick={() => setSelectedDate(addDays(selectedDate, -1))}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h2 className="text-xl font-semibold">
|
||||
{/* today circle */}
|
||||
<Button
|
||||
variant="outline"
|
||||
title="Go to today"
|
||||
onClick={() => setSelectedDate(startOfToday())}
|
||||
className="rounded-full w-auto px-3 font-semibold text-sm"
|
||||
>
|
||||
{formattedSelectedDate}
|
||||
</h2>
|
||||
</Button>
|
||||
{/* > next day */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
title="Next day"
|
||||
onClick={() => setSelectedDate(addDays(selectedDate, 1))}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
{/* >> next week */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
title="Next week"
|
||||
onClick={() => setSelectedDate(addWeeks(selectedDate, 1))}
|
||||
>
|
||||
<ChevronsRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Top button with popover calendar */}
|
||||
|
||||
Reference in New Issue
Block a user