import React, { useState, useEffect } from "react"; import { Eye, EyeOff } from "lucide-react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { useParams } from "wouter"; import { StaffTable } from "@/components/staffs/staff-table"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent } from "@/components/ui/card"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { StaffForm } from "@/components/staffs/staff-form"; import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; import { CredentialTable } from "@/components/settings/insuranceCredTable"; import { useAuth } from "@/hooks/use-auth"; import { NpiProviderTable } from "@/components/settings/npiProviderTable"; import { ProgramBridgeTable } from "@/components/settings/program-bridge-table"; import { TwilioSettingsCard } from "@/components/settings/twilio-settings-card"; import { AiSettingsCard } from "@/components/settings/ai-settings-card"; import { OfficeHoursCard } from "@/components/settings/office-hours-card"; import { OfficeContactCard } from "@/components/settings/office-contact-card"; import { ProcedureTimeslotCard } from "@/components/settings/procedure-timeslot-card"; import { InsuranceContactCard } from "@/components/settings/insurance-contact-card"; import { AiChatSettingsCard } from "@/components/settings/ai-chat-settings-card"; import { SeleniumSettingsCard } from "@/components/settings/selenium-settings-card"; export default function SettingsPage() { const { toast } = useToast(); const { user: currentUser } = useAuth(); const isAdmin = currentUser?.username === "admin"; const params = useParams(); const section = params.section ?? "staff"; // Staff state const [modalOpen, setModalOpen] = useState(false); const [editingStaff, setEditingStaff] = useState(null); const [isDeleteStaffOpen, setIsDeleteStaffOpen] = useState(false); const [currentStaff, setCurrentStaff] = useState(undefined); // User management state const [newUsername, setNewUsername] = useState(""); const [newPassword, setNewPassword] = useState(""); const [showNewUserPassword, setShowNewUserPassword] = useState(false); const [showAdminPassword, setShowAdminPassword] = useState(false); const [usernameUser, setUsernameUser] = useState(""); useEffect(() => { if (currentUser?.username) setUsernameUser(currentUser.username); }, [currentUser]); // ── Staff ────────────────────────────────────────────────────── const { data: staff = [], isLoading: staffLoading, isError: staffError, error: staffErrorMsg } = useQuery({ queryKey: ["/api/staffs/"], queryFn: async () => { const res = await apiRequest("GET", "/api/staffs/"); if (!res.ok) throw new Error("Failed to fetch staff"); return res.json(); }, staleTime: 1000 * 60 * 5, }); const addStaffMutate = useMutation({ mutationFn: async (newStaff) => { const res = await apiRequest("POST", "/api/staffs/", newStaff); if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to add staff"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Added" }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); const updateStaffMutate = useMutation({ mutationFn: async ({ id, updatedFields }) => { const res = await apiRequest("PUT", `/api/staffs/${id}`, updatedFields); if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to update staff"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Updated" }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); const deleteStaffMutation = useMutation({ mutationFn: async (id) => { const res = await apiRequest("DELETE", `/api/staffs/${id}`); if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.message || "Failed to delete staff"); } return id; }, onSuccess: () => { setIsDeleteStaffOpen(false); queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Removed" }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); useEffect(() => { if (addStaffMutate.status === "success" || updateStaffMutate.status === "success") setModalOpen(false); }, [addStaffMutate.status, updateStaffMutate.status]); // ── Users ────────────────────────────────────────────────────── const { data: allUsers = [], refetch: refetchUsers } = useQuery({ queryKey: ["/api/users/list"], queryFn: async () => { const res = await apiRequest("GET", "/api/users/list"); if (!res.ok) return []; return res.json(); }, enabled: false, }); useEffect(() => { if (isAdmin) refetchUsers(); }, [isAdmin]); const addUserMutation = useMutation({ mutationFn: async (data) => { const res = await apiRequest("POST", "/api/users/", data); if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.error || "Failed to create user"); } return res.json(); }, onSuccess: () => { setNewUsername(""); setNewPassword(""); refetchUsers(); toast({ title: "User Created" }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); const deleteUserMutation = useMutation({ mutationFn: async (id) => { const res = await apiRequest("DELETE", `/api/users/${id}`); if (!res.ok) throw new Error("Failed to delete user"); }, onSuccess: () => { refetchUsers(); toast({ title: "User Deleted" }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); const updateUserMutate = useMutation({ mutationFn: async (updates) => { if (!currentUser?.id) throw new Error("User not loaded"); const res = await apiRequest("PUT", `/api/users/${currentUser.id}`, updates); if (!res.ok) { const e = await res.json().catch(() => null); throw new Error(e?.error || "Failed to update user"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/users/"] }); toast({ title: "Updated", description: "Your profile has been updated." }); }, onError: (e) => toast({ title: "Error", description: e?.message, variant: "destructive" }), }); // ── Section renderer ─────────────────────────────────────────── const renderSection = () => { switch (section) { case "staff": return (
{ setEditingStaff(null); setModalOpen(true); }} onEdit={(s) => { setEditingStaff(s); setModalOpen(true); }} onDelete={(s) => { setCurrentStaff(s); setIsDeleteStaffOpen(true); }} onView={(s) => alert(`Viewing staff member:\n${s.name} (${s.email || "No email"})`)}/> {staffError &&

{staffErrorMsg?.message || "Failed to load staff data."}

} { if (currentStaff?.id) deleteStaffMutation.mutate(currentStaff.id); }} onCancel={() => setIsDeleteStaffOpen(false)} entityName={currentStaff?.name}/>
); case "users": return (

Manage Users

{allUsers.length === 0 &&

No users found.

} {allUsers.map((u) => (
{u.username} {u.username !== "admin" && ()}
))}
{ e.preventDefault(); if (!newUsername.trim() || !newPassword.trim()) return; addUserMutation.mutate({ username: newUsername.trim(), password: newPassword.trim() }); }}>

Add New User

setNewUsername(e.target.value)} className="mt-1 p-2 border rounded w-full" placeholder="Enter username" required/>
setNewPassword(e.target.value)} className="p-2 border rounded w-full pr-10" placeholder="••••••••" required/>
); case "account": return (

Account Settings

{ e.preventDefault(); const formData = new FormData(e.currentTarget); const password = formData.get("password")?.toString().trim() || undefined; updateUserMutate.mutate({ ...(isAdmin ? {} : { username: usernameUser?.trim() || undefined }), password: password || undefined }); }}>
setUsernameUser(e.target.value)} className="mt-1 p-2 border rounded w-full disabled:bg-gray-100 disabled:cursor-not-allowed disabled:text-gray-500" disabled={isAdmin}/> {isAdmin &&

Admin username cannot be changed.

}

Leave blank to keep current password.

); case "credentials": return ; case "npi": return ; case "programs": return ; case "twilio": return ; case "ai": return ; case "aichat": return ; case "officehours": return ; case "officecontact": return ; case "proceduretimeslot": return ; case "insurancecontact": return ; case "selenium": return ; default: return null; } }; return (<> {renderSection()} {/* Staff add/edit modal */} {modalOpen && (

{editingStaff ? "Edit Staff" : "Add Staff"}

{ if (editingStaff) { if (!editingStaff.id) return; updateStaffMutate.mutate({ id: editingStaff.id, updatedFields: formData }); } else { addStaffMutate.mutate(formData); } }} onCancel={() => setModalOpen(false)} isLoading={addStaffMutate.status === "pending" || updateStaffMutate.status === "pending"}/>
)} ); }