feat: label AI-rescheduled appointments and add manual confirm option

- Add movedByAi boolean column to Appointment table (default false)
- reschedule-graph: set movedByAi=true when AI moves an appointment
- PATCH /api/appointments/:id/confirm endpoint clears the movedByAi flag
- Schedule grid: show teal AI badge on appointment cards where movedByAi=true
- Right-click context menu: 'Manually Confirmed' removes the AI badge via the confirm endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-11 19:23:36 -04:00
parent 1ff843bc79
commit 5e3cfef52b
76 changed files with 212 additions and 12 deletions

View File

@@ -29,6 +29,8 @@ import {
MessageSquare,
Clock,
ExternalLink,
Bot,
UserCheck,
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { Calendar } from "@/components/ui/calendar";
@@ -87,6 +89,7 @@ interface ScheduledAppointment {
patientInsuranceProvider: string | null;
hasProcedures: boolean;
hasClaimWithNumber: boolean;
movedByAi?: boolean;
staffId: number;
date: string | Date;
startTime: string | Date;
@@ -595,6 +598,7 @@ export default function AppointmentsPage() {
patientInsuranceProvider,
hasProcedures: !!(apt as any).hasProcedures,
hasClaimWithNumber: !!(apt as any).hasClaimWithNumber,
movedByAi: !!(apt as any).movedByAi,
staffId,
status: apt.status ?? null,
date: formatLocalDate(apt.date),
@@ -854,6 +858,15 @@ export default function AppointmentsPage() {
<div className="font-bold truncate flex items-center gap-1">
<Move className="h-3 w-3" />
{appointment.patientName}
{appointment.movedByAi && (
<span
className="ml-1 inline-flex items-center gap-0.5 rounded bg-teal-600 px-1 py-0.5 text-[10px] font-semibold text-white leading-none"
title="Rescheduled by AI — pending human confirmation"
>
<Bot className="h-2.5 w-2.5" />
AI
</span>
)}
</div>
<div className="truncate">
{appointment.type?.startsWith("other:")
@@ -977,6 +990,16 @@ export default function AppointmentsPage() {
console.log(`Opening clinic notes for appointment: ${appointmentId}`);
};
const handleManualConfirm = async (appointmentId: number) => {
try {
await apiRequest("PATCH", `/api/appointments/${appointmentId}/confirm`);
queryClient.invalidateQueries({ queryKey: qkAppointmentsDay(formattedSelectedDate) });
toast({ title: "Confirmed", description: "AI-moved label removed. Appointment manually confirmed." });
} catch (err: any) {
toast({ title: "Error", description: err?.message ?? "Failed to confirm appointment.", variant: "destructive" });
}
};
const handleCheckAllEligibilities = async () => {
if (!user) {
toast({
@@ -1596,6 +1619,14 @@ export default function AppointmentsPage() {
Chat
</span>
</Item>
{/* Manually Confirmed — only relevant for AI-moved appointments */}
<Item onClick={({ props }) => handleManualConfirm(props.appointmentId)}>
<span className="flex items-center gap-2 text-teal-700">
<UserCheck className="h-4 w-4" />
Manually Confirmed
</span>
</Item>
</Menu>
{/* Main Content */}