This commit is contained in:
2025-05-16 16:41:43 +05:30
parent f304cccc68
commit 2745e95b49
5 changed files with 103 additions and 44 deletions

View File

@@ -28,6 +28,7 @@ interface AddAppointmentModalProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onSubmit: (data: InsertAppointment | UpdateAppointment) => void; onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
onDelete?: (id: number) => void;
isLoading: boolean; isLoading: boolean;
appointment?: Appointment; appointment?: Appointment;
patients: Patient[]; patients: Patient[];
@@ -37,10 +38,12 @@ export function AddAppointmentModal({
open, open,
onOpenChange, onOpenChange,
onSubmit, onSubmit,
onDelete,
isLoading, isLoading,
appointment, appointment,
patients, patients,
}: AddAppointmentModalProps) { }: AddAppointmentModalProps) {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
@@ -58,6 +61,8 @@ export function AddAppointmentModal({
onOpenChange(false); onOpenChange(false);
}} }}
isLoading={isLoading} isLoading={isLoading}
onDelete={onDelete}
onOpenChange={onOpenChange}
/> />
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -73,6 +73,8 @@ interface AppointmentFormProps {
appointment?: Appointment; appointment?: Appointment;
patients: Patient[]; patients: Patient[];
onSubmit: (data: InsertAppointment | UpdateAppointment) => void; onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
onDelete?: (id: number) => void;
onOpenChange?: (open: boolean) => void;
isLoading?: boolean; isLoading?: boolean;
} }
@@ -80,6 +82,8 @@ export function AppointmentForm({
appointment, appointment,
patients, patients,
onSubmit, onSubmit,
onDelete,
onOpenChange,
isLoading = false, isLoading = false,
}: AppointmentFormProps) { }: AppointmentFormProps) {
const { user } = useAuth(); const { user } = useAuth();
@@ -92,7 +96,6 @@ useEffect(() => {
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
}, []); }, []);
const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } = const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } =
useQuery<Staff[]>({ useQuery<Staff[]>({
queryKey: ["/api/staffs/"], queryKey: ["/api/staffs/"],
@@ -183,13 +186,14 @@ useEffect(() => {
const term = debouncedSearchTerm.toLowerCase(); const term = debouncedSearchTerm.toLowerCase();
setFilteredPatients( setFilteredPatients(
patients.filter((p) => patients.filter((p) =>
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`.toLowerCase().includes(term) `${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`
.toLowerCase()
.includes(term)
) )
); );
} }
}, [debouncedSearchTerm, patients]); }, [debouncedSearchTerm, patients]);
// Force form field values to update and clean up storage // Force form field values to update and clean up storage
useEffect(() => { useEffect(() => {
if (parsedStoredData) { if (parsedStoredData) {
@@ -305,7 +309,6 @@ useEffect(() => {
</FormControl> </FormControl>
<SelectContent> <SelectContent>
<div className="p-2" onKeyDown={(e) => e.stopPropagation()}> <div className="p-2" onKeyDown={(e) => e.stopPropagation()}>
<Input <Input
ref={inputRef} ref={inputRef}
placeholder="Search patients..." placeholder="Search patients..."
@@ -313,7 +316,7 @@ useEffect(() => {
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
const navKeys = ['ArrowDown', 'ArrowUp', 'Enter']; const navKeys = ["ArrowDown", "ArrowUp", "Enter"];
if (!navKeys.includes(e.key)) { if (!navKeys.includes(e.key)) {
e.stopPropagation(); // Only stop keys that affect select state e.stopPropagation(); // Only stop keys that affect select state
} }
@@ -321,18 +324,22 @@ useEffect(() => {
/> />
</div> </div>
<div className="max-h-60 overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/30"> <div className="max-h-60 overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/30">
{filteredPatients.length > 0 ? ( {filteredPatients.length > 0 ? (
filteredPatients.map((patient) => ( filteredPatients.map((patient) => (
<SelectItem <SelectItem
key={patient.id} key={patient.id}
value={patient.id.toString()} value={patient.id.toString()}
><div className="flex flex-col"> >
<div className="flex flex-col">
<span className="font-medium"> <span className="font-medium">
{patient.firstName} {patient.lastName} {patient.firstName} {patient.lastName}
</span> </span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
DOB: {new Date(patient.dateOfBirth).toLocaleDateString()} {patient.phone} DOB:{" "}
{new Date(
patient.dateOfBirth
).toLocaleDateString()}{" "}
{patient.phone}
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
@@ -589,6 +596,22 @@ useEffect(() => {
<Button type="submit" disabled={isLoading} className="w-full"> <Button type="submit" disabled={isLoading} className="w-full">
{appointment ? "Update Appointment" : "Create Appointment"} {appointment ? "Update Appointment" : "Create Appointment"}
</Button> </Button>
{appointment?.id && onDelete && (
<Button
type="button"
onClick={() => {
onOpenChange?.(false); // 👈 Close the modal first
setTimeout(() => {
onDelete?.(appointment.id!);
}, 300); // 300ms is safe for most animations
}}
className="bg-red-600 text-white w-full rounded hover:bg-red-700"
>
Delete Appointment
</Button>
)}
</form> </form>
</Form> </Form>
</div> </div>

View File

@@ -2,12 +2,12 @@ export const DeleteConfirmationDialog = ({
isOpen, isOpen,
onConfirm, onConfirm,
onCancel, onCancel,
patientName, entityName,
}: { }: {
isOpen: boolean; isOpen: boolean;
onConfirm: () => void; onConfirm: () => void;
onCancel: () => void; onCancel: () => void;
patientName?: string; entityName?: string;
}) => { }) => {
if (!isOpen) return null; if (!isOpen) return null;
@@ -15,7 +15,9 @@ export const DeleteConfirmationDialog = ({
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50"> <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"> <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> <h2 className="text-xl font-semibold mb-4">Confirm Deletion</h2>
<p>Are you sure you want to delete <strong>{patientName}</strong>?</p> <p>
Are you sure you want to delete <strong>{entityName}</strong>?
</p>
<div className="mt-6 flex justify-end space-x-4"> <div className="mt-6 flex justify-end space-x-4">
<button <button
className="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300" className="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300"

View File

@@ -37,6 +37,7 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { Menu, Item, useContextMenu } from "react-contexify"; import { Menu, Item, useContextMenu } from "react-contexify";
import "react-contexify/ReactContexify.css"; import "react-contexify/ReactContexify.css";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
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>;
@@ -110,6 +111,11 @@ export default function AppointmentsPage() {
const [selectedDate, setSelectedDate] = useState<Date>(startOfToday()); const [selectedDate, setSelectedDate] = useState<Date>(startOfToday());
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [location] = useLocation(); const [location] = useLocation();
const [confirmDeleteState, setConfirmDeleteState] = useState<{
open: boolean;
appointmentId?: number;
appointmentTitle?: string;
}>({ open: false });
// Create context menu hook // Create context menu hook
const { show } = useContextMenu({ const { show } = useContextMenu({
@@ -350,6 +356,7 @@ export default function AppointmentsPage() {
// Invalidate both appointments and patients queries // Invalidate both appointments and patients queries
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] }); queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
setConfirmDeleteState({ open: false });
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast({ toast({
@@ -397,11 +404,25 @@ export default function AppointmentsPage() {
setIsAddModalOpen(true); setIsAddModalOpen(true);
}; };
// Handle delete appointment // When user confirms delete in dialog
const handleConfirmDelete = () => {
if (!confirmDeleteState.appointmentId) return;
deleteAppointmentMutation.mutate(confirmDeleteState.appointmentId);
};
const handleDeleteAppointment = (id: number) => { const handleDeleteAppointment = (id: number) => {
if (confirm("Are you sure you want to delete this appointment?")) { const appointment = appointments.find((a) => a.id === id);
deleteAppointmentMutation.mutate(id); if (!appointment) return;
}
// Find patient by patientId
const patient = patients.find((p) => p.id === appointment.patientId);
setConfirmDeleteState({
open: true,
appointmentId: id,
appointmentTitle: `${patient?.firstName ?? "Appointment"}`,
});
}; };
const toggleMobileMenu = () => { const toggleMobileMenu = () => {
@@ -911,6 +932,14 @@ export default function AppointmentsPage() {
} }
appointment={editingAppointment} appointment={editingAppointment}
patients={patients} patients={patients}
onDelete={handleDeleteAppointment}
/>
<DeleteConfirmationDialog
isOpen={confirmDeleteState.open}
onConfirm={handleConfirmDelete}
onCancel={() => setConfirmDeleteState({ open: false })}
entityName={confirmDeleteState.appointmentTitle}
/> />
{/* Claim Services Modal */} {/* Claim Services Modal */}

View File

@@ -248,7 +248,7 @@ export default function SettingsPage() {
isOpen={isDeleteStaffOpen} isOpen={isDeleteStaffOpen}
onConfirm={handleConfirmDeleteStaff} onConfirm={handleConfirmDeleteStaff}
onCancel={() => setIsDeleteStaffOpen(false)} onCancel={() => setIsDeleteStaffOpen(false)}
patientName={currentStaff?.name} entityName={currentStaff?.name}
/> />
</div> </div>
</CardContent> </CardContent>