import React, { useState, useEffect } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; 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 { Staff } from "@repo/db/types"; type SafeUser = { id: number; username: string; role: "ADMIN" | "USER" }; export default function SettingsPage() { const { toast } = useToast(); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); // Modal and editing staff state const [modalOpen, setModalOpen] = useState(false); const [credentialModalOpen, setCredentialModalOpen] = useState(false); const [editingStaff, setEditingStaff] = useState(null); const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev); // Fetch staff data const { data: staff = [], isLoading, isError, error, } = 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, // 5 minutes cache }); // Add Staff mutation const addStaffMutate = useMutation< Staff, // Return type Error, // Error type Omit // Variables >({ mutationFn: async (newStaff: Omit) => { const res = await apiRequest("POST", "/api/staffs/", newStaff); if (!res.ok) { const errorData = await res.json().catch(() => null); throw new Error(errorData?.message || "Failed to add staff"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Added", description: "Staff member added successfully.", variant: "default", }); }, onError: (error: any) => { toast({ title: "Error", description: error?.message || "Failed to add staff", variant: "destructive", }); }, }); // Update Staff mutation const updateStaffMutate = useMutation< Staff, Error, { id: number; updatedFields: Partial } >({ mutationFn: async ({ id, updatedFields, }: { id: number; updatedFields: Partial; }) => { const res = await apiRequest("PUT", `/api/staffs/${id}`, updatedFields); if (!res.ok) { const errorData = await res.json().catch(() => null); throw new Error(errorData?.message || "Failed to update staff"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Updated", description: "Staff member updated successfully.", variant: "default", }); }, onError: (error: any) => { toast({ title: "Error", description: error?.message || "Failed to update staff", variant: "destructive", }); }, }); // Delete Staff mutation const deleteStaffMutation = useMutation({ mutationFn: async (id: number) => { const res = await apiRequest("DELETE", `/api/staffs/${id}`); if (!res.ok) { const errorData = await res.json().catch(() => null); throw new Error(errorData?.message || "Failed to delete staff"); } return id; }, onSuccess: () => { setIsDeleteStaffOpen(false); queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] }); toast({ title: "Staff Removed", description: "Staff member deleted.", variant: "default", }); }, onError: (error: any) => { toast({ title: "Error", description: error?.message || "Failed to delete staff", variant: "destructive", }); }, }); // Extract mutation states for modal control and loading const isAdding = addStaffMutate.status === "pending"; const isAddSuccess = addStaffMutate.status === "success"; const isUpdating = updateStaffMutate.status === "pending"; const isUpdateSuccess = updateStaffMutate.status === "success"; // Open Add modal const openAddStaffModal = () => { setEditingStaff(null); setModalOpen(true); }; // Open Edit modal const openEditStaffModal = (staff: Staff) => { setEditingStaff(staff); setModalOpen(true); }; // Handle form submit for Add or Edit const handleFormSubmit = (formData: Omit) => { if (editingStaff) { // Editing existing staff if (editingStaff.id === undefined) { toast({ title: "Error", description: "Staff ID is missing", variant: "destructive", }); return; } updateStaffMutate.mutate({ id: editingStaff.id, updatedFields: formData, }); } else { addStaffMutate.mutate(formData); } }; const handleModalCancel = () => { setModalOpen(false); }; // Close modal on successful add/update useEffect(() => { if (isAddSuccess || isUpdateSuccess) { setModalOpen(false); } }, [isAddSuccess, isUpdateSuccess]); const [isDeleteStaffOpen, setIsDeleteStaffOpen] = useState(false); const [currentStaff, setCurrentStaff] = useState( undefined ); const handleDeleteStaff = (staff: Staff) => { setCurrentStaff(staff); setIsDeleteStaffOpen(true); }; const handleConfirmDeleteStaff = async () => { if (currentStaff?.id) { deleteStaffMutation.mutate(currentStaff.id); } else { toast({ title: "Error", description: "No Staff selected for deletion.", variant: "destructive", }); } }; const handleViewStaff = (staff: Staff) => alert( `Viewing staff member:\n${staff.name} (${staff.email || "No email"})` ); // --- Users control (list, add, edit password, delete) --- const { data: usersList = [], isLoading: usersLoading, isError: usersError, error: usersErrorObj, } = useQuery({ queryKey: ["/api/users/list"], queryFn: async () => { const res = await apiRequest("GET", "/api/users/list"); if (!res.ok) throw new Error("Failed to fetch users"); return res.json(); }, staleTime: 1000 * 60 * 2, }); const addUserMutate = useMutation({ mutationFn: async (data) => { const res = await apiRequest("POST", "/api/users/", data); if (!res.ok) { const err = await res.json().catch(() => null); throw new Error(err?.error || "Failed to add user"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/users/list"] }); setAddUserModalOpen(false); toast({ title: "User Added", description: "User created successfully.", variant: "default" }); }, onError: (e: any) => { toast({ title: "Error", description: e?.message || "Failed to add user", variant: "destructive" }); }, }); const updateUserPasswordMutate = useMutation({ mutationFn: async ({ id, password }) => { const res = await apiRequest("PUT", `/api/users/${id}`, { password }); if (!res.ok) { const err = await res.json().catch(() => null); throw new Error(err?.error || "Failed to update password"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/users/list"] }); setEditPasswordUser(null); toast({ title: "Password Updated", description: "Password changed successfully.", variant: "default" }); }, onError: (e: any) => { toast({ title: "Error", description: e?.message || "Failed to update password", variant: "destructive" }); }, }); const deleteUserMutate = useMutation({ mutationFn: async (id) => { const res = await apiRequest("DELETE", `/api/users/${id}`); if (!res.ok) { const err = await res.json().catch(() => null); throw new Error(err?.error || "Failed to delete user"); } return id; }, onSuccess: () => { setUserToDelete(null); queryClient.invalidateQueries({ queryKey: ["/api/users/list"] }); toast({ title: "User Removed", description: "User deleted.", variant: "default" }); }, onError: (e: any) => { toast({ title: "Error", description: e?.message || "Failed to delete user", variant: "destructive" }); }, }); const [addUserModalOpen, setAddUserModalOpen] = useState(false); const [editPasswordUser, setEditPasswordUser] = useState(null); const [userToDelete, setUserToDelete] = useState(null); // MANAGE USER (current user profile) const [usernameUser, setUsernameUser] = useState(""); //fetch user const { user } = useAuth(); useEffect(() => { if (user?.username) { setUsernameUser(user.username); } }, [user]); //update user mutation const updateUserMutate = useMutation({ mutationFn: async ( updates: Partial<{ username: string; password: string }> ) => { if (!user?.id) throw new Error("User not loaded"); const res = await apiRequest("PUT", `/api/users/${user.id}`, updates); if (!res.ok) { const errorData = await res.json().catch(() => null); throw new Error(errorData?.error || "Failed to update user"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/users/"] }); toast({ title: "Updated", description: "Your profile has been updated.", variant: "default", }); }, onError: (err: any) => { toast({ title: "Error", description: err?.message || "Failed to update user", variant: "destructive", }); }, }); return (
{isError && (

{(error as Error)?.message || "Failed to load staff data."}

)} setIsDeleteStaffOpen(false)} entityName={currentStaff?.name} />
{/* Modal Overlay */} {modalOpen && (

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

)} {/* Users control section */}

User Accounts

{usersLoading && ( )} {usersError && ( )} {!usersLoading && !usersError && usersList.filter((u) => u.id !== user?.id).length === 0 && ( )} {!usersLoading && usersList.filter((u) => u.id !== user?.id).map((u) => ( ))}
Username Role Actions
Loading users...
{(usersErrorObj as Error)?.message}
No other users.
{u.username} {u.role}
{/* User Setting section (current user profile) */}

Admin Setting

{ e.preventDefault(); const formData = new FormData(e.currentTarget); const password = formData.get("password")?.toString().trim() || undefined; updateUserMutate.mutate({ username: usernameUser?.trim() || undefined, password: password || undefined, }); }} >
setUsernameUser(e.target.value)} className="mt-1 p-2 border rounded w-full" />

Leave blank to keep current password.

{/* Add User modal */} {addUserModalOpen && (

Add User

{ e.preventDefault(); const form = e.currentTarget; const username = (form.querySelector('[name="new-username"]') as HTMLInputElement)?.value?.trim(); const password = (form.querySelector('[name="new-password"]') as HTMLInputElement)?.value; const role = (form.querySelector('[name="new-role"]') as HTMLSelectElement)?.value as "ADMIN" | "USER"; if (!username || !password) { toast({ title: "Error", description: "Username and password are required.", variant: "destructive" }); return; } addUserMutate.mutate({ username, password, role: role || "USER" }); }} className="space-y-4" >
)} {/* Edit password modal */} {editPasswordUser && (

Change password for {editPasswordUser.username}

{ e.preventDefault(); const form = e.currentTarget; const password = (form.querySelector('[name="edit-password"]') as HTMLInputElement)?.value; if (!password?.trim()) { toast({ title: "Error", description: "Password is required.", variant: "destructive" }); return; } updateUserPasswordMutate.mutate({ id: editPasswordUser.id, password }); }} className="space-y-4" >
)} userToDelete && deleteUserMutate.mutate(userToDelete.id)} onCancel={() => setUserToDelete(null)} entityName={userToDelete?.username} /> {/* Credential Section */}
); }