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

View File

@@ -73,6 +73,8 @@ interface AppointmentFormProps {
appointment?: Appointment;
patients: Patient[];
onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
onDelete?: (id: number) => void;
onOpenChange?: (open: boolean) => void;
isLoading?: boolean;
}
@@ -80,18 +82,19 @@ export function AppointmentForm({
appointment,
patients,
onSubmit,
onDelete,
onOpenChange,
isLoading = false,
}: AppointmentFormProps) {
const { user } = useAuth();
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current?.focus();
}, 50); // small delay ensures content is mounted
return () => clearTimeout(timeout);
}, []);
useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current?.focus();
}, 50); // small delay ensures content is mounted
return () => clearTimeout(timeout);
}, []);
const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } =
useQuery<Staff[]>({
@@ -177,18 +180,19 @@ useEffect(() => {
const [filteredPatients, setFilteredPatients] = useState(patients);
useEffect(() => {
if (!debouncedSearchTerm.trim()) {
setFilteredPatients(patients);
} else {
const term = debouncedSearchTerm.toLowerCase();
setFilteredPatients(
patients.filter((p) =>
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`.toLowerCase().includes(term)
)
);
}
}, [debouncedSearchTerm, patients]);
if (!debouncedSearchTerm.trim()) {
setFilteredPatients(patients);
} else {
const term = debouncedSearchTerm.toLowerCase();
setFilteredPatients(
patients.filter((p) =>
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`
.toLowerCase()
.includes(term)
)
);
}
}, [debouncedSearchTerm, patients]);
// Force form field values to update and clean up storage
useEffect(() => {
@@ -304,8 +308,7 @@ useEffect(() => {
</SelectTrigger>
</FormControl>
<SelectContent>
<div className="p-2" onKeyDown={(e) => e.stopPropagation()}>
<div className="p-2" onKeyDown={(e) => e.stopPropagation()}>
<Input
ref={inputRef}
placeholder="Search patients..."
@@ -313,28 +316,32 @@ useEffect(() => {
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={(e) => {
const navKeys = ['ArrowDown', 'ArrowUp', 'Enter'];
if (!navKeys.includes(e.key)) {
e.stopPropagation(); // Only stop keys that affect select state
}
}}
const navKeys = ["ArrowDown", "ArrowUp", "Enter"];
if (!navKeys.includes(e.key)) {
e.stopPropagation(); // Only stop keys that affect select state
}
}}
/>
</div>
<div className="max-h-60 overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/30">
{filteredPatients.length > 0 ? (
filteredPatients.map((patient) => (
<SelectItem
key={patient.id}
value={patient.id.toString()}
><div className="flex flex-col">
<span className="font-medium">
{patient.firstName} {patient.lastName}
</span>
<span className="text-xs text-muted-foreground">
DOB: {new Date(patient.dateOfBirth).toLocaleDateString()} {patient.phone}
</span>
</div>
>
<div className="flex flex-col">
<span className="font-medium">
{patient.firstName} {patient.lastName}
</span>
<span className="text-xs text-muted-foreground">
DOB:{" "}
{new Date(
patient.dateOfBirth
).toLocaleDateString()}{" "}
{patient.phone}
</span>
</div>
</SelectItem>
))
) : (
@@ -589,6 +596,22 @@ useEffect(() => {
<Button type="submit" disabled={isLoading} className="w-full">
{appointment ? "Update Appointment" : "Create Appointment"}
</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>
</div>

View File

@@ -2,12 +2,12 @@ export const DeleteConfirmationDialog = ({
isOpen,
onConfirm,
onCancel,
patientName,
entityName,
}: {
isOpen: boolean;
onConfirm: () => void;
onCancel: () => void;
patientName?: string;
entityName?: string;
}) => {
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="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>
<p>
Are you sure you want to delete <strong>{entityName}</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"

View File

@@ -37,6 +37,7 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { Menu, Item, useContextMenu } from "react-contexify";
import "react-contexify/ReactContexify.css";
import { useLocation } from "wouter";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
@@ -110,6 +111,11 @@ export default function AppointmentsPage() {
const [selectedDate, setSelectedDate] = useState<Date>(startOfToday());
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [location] = useLocation();
const [confirmDeleteState, setConfirmDeleteState] = useState<{
open: boolean;
appointmentId?: number;
appointmentTitle?: string;
}>({ open: false });
// Create context menu hook
const { show } = useContextMenu({
@@ -139,7 +145,7 @@ export default function AppointmentsPage() {
// Assign colors cycling through the list
const staffMembers = staffMembersRaw.map((staff, index) => ({
...staff,
color: colors[index % colors.length] || "bg-gray-400",
}));
@@ -350,6 +356,7 @@ export default function AppointmentsPage() {
// Invalidate both appointments and patients queries
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
setConfirmDeleteState({ open: false });
},
onError: (error: Error) => {
toast({
@@ -397,11 +404,25 @@ export default function AppointmentsPage() {
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) => {
if (confirm("Are you sure you want to delete this appointment?")) {
deleteAppointmentMutation.mutate(id);
}
const appointment = appointments.find((a) => a.id === 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 = () => {
@@ -911,6 +932,14 @@ export default function AppointmentsPage() {
}
appointment={editingAppointment}
patients={patients}
onDelete={handleDeleteAppointment}
/>
<DeleteConfirmationDialog
isOpen={confirmDeleteState.open}
onConfirm={handleConfirmDelete}
onCancel={() => setConfirmDeleteState({ open: false })}
entityName={confirmDeleteState.appointmentTitle}
/>
{/* Claim Services Modal */}

View File

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