feat: chat window, preferred language, insurance contact, and AI call eligibility

- Schedule: right-click Chat option opens floating SMS chat window
- Chat window: SMS template selector with appointment date/time pre-filled
- Chat window: office name and phone pulled from Settings > Office Contact
- Chat window: Preferred Language selector (English, Spanish, Portuguese,
  Mandarin, Cantonese, Arabic, Haitian Creole) with fully translated templates
  and locale-aware date/time formatting
- Patient form: Preferred Language field (add/edit), default English
- Settings > Office Contact: added Dental Office Name field
- Settings > Advanced: Insurance Contact page (CRUD — company name + phone)
- Prisma schema: preferredLanguage on Patient, officeName on OfficeContact,
  new InsuranceContact model
- Patient management: Upload Patient Document moved below Patient Records
- Insurance Eligibility: AI Call Insurance collapsible section; insurance
  company and phone auto-populated from saved Insurance Contacts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-07 16:42:37 -04:00
parent dd0df4a435
commit 16429320fa
16 changed files with 977 additions and 115 deletions

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
@@ -10,7 +10,14 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { CheckCircle, LoaderCircleIcon } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { CheckCircle, LoaderCircleIcon, Bot, PhoneCall, ChevronDown, ChevronUp } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { useToast } from "@/hooks/use-toast";
import { PatientTable } from "@/components/patients/patient-table";
@@ -57,6 +64,25 @@ export default function InsuranceStatusPage() {
const [isCheckingEligibilityClaimsPreAuth, setIsCheckingEligibilityClaimsPreAuth] =
useState(false);
// AI Call Insurance section
const [aiCallOpen, setAiCallOpen] = useState(false);
const [aiSelectedContactId, setAiSelectedContactId] = useState<string>("");
const [aiCallNotes, setAiCallNotes] = useState("");
type InsuranceContact = { id: number; name: string; phoneNumber?: string | null };
const { data: insuranceContacts = [] } = useQuery<InsuranceContact[]>({
queryKey: ["/api/insurance-contacts"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/insurance-contacts");
if (!res.ok) return [];
return res.json();
},
});
const selectedInsuranceContact = insuranceContacts.find(
(c) => String(c.id) === aiSelectedContactId
) ?? null;
// PDF preview modal state
const [previewOpen, setPreviewOpen] = useState(false);
const [previewPdfId, setPreviewPdfId] = useState<number | null>(null);
@@ -742,6 +768,119 @@ export default function InsuranceStatusPage() {
</CardContent>
</Card>
{/* AI Call Insurance */}
<Card>
<CardHeader
className="cursor-pointer select-none"
onClick={() => setAiCallOpen((o) => !o)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="h-9 w-9 rounded-lg bg-violet-100 flex items-center justify-center flex-shrink-0">
<Bot className="h-5 w-5 text-violet-600" />
</div>
<div>
<CardTitle className="text-base">AI Call Insurance</CardTitle>
<CardDescription className="mt-0.5">
Use AI to call the insurance company and check eligibility automatically
</CardDescription>
</div>
</div>
{aiCallOpen
? <ChevronUp className="h-4 w-4 text-muted-foreground" />
: <ChevronDown className="h-4 w-4 text-muted-foreground" />}
</div>
</CardHeader>
{aiCallOpen && (
<CardContent className="space-y-5 pt-0">
{/* Patient context banner */}
{selectedPatient && (
<div className="flex items-center gap-2 text-sm bg-violet-50 border border-violet-200 rounded-md px-3 py-2">
<Bot className="h-4 w-4 text-violet-500 flex-shrink-0" />
<span>
Selected patient:{" "}
<span className="font-medium">
{selectedPatient.firstName} {selectedPatient.lastName}
</span>
{selectedPatient.insuranceId && (
<> &mdash; Member ID: <span className="font-medium">{selectedPatient.insuranceId}</span></>
)}
</span>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label>Insurance Company</Label>
{insuranceContacts.length === 0 ? (
<p className="text-sm text-muted-foreground italic py-2">
No insurance contacts saved.{" "}
<a href="/settings/insurancecontact" className="underline text-violet-600">
Add one in Settings Insurance Contact
</a>
</p>
) : (
<Select
value={aiSelectedContactId}
onValueChange={setAiSelectedContactId}
>
<SelectTrigger>
<SelectValue placeholder="Select insurance company…" />
</SelectTrigger>
<SelectContent>
{insuranceContacts.map((c) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
<div className="space-y-1.5">
<Label>Phone Number</Label>
<Input
readOnly
value={selectedInsuranceContact?.phoneNumber ?? ""}
placeholder="Auto-filled from insurance contact"
className="bg-gray-50 text-gray-600 cursor-default"
/>
</div>
<div className="space-y-1.5 sm:col-span-2">
<Label htmlFor="ai-call-notes">
Additional Notes for AI{" "}
<span className="text-muted-foreground font-normal">(Optional)</span>
</Label>
<Input
id="ai-call-notes"
placeholder="e.g. Ask about dental benefits and annual maximum"
value={aiCallNotes}
onChange={(e) => setAiCallNotes(e.target.value)}
/>
</div>
</div>
<div className="flex items-center gap-3">
<Button
disabled
className="gap-2 bg-violet-600 hover:bg-violet-700 text-white opacity-60 cursor-not-allowed"
>
<PhoneCall className="h-4 w-4" />
Start AI Call
</Button>
<p className="text-xs text-muted-foreground italic">
{selectedInsuranceContact
? `Will call ${selectedInsuranceContact.name}${selectedInsuranceContact.phoneNumber ? ` at ${selectedInsuranceContact.phoneNumber}` : ""} — AI calling logic coming soon`
: "Select an insurance company to begin — AI calling logic coming soon"}
</p>
</div>
</CardContent>
)}
</Card>
{/* Patients Table */}
<Card>
<CardHeader>