done
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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({
|
||||
@@ -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 */}
|
||||
|
||||
@@ -248,7 +248,7 @@ export default function SettingsPage() {
|
||||
isOpen={isDeleteStaffOpen}
|
||||
onConfirm={handleConfirmDeleteStaff}
|
||||
onCancel={() => setIsDeleteStaffOpen(false)}
|
||||
patientName={currentStaff?.name}
|
||||
entityName={currentStaff?.name}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user