savenschedule working and delete issue in pateint page, add patint issue on dashboard and patient page
This commit is contained in:
@@ -200,6 +200,7 @@ router.delete(
|
|||||||
await storage.deletePatient(patientId);
|
await storage.deletePatient(patientId);
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Delete patient error:", error);
|
||||||
res.status(500).json({ message: "Failed to delete patient" });
|
res.status(500).json({ message: "Failed to delete patient" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Edit, Eye, MoreVertical } from "lucide-react";
|
import { Delete, Edit, Eye, MoreVertical } from "lucide-react";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
@@ -39,9 +39,10 @@ interface PatientTableProps {
|
|||||||
patients: Patient[];
|
patients: Patient[];
|
||||||
onEdit: (patient: Patient) => void;
|
onEdit: (patient: Patient) => void;
|
||||||
onView: (patient: Patient) => void;
|
onView: (patient: Patient) => void;
|
||||||
|
onDelete: (patient: Patient) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTableProps) {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const patientsPerPage = 5;
|
const patientsPerPage = 5;
|
||||||
|
|
||||||
@@ -167,6 +168,17 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<div className="flex justify-end space-x-2">
|
<div className="flex justify-end space-x-2">
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
onDelete(patient)
|
||||||
|
}
|
||||||
|
className="text-red-600 hover:text-red-900"
|
||||||
|
aria-label="Delete Staff"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<Delete/>
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Delete, Edit } from "lucide-react";
|
||||||
|
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ interface StaffTableProps {
|
|||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
onEdit: (staff: Staff) => void;
|
onEdit: (staff: Staff) => void;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (staff: Staff) => void;
|
||||||
onView: (staff: Staff) => void;
|
onView: (staff: Staff) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,22 +150,27 @@ export function StaffTable({
|
|||||||
{formattedDate}
|
{formattedDate}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
||||||
<button
|
<Button
|
||||||
onClick={() => staff.id !== undefined && onEdit(staff)}
|
|
||||||
className="text-blue-600 hover:text-blue-900"
|
|
||||||
aria-label="Edit Staff"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
staff.id !== undefined && onDelete(staff.id)
|
staff !== undefined && onDelete(staff)
|
||||||
}
|
}
|
||||||
className="text-red-600 hover:text-red-900"
|
className="text-red-600 hover:text-red-900"
|
||||||
aria-label="Delete Staff"
|
aria-label="Delete Staff"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
>
|
>
|
||||||
Delete
|
<Delete/>
|
||||||
</button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => staff.id !== undefined && onEdit(staff)}
|
||||||
|
className="text-blue-600 hover:text-blue-900"
|
||||||
|
aria-label="Edit Staff"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|||||||
36
apps/Frontend/src/components/ui/deleteDialog.tsx
Normal file
36
apps/Frontend/src/components/ui/deleteDialog.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export const DeleteConfirmationDialog = ({
|
||||||
|
isOpen,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
patientName,
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
patientName?: string;
|
||||||
|
}) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
|
||||||
|
<div className="bg-white p-6 rounded-md shadow-md w-[90%] max-w-md">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Confirm Deletion</h2>
|
||||||
|
<p>Are you sure you want to delete <strong>{patientName}</strong>?</p>
|
||||||
|
<div className="mt-6 flex justify-end space-x-4">
|
||||||
|
<button
|
||||||
|
className="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300"
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700"
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState, useRef} from "react";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
|
|
||||||
//creating types out of schema auto generated.
|
//creating types out of schema auto generated.
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||||
@@ -82,10 +83,17 @@ const updatePatientSchema = (
|
|||||||
|
|
||||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||||
|
|
||||||
|
// Type for the ref to access modal methods
|
||||||
|
type AddPatientModalRef = {
|
||||||
|
shouldSchedule: boolean;
|
||||||
|
navigateToSchedule: (patientId: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
|
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
|
||||||
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
|
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
|
||||||
|
const [isDeletePatientOpen, setIsDeletePatientOpen] = useState(false);
|
||||||
const [isAddAppointmentOpen, setIsAddAppointmentOpen] = useState(false);
|
const [isAddAppointmentOpen, setIsAddAppointmentOpen] = useState(false);
|
||||||
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
|
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
|
||||||
undefined
|
undefined
|
||||||
@@ -96,6 +104,9 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const addPatientModalRef = useRef<AddPatientModalRef | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch patients
|
// Fetch patients
|
||||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
||||||
@@ -176,6 +187,31 @@ export default function Dashboard() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const deletePatientMutation = useMutation({
|
||||||
|
mutationFn: async (id: number) => {
|
||||||
|
const res = await apiRequest("DELETE", `/api/patients/${id}`);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
setIsDeletePatientOpen(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Patient deleted successfully!",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error)
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: `Failed to delete patient: ${error.message}`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const toggleMobileMenu = () => {
|
const toggleMobileMenu = () => {
|
||||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||||
};
|
};
|
||||||
@@ -216,6 +252,28 @@ export default function Dashboard() {
|
|||||||
setIsViewPatientOpen(true);
|
setIsViewPatientOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeletePatient = (patient: Patient) => {
|
||||||
|
setCurrentPatient(patient);
|
||||||
|
setIsDeletePatientOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDeletePatient = async () => {
|
||||||
|
if (currentPatient) {
|
||||||
|
deletePatientMutation.mutate(currentPatient.id);
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "No patient selected for deletion.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
isLoadingPatients ||
|
||||||
|
addPatientMutation.isPending ||
|
||||||
|
updatePatientMutation.isPending;
|
||||||
|
|
||||||
// Create appointment mutation
|
// Create appointment mutation
|
||||||
const createAppointmentMutation = useMutation({
|
const createAppointmentMutation = useMutation({
|
||||||
mutationFn: async (appointment: InsertAppointment) => {
|
mutationFn: async (appointment: InsertAppointment) => {
|
||||||
@@ -491,19 +549,27 @@ export default function Dashboard() {
|
|||||||
patients={filteredPatients}
|
patients={filteredPatients}
|
||||||
onEdit={handleEditPatient}
|
onEdit={handleEditPatient}
|
||||||
onView={handleViewPatient}
|
onView={handleViewPatient}
|
||||||
|
onDelete={handleDeletePatient}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
isOpen={isDeletePatientOpen}
|
||||||
|
onConfirm={handleConfirmDeletePatient}
|
||||||
|
onCancel={() => setIsDeletePatientOpen(false)}
|
||||||
|
patientName={currentPatient?.name}
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add/Edit Patient Modal */}
|
{/* Add/Edit Patient Modal */}
|
||||||
<AddPatientModal
|
<AddPatientModal
|
||||||
|
ref={addPatientModalRef}
|
||||||
open={isAddPatientOpen}
|
open={isAddPatientOpen}
|
||||||
onOpenChange={setIsAddPatientOpen}
|
onOpenChange={setIsAddPatientOpen}
|
||||||
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient}
|
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient}
|
||||||
isLoading={
|
isLoading={isLoading}
|
||||||
addPatientMutation.isPending || updatePatientMutation.isPending
|
|
||||||
}
|
|
||||||
patient={currentPatient}
|
patient={currentPatient}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas
|
|||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
const PatientSchema = (
|
const PatientSchema = (
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
@@ -64,6 +72,7 @@ export default function PatientsPage() {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
|
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
|
||||||
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
|
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
|
||||||
|
const [isDeletePatientOpen, setIsDeletePatientOpen] = useState(false);
|
||||||
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
|
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
@@ -152,12 +161,35 @@ export default function PatientsPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deletePatientMutation = useMutation({
|
||||||
|
mutationFn: async (id: number) => {
|
||||||
|
const res = await apiRequest("DELETE", `/api/patients/${id}`);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
setIsDeletePatientOpen(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Patient deleted successfully!",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error);
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: `Failed to delete patient: ${error.message}`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const toggleMobileMenu = () => {
|
const toggleMobileMenu = () => {
|
||||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddPatient = (patient: InsertPatient) => {
|
const handleAddPatient = (patient: InsertPatient) => {
|
||||||
// Add userId to the patient data
|
|
||||||
if (user) {
|
if (user) {
|
||||||
addPatientMutation.mutate({
|
addPatientMutation.mutate({
|
||||||
...patient,
|
...patient,
|
||||||
@@ -193,6 +225,23 @@ export default function PatientsPage() {
|
|||||||
setIsViewPatientOpen(true);
|
setIsViewPatientOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeletePatient = (patient: Patient) => {
|
||||||
|
setCurrentPatient(patient);
|
||||||
|
setIsDeletePatientOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDeletePatient = async () => {
|
||||||
|
if (currentPatient) {
|
||||||
|
deletePatientMutation.mutate(currentPatient.id);
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "No patient selected for deletion.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isLoadingPatients ||
|
isLoadingPatients ||
|
||||||
addPatientMutation.isPending ||
|
addPatientMutation.isPending ||
|
||||||
@@ -370,10 +419,178 @@ export default function PatientsPage() {
|
|||||||
patients={filteredPatients}
|
patients={filteredPatients}
|
||||||
onEdit={handleEditPatient}
|
onEdit={handleEditPatient}
|
||||||
onView={handleViewPatient}
|
onView={handleViewPatient}
|
||||||
|
onDelete={handleDeletePatient}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
isOpen={isDeletePatientOpen}
|
||||||
|
onConfirm={handleConfirmDeletePatient}
|
||||||
|
onCancel={() => setIsDeletePatientOpen(false)}
|
||||||
|
patientName={currentPatient?.name}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* View Patient Modal */}
|
||||||
|
<Dialog open={isViewPatientOpen} onOpenChange={setIsViewPatientOpen}>
|
||||||
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Patient Details</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Complete information about the patient.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{currentPatient && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
|
||||||
|
{currentPatient.firstName.charAt(0)}
|
||||||
|
{currentPatient.lastName.charAt(0)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold">
|
||||||
|
{currentPatient.firstName} {currentPatient.lastName}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">
|
||||||
|
Personal Information
|
||||||
|
</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Date of Birth:</span>{" "}
|
||||||
|
{new Date(
|
||||||
|
currentPatient.dateOfBirth
|
||||||
|
).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Gender:</span>{" "}
|
||||||
|
{currentPatient.gender.charAt(0).toUpperCase() +
|
||||||
|
currentPatient.gender.slice(1)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Status:</span>{" "}
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
currentPatient.status === "active"
|
||||||
|
? "text-green-600"
|
||||||
|
: "text-amber-600"
|
||||||
|
} font-medium`}
|
||||||
|
>
|
||||||
|
{currentPatient.status.charAt(0).toUpperCase() +
|
||||||
|
currentPatient.status.slice(1)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">
|
||||||
|
Contact Information
|
||||||
|
</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Phone:</span>{" "}
|
||||||
|
{currentPatient.phone}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Email:</span>{" "}
|
||||||
|
{currentPatient.email || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Address:</span>{" "}
|
||||||
|
{currentPatient.address ? (
|
||||||
|
<>
|
||||||
|
{currentPatient.address}
|
||||||
|
{currentPatient.city && `, ${currentPatient.city}`}
|
||||||
|
{currentPatient.zipCode &&
|
||||||
|
` ${currentPatient.zipCode}`}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"N/A"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Insurance</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Provider:</span>{" "}
|
||||||
|
{currentPatient.insuranceProvider
|
||||||
|
? currentPatient.insuranceProvider === "delta"
|
||||||
|
? "Delta Dental"
|
||||||
|
: currentPatient.insuranceProvider === "metlife"
|
||||||
|
? "MetLife"
|
||||||
|
: currentPatient.insuranceProvider === "cigna"
|
||||||
|
? "Cigna"
|
||||||
|
: currentPatient.insuranceProvider === "aetna"
|
||||||
|
? "Aetna"
|
||||||
|
: currentPatient.insuranceProvider
|
||||||
|
: "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">ID:</span>{" "}
|
||||||
|
{currentPatient.insuranceId || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Group Number:</span>{" "}
|
||||||
|
{currentPatient.groupNumber || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Policy Holder:</span>{" "}
|
||||||
|
{currentPatient.policyHolder || "Self"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">
|
||||||
|
Medical Information
|
||||||
|
</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Allergies:</span>{" "}
|
||||||
|
{currentPatient.allergies || "None reported"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Medical Conditions:</span>{" "}
|
||||||
|
{currentPatient.medicalConditions || "None reported"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-2 pt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsViewPatientOpen(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsViewPatientOpen(false);
|
||||||
|
handleEditPatient(currentPatient);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Patient
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
|
||||||
{/* Add/Edit Patient Modal */}
|
{/* Add/Edit Patient Modal */}
|
||||||
<AddPatientModal
|
<AddPatientModal
|
||||||
ref={addPatientModalRef}
|
ref={addPatientModalRef}
|
||||||
@@ -382,17 +599,6 @@ export default function PatientsPage() {
|
|||||||
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient}
|
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
patient={currentPatient}
|
patient={currentPatient}
|
||||||
// Pass extracted info as a separate prop to avoid triggering edit mode
|
|
||||||
extractedInfo={
|
|
||||||
!currentPatient && extractedInfo
|
|
||||||
? {
|
|
||||||
firstName: extractedInfo.firstName || "",
|
|
||||||
lastName: extractedInfo.lastName || "",
|
|
||||||
dateOfBirth: extractedInfo.dateOfBirth || "",
|
|
||||||
insuranceId: extractedInfo.insuranceId || "",
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { StaffForm } from "@/components/staffs/staff-form";
|
import { StaffForm } from "@/components/staffs/staff-form";
|
||||||
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
|
|
||||||
// Correctly infer Staff type from zod schema
|
// Correctly infer Staff type from zod schema
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||||
@@ -120,6 +121,7 @@ export default function SettingsPage() {
|
|||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
setIsDeleteStaffOpen(false);
|
||||||
queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] });
|
queryClient.invalidateQueries({ queryKey: ["/api/staffs/"] });
|
||||||
toast({
|
toast({
|
||||||
title: "Staff Removed",
|
title: "Staff Removed",
|
||||||
@@ -188,14 +190,28 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
}, [isAddSuccess, isUpdateSuccess]);
|
}, [isAddSuccess, isUpdateSuccess]);
|
||||||
|
|
||||||
// Delete staff
|
const [isDeleteStaffOpen, setIsDeleteStaffOpen] = useState(false);
|
||||||
const handleDeleteStaff = (id: number) => {
|
const [currentStaff, setCurrentStaff] = useState<Staff | undefined>(
|
||||||
if (confirm("Are you sure you want to delete this staff member?")) {
|
undefined
|
||||||
deleteStaffMutation.mutate(id);
|
);
|
||||||
|
|
||||||
|
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",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// View staff handler (just an alert for now)
|
|
||||||
const handleViewStaff = (staff: Staff) =>
|
const handleViewStaff = (staff: Staff) =>
|
||||||
alert(
|
alert(
|
||||||
`Viewing staff member:\n${staff.name} (${staff.email || "No email"})`
|
`Viewing staff member:\n${staff.name} (${staff.email || "No email"})`
|
||||||
@@ -227,6 +243,13 @@ export default function SettingsPage() {
|
|||||||
{(error as Error)?.message || "Failed to load staff data."}
|
{(error as Error)?.message || "Failed to load staff data."}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
isOpen={isDeleteStaffOpen}
|
||||||
|
onConfirm={handleConfirmDeleteStaff}
|
||||||
|
onCancel={() => setIsDeleteStaffOpen(false)}
|
||||||
|
patientName={currentStaff?.name}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user