feat: group schedule toolbar; add Consent, New Patient Form Link, Consent Templates placeholders
- Schedule page: toolbar grouped into Appointment, Consent, Eligibility, Claim, Communication and Download sections; new Consent button (stub) - Patients page: New Patient Form Link button (stub) - Settings: Consent Templates sidebar entry and placeholder section Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import {
|
|||||||
KeyRound,
|
KeyRound,
|
||||||
Copy,
|
Copy,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
|
FileSignature,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState, useEffect } from "react";
|
||||||
@@ -273,6 +274,11 @@ export function Sidebar() {
|
|||||||
path: "/settings/npi",
|
path: "/settings/npi",
|
||||||
icon: <Stethoscope className="h-4 w-4 text-gray-400" />,
|
icon: <Stethoscope className="h-4 w-4 text-gray-400" />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Consent Templates",
|
||||||
|
path: "/settings/consenttemplates",
|
||||||
|
icon: <FileSignature className="h-4 w-4 text-gray-400" />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Program Bridge",
|
name: "Program Bridge",
|
||||||
path: "/settings/programs",
|
path: "/settings/programs",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
Bot,
|
Bot,
|
||||||
UserCheck,
|
UserCheck,
|
||||||
|
FileSignature,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
@@ -1531,6 +1532,11 @@ export default function AppointmentsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Consent: logic to be defined later.
|
||||||
|
const handleConsent = () => {
|
||||||
|
toast({ title: "Consent", description: "Consent feature coming soon." });
|
||||||
|
};
|
||||||
|
|
||||||
const handleDownloadClaimPdfs = async () => {
|
const handleDownloadClaimPdfs = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
setIsDownloadingClaimPdfs(true);
|
setIsDownloadingClaimPdfs(true);
|
||||||
@@ -1865,7 +1871,7 @@ export default function AppointmentsPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex flex-col gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">
|
<h1 className="text-3xl font-bold tracking-tight">
|
||||||
Appointment Schedule
|
Appointment Schedule
|
||||||
@@ -1875,291 +1881,347 @@ export default function AppointmentsPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 flex-wrap">
|
<div className="flex items-start gap-3 flex-wrap">
|
||||||
<Button
|
{/* Appointment group */}
|
||||||
onClick={() => {
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
setEditingAppointment(undefined);
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
setIsAddModalOpen(true);
|
Appointment
|
||||||
}}
|
</span>
|
||||||
disabled={isLoading}
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
>
|
<Button
|
||||||
<Plus className="h-4 w-4" />
|
onClick={() => {
|
||||||
New Appointment
|
setEditingAppointment(undefined);
|
||||||
</Button>
|
setIsAddModalOpen(true);
|
||||||
|
|
||||||
{/* Check Eligibility for Column section */}
|
|
||||||
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleCheckAllEligibilities()}
|
|
||||||
disabled={isLoading || isCheckingAllElig || selectedStaffColumns.size === 0}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{isCheckingAllElig ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
|
||||||
Checking...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Shield className="h-4 w-4 mr-1" />
|
|
||||||
Check Eligibility for Column
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{staffMembers.map((staff, index) => (
|
|
||||||
<label
|
|
||||||
key={staff.id}
|
|
||||||
className="flex items-center gap-1 cursor-pointer select-none"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
|
||||||
checked={selectedStaffColumns.has(Number(staff.id))}
|
|
||||||
onChange={() => toggleStaffColumn(Number(staff.id))}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Claim for Column section */}
|
|
||||||
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleClaimForColumn()}
|
|
||||||
disabled={isLoading || isClaimingColumn || selectedClaimColumns.size === 0}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{isClaimingColumn ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
|
||||||
Submitting...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<FileCheck className="h-4 w-4 mr-1" />
|
|
||||||
Claim for Column
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{staffMembers.map((staff, index) => (
|
|
||||||
<label
|
|
||||||
key={staff.id}
|
|
||||||
className="flex items-center gap-1 cursor-pointer select-none"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
|
||||||
checked={selectedClaimColumns.has(Number(staff.id))}
|
|
||||||
onChange={() => toggleClaimColumn(Number(staff.id))}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Claim for Column with AI section */}
|
|
||||||
<div className="flex flex-wrap items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleClaimForColumnWithAi()}
|
|
||||||
disabled={isLoading || isClaimingAiColumn || selectedClaimAiColumns.size === 0}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{isClaimingAiColumn ? (
|
|
||||||
<>
|
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
|
||||||
Submitting...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Bot className="h-4 w-4 mr-1" />
|
|
||||||
Claim/Preauth for Column with AI
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{staffMembers.map((staff, index) => (
|
|
||||||
<label
|
|
||||||
key={staff.id}
|
|
||||||
className="flex items-center gap-1 cursor-pointer select-none"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
|
||||||
checked={selectedClaimAiColumns.has(Number(staff.id))}
|
|
||||||
onChange={() => toggleClaimAiColumn(Number(staff.id))}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
|
||||||
<Switch
|
|
||||||
id="auto-confirm-toggle"
|
|
||||||
checked={autoConfirmEnabled}
|
|
||||||
onCheckedChange={(v) => {
|
|
||||||
setAutoConfirmEnabled(v);
|
|
||||||
localStorage.setItem("ai_claim_auto_confirm", v ? "1" : "0");
|
|
||||||
}}
|
}}
|
||||||
/>
|
disabled={isLoading}
|
||||||
<Label htmlFor="auto-confirm-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
|
>
|
||||||
Auto Confirm
|
<Plus className="h-4 w-4" />
|
||||||
</Label>
|
New Appointment
|
||||||
</div>
|
</Button>
|
||||||
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
|
||||||
<Switch
|
|
||||||
id="auto-claim-toggle"
|
|
||||||
checked={autoClaimEnabled}
|
|
||||||
onCheckedChange={setAutoClaimEnabled}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="auto-claim-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
|
|
||||||
Auto Claim
|
|
||||||
</Label>
|
|
||||||
{autoClaimEnabled && (
|
|
||||||
<Select value={autoClaimTime} onValueChange={setAutoClaimTime}>
|
|
||||||
<SelectTrigger className="h-8 w-28 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Array.from({ length: 24 }, (_, h) => {
|
|
||||||
const value = `${String(h).padStart(2, "0")}:00`;
|
|
||||||
const label = h === 0 ? "12 AM" : h < 12 ? `${h} AM` : h === 12 ? "12 PM" : `${h - 12} PM`;
|
|
||||||
return (
|
|
||||||
<SelectItem key={value} value={value}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text Reminder for Column section */}
|
{/* Consent group */}
|
||||||
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
<Button
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
onClick={() => handleSendRemindersForColumn()}
|
Consent
|
||||||
disabled={isLoading || isSendingReminders || selectedReminderColumns.size === 0}
|
</span>
|
||||||
size="sm"
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
>
|
<Button
|
||||||
{isSendingReminders ? (
|
onClick={() => handleConsent()}
|
||||||
<>
|
disabled={isLoading}
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
|
||||||
Sending...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<MessageSquare className="h-4 w-4 mr-1" />
|
|
||||||
Text Reminder for Column
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{staffMembers.map((staff, index) => (
|
|
||||||
<label
|
|
||||||
key={staff.id}
|
|
||||||
className="flex items-center gap-1 cursor-pointer select-none"
|
|
||||||
>
|
>
|
||||||
<input
|
<FileSignature className="h-4 w-4" />
|
||||||
type="checkbox"
|
Consent
|
||||||
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
</Button>
|
||||||
checked={selectedReminderColumns.has(Number(staff.id))}
|
|
||||||
onChange={() => toggleReminderColumn(Number(staff.id))}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
<div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="switch"
|
|
||||||
aria-checked={reminderAiFollowUp}
|
|
||||||
onClick={() => setReminderAiFollowUp((v) => !v)}
|
|
||||||
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${reminderAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${reminderAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Reschedule for Column section */}
|
{/* Eligibility group */}
|
||||||
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
<Button
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
onClick={() => handleSendRescheduleForColumn()}
|
Eligibility
|
||||||
disabled={isLoading || isSendingReschedule || selectedRescheduleColumns.size === 0}
|
</span>
|
||||||
size="sm"
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
>
|
{/* Check Eligibility for Column section */}
|
||||||
{isSendingReschedule ? (
|
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
<>
|
<Button
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
onClick={() => handleCheckAllEligibilities()}
|
||||||
Sending...
|
disabled={isLoading || isCheckingAllElig || selectedStaffColumns.size === 0}
|
||||||
</>
|
size="sm"
|
||||||
) : (
|
>
|
||||||
<>
|
{isCheckingAllElig ? (
|
||||||
<MessageSquare className="h-4 w-4 mr-1" />
|
<>
|
||||||
Reschedule for Column
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
</>
|
Checking...
|
||||||
)}
|
</>
|
||||||
</Button>
|
) : (
|
||||||
{staffMembers.map((staff, index) => (
|
<>
|
||||||
<label
|
<Shield className="h-4 w-4 mr-1" />
|
||||||
key={staff.id}
|
Check Eligibility for Column
|
||||||
className="flex items-center gap-1 cursor-pointer select-none"
|
</>
|
||||||
>
|
)}
|
||||||
<input
|
</Button>
|
||||||
type="checkbox"
|
{staffMembers.map((staff, index) => (
|
||||||
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
<label
|
||||||
checked={selectedRescheduleColumns.has(Number(staff.id))}
|
key={staff.id}
|
||||||
onChange={() => toggleRescheduleColumn(Number(staff.id))}
|
className="flex items-center gap-1 cursor-pointer select-none"
|
||||||
/>
|
>
|
||||||
<span className="text-sm font-medium">
|
<input
|
||||||
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
type="checkbox"
|
||||||
</span>
|
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
||||||
</label>
|
checked={selectedStaffColumns.has(Number(staff.id))}
|
||||||
))}
|
onChange={() => toggleStaffColumn(Number(staff.id))}
|
||||||
<div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
|
/>
|
||||||
<button
|
<span className="text-sm font-medium">
|
||||||
type="button"
|
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
||||||
role="switch"
|
</span>
|
||||||
aria-checked={rescheduleAiFollowUp}
|
</label>
|
||||||
onClick={() => setRescheduleAiFollowUp((v) => !v)}
|
))}
|
||||||
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${rescheduleAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
|
</div>
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${rescheduleAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Filled Claim/Preauth Form: ADA forms (e.g. BCBS) for the selected date */}
|
{/* Claim group */}
|
||||||
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
<Button
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
onClick={() => handleDownloadClaimPdfs()}
|
Claim
|
||||||
disabled={isLoading || isDownloadingClaimPdfs}
|
</span>
|
||||||
size="sm"
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
>
|
{/* Claim for Column with AI section */}
|
||||||
{isDownloadingClaimPdfs ? (
|
<div className="flex flex-wrap items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
<>
|
<Button
|
||||||
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
onClick={() => handleClaimForColumnWithAi()}
|
||||||
Downloading...
|
disabled={isLoading || isClaimingAiColumn || selectedClaimAiColumns.size === 0}
|
||||||
</>
|
size="sm"
|
||||||
) : (
|
>
|
||||||
<>
|
{isClaimingAiColumn ? (
|
||||||
<Download className="h-4 w-4 mr-1" />
|
<>
|
||||||
Download Filled Claim/Preauth Form
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
</>
|
Submitting...
|
||||||
)}
|
</>
|
||||||
</Button>
|
) : (
|
||||||
|
<>
|
||||||
|
<Bot className="h-4 w-4 mr-1" />
|
||||||
|
Claim/Preauth for Column with AI
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{staffMembers.map((staff, index) => (
|
||||||
|
<label
|
||||||
|
key={staff.id}
|
||||||
|
className="flex items-center gap-1 cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
||||||
|
checked={selectedClaimAiColumns.has(Number(staff.id))}
|
||||||
|
onChange={() => toggleClaimAiColumn(Number(staff.id))}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
||||||
|
<Switch
|
||||||
|
id="auto-confirm-toggle"
|
||||||
|
checked={autoConfirmEnabled}
|
||||||
|
onCheckedChange={(v) => {
|
||||||
|
setAutoConfirmEnabled(v);
|
||||||
|
localStorage.setItem("ai_claim_auto_confirm", v ? "1" : "0");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-confirm-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
|
||||||
|
Auto Confirm
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 border-l pl-3 ml-1">
|
||||||
|
<Switch
|
||||||
|
id="auto-claim-toggle"
|
||||||
|
checked={autoClaimEnabled}
|
||||||
|
onCheckedChange={setAutoClaimEnabled}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-claim-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
|
||||||
|
Auto Claim
|
||||||
|
</Label>
|
||||||
|
{autoClaimEnabled && (
|
||||||
|
<Select value={autoClaimTime} onValueChange={setAutoClaimTime}>
|
||||||
|
<SelectTrigger className="h-8 w-28 text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Array.from({ length: 24 }, (_, h) => {
|
||||||
|
const value = `${String(h).padStart(2, "0")}:00`;
|
||||||
|
const label = h === 0 ? "12 AM" : h < 12 ? `${h} AM` : h === 12 ? "12 PM" : `${h - 12} PM`;
|
||||||
|
return (
|
||||||
|
<SelectItem key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Claim for Column section */}
|
||||||
|
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleClaimForColumn()}
|
||||||
|
disabled={isLoading || isClaimingColumn || selectedClaimColumns.size === 0}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isClaimingColumn ? (
|
||||||
|
<>
|
||||||
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
|
Submitting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FileCheck className="h-4 w-4 mr-1" />
|
||||||
|
Claim for Column
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{staffMembers.map((staff, index) => (
|
||||||
|
<label
|
||||||
|
key={staff.id}
|
||||||
|
className="flex items-center gap-1 cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
||||||
|
checked={selectedClaimColumns.has(Number(staff.id))}
|
||||||
|
onChange={() => toggleClaimColumn(Number(staff.id))}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Communication group */}
|
||||||
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
|
Communication
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{/* Text Reminder for Column section */}
|
||||||
|
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleSendRemindersForColumn()}
|
||||||
|
disabled={isLoading || isSendingReminders || selectedReminderColumns.size === 0}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isSendingReminders ? (
|
||||||
|
<>
|
||||||
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
|
Sending...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<MessageSquare className="h-4 w-4 mr-1" />
|
||||||
|
Text Reminder for Column
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{staffMembers.map((staff, index) => (
|
||||||
|
<label
|
||||||
|
key={staff.id}
|
||||||
|
className="flex items-center gap-1 cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
||||||
|
checked={selectedReminderColumns.has(Number(staff.id))}
|
||||||
|
onChange={() => toggleReminderColumn(Number(staff.id))}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
<div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={reminderAiFollowUp}
|
||||||
|
onClick={() => setReminderAiFollowUp((v) => !v)}
|
||||||
|
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${reminderAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${reminderAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reschedule for Column section */}
|
||||||
|
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleSendRescheduleForColumn()}
|
||||||
|
disabled={isLoading || isSendingReschedule || selectedRescheduleColumns.size === 0}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isSendingReschedule ? (
|
||||||
|
<>
|
||||||
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
|
Sending...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<MessageSquare className="h-4 w-4 mr-1" />
|
||||||
|
Reschedule for Column
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{staffMembers.map((staff, index) => (
|
||||||
|
<label
|
||||||
|
key={staff.id}
|
||||||
|
className="flex items-center gap-1 cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-gray-400 accent-teal-600"
|
||||||
|
checked={selectedRescheduleColumns.has(Number(staff.id))}
|
||||||
|
onChange={() => toggleRescheduleColumn(Number(staff.id))}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
<div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={rescheduleAiFollowUp}
|
||||||
|
onClick={() => setRescheduleAiFollowUp((v) => !v)}
|
||||||
|
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${rescheduleAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${rescheduleAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Download group */}
|
||||||
|
<div className="flex flex-col gap-1 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 p-2">
|
||||||
|
<span className="px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
|
Download
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{/* Download Filled Claim/Preauth Form: ADA forms (e.g. BCBS) for the selected date */}
|
||||||
|
<div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleDownloadClaimPdfs()}
|
||||||
|
disabled={isLoading || isDownloadingClaimPdfs}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isDownloadingClaimPdfs ? (
|
||||||
|
<>
|
||||||
|
<LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
|
||||||
|
Downloading...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Download className="h-4 w-4 mr-1" />
|
||||||
|
Download Filled Claim/Preauth Form
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Camera,
|
Camera,
|
||||||
NotebookPen,
|
NotebookPen,
|
||||||
FileCheck2,
|
FileCheck2,
|
||||||
|
Link2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import {
|
import {
|
||||||
@@ -398,6 +399,18 @@ export default function PatientsPage() {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
New Patient
|
New Patient
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// New patient form link: logic to be defined later.
|
||||||
|
toast({ title: "New Patient Form Link", description: "Coming soon." });
|
||||||
|
}}
|
||||||
|
className="gap-1"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Link2 className="h-4 w-4" />
|
||||||
|
New Patient Form Link
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type SectionId =
|
|||||||
| "account"
|
| "account"
|
||||||
| "credentials"
|
| "credentials"
|
||||||
| "npi"
|
| "npi"
|
||||||
|
| "consenttemplates"
|
||||||
| "programs"
|
| "programs"
|
||||||
| "twilio"
|
| "twilio"
|
||||||
| "telnyx"
|
| "telnyx"
|
||||||
@@ -256,6 +257,17 @@ export default function SettingsPage() {
|
|||||||
case "npi":
|
case "npi":
|
||||||
return <NpiProviderTable />;
|
return <NpiProviderTable />;
|
||||||
|
|
||||||
|
case "consenttemplates":
|
||||||
|
// Consent templates: logic to be defined later.
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900">Consent Templates</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">Coming soon.</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
case "programs":
|
case "programs":
|
||||||
return <ProgramBridgeTable />;
|
return <ProgramBridgeTable />;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user