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:
@@ -439,6 +439,7 @@ async function moveAppointment(
|
||||
startTime: newStartTime,
|
||||
endTime: newEndTime,
|
||||
status: "scheduled",
|
||||
movedByAi: true,
|
||||
} as any);
|
||||
|
||||
return "ok";
|
||||
|
||||
@@ -417,6 +417,19 @@ router.put(
|
||||
}
|
||||
);
|
||||
|
||||
// Manually confirm an AI-moved appointment (clears the movedByAi flag)
|
||||
router.patch("/:id/confirm", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
if (isNaN(id)) return res.status(400).json({ message: "Invalid appointment ID" });
|
||||
const updated = await storage.updateAppointment(id, { movedByAi: false } as any);
|
||||
return res.json(updated);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return res.status(500).json({ message: msg });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete an appointment
|
||||
router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user