first commit

This commit is contained in:
2025-05-08 21:27:29 +05:30
commit 230d5c89f0
343 changed files with 42391 additions and 0 deletions

View File

@@ -0,0 +1,462 @@
import { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { format } from "date-fns";
import { InsertAppointment, UpdateAppointment, Appointment, Patient } from "@shared/schema";
// Define staff members (should match those in appointments-page.tsx)
const staffMembers = [
{ id: "doctor1", name: "Dr. Kai Gao", role: "doctor" },
{ id: "doctor2", name: "Dr. Jane Smith", role: "doctor" },
{ id: "hygienist1", name: "Hygienist One", role: "hygienist" },
{ id: "hygienist2", name: "Hygienist Two", role: "hygienist" },
{ id: "hygienist3", name: "Hygienist Three", role: "hygienist" },
];
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { CalendarIcon, Clock } from "lucide-react";
import { cn } from "@/lib/utils";
// Create a schema for appointment validation
const appointmentSchema = z.object({
patientId: z.coerce.number().positive(),
title: z.string().optional(),
date: z.date({
required_error: "Appointment date is required",
}),
startTime: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, {
message: "Start time must be in format HH:MM",
}),
endTime: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/, {
message: "End time must be in format HH:MM",
}),
type: z.string().min(1, "Appointment type is required"),
notes: z.string().optional(),
status: z.string().default("scheduled"),
staff: z.string().default(staffMembers[0].id),
});
export type AppointmentFormValues = z.infer<typeof appointmentSchema>;
interface AppointmentFormProps {
appointment?: Appointment;
patients: Patient[];
onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
isLoading?: boolean;
}
export function AppointmentForm({
appointment,
patients,
onSubmit,
isLoading = false
}: AppointmentFormProps) {
// Get the stored data from session storage
const storedDataString = sessionStorage.getItem('newAppointmentData');
let parsedStoredData = null;
// Try to parse it if it exists
if (storedDataString) {
try {
parsedStoredData = JSON.parse(storedDataString);
console.log('Initial appointment data from storage:', parsedStoredData);
// LOG the specific time values for debugging
console.log('Time values in stored data:', {
startTime: parsedStoredData.startTime,
endTime: parsedStoredData.endTime
});
} catch (error) {
console.error('Error parsing stored appointment data:', error);
}
}
// Format the date and times for the form
const defaultValues: Partial<AppointmentFormValues> = appointment
? {
patientId: appointment.patientId,
title: appointment.title,
date: new Date(appointment.date),
startTime: appointment.startTime.slice(0, 5), // HH:MM from HH:MM:SS
endTime: appointment.endTime.slice(0, 5), // HH:MM from HH:MM:SS
type: appointment.type,
notes: appointment.notes || "",
status: appointment.status || "scheduled",
}
: parsedStoredData
? {
patientId: parsedStoredData.patientId,
date: new Date(parsedStoredData.date),
title: parsedStoredData.title || "",
startTime: parsedStoredData.startTime, // This should now be correctly applied
endTime: parsedStoredData.endTime,
type: parsedStoredData.type || "checkup",
status: parsedStoredData.status || "scheduled",
notes: parsedStoredData.notes || "",
staff: parsedStoredData.staff || staffMembers[0].id,
}
: {
date: new Date(),
title: "",
startTime: "09:00",
endTime: "09:30",
type: "checkup",
status: "scheduled",
staff: "doctor1",
};
const form = useForm<AppointmentFormValues>({
resolver: zodResolver(appointmentSchema),
defaultValues,
});
// Force form field values to update and clean up storage
useEffect(() => {
if (parsedStoredData) {
// Force-update the form with the stored values
console.log("Force updating form fields with:", parsedStoredData);
// Update form field values directly
if (parsedStoredData.startTime) {
form.setValue('startTime', parsedStoredData.startTime);
console.log(`Setting startTime to: ${parsedStoredData.startTime}`);
}
if (parsedStoredData.endTime) {
form.setValue('endTime', parsedStoredData.endTime);
console.log(`Setting endTime to: ${parsedStoredData.endTime}`);
}
if (parsedStoredData.staff) {
form.setValue('staff', parsedStoredData.staff);
}
if (parsedStoredData.date) {
form.setValue('date', new Date(parsedStoredData.date));
}
// Clean up session storage
sessionStorage.removeItem('newAppointmentData');
}
}, [form]);
const handleSubmit = (data: AppointmentFormValues) => {
// Convert date to string format for the API and ensure patientId is properly parsed as a number
console.log("Form data before submission:", data);
// Make sure patientId is a number
const patientId = typeof data.patientId === 'string'
? parseInt(data.patientId, 10)
: data.patientId;
// Get patient name for the title
const patient = patients.find(p => p.id === patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : 'Patient';
// Auto-create title if it's empty
let title = data.title;
if (!title || title.trim() === '') {
// Format: "April 19" - just the date
title = format(data.date, 'MMMM d');
}
// Make sure notes include staff information (needed for appointment display in columns)
let notes = data.notes || '';
// Get the selected staff member
const selectedStaff = staffMembers.find(staff => staff.id === data.staff) || staffMembers[0];
// If there's no staff information in the notes, add it
if (!notes.includes('Appointment with')) {
notes = notes ? `${notes}\nAppointment with ${selectedStaff.name}` : `Appointment with ${selectedStaff.name}`;
}
onSubmit({
...data,
title,
notes,
patientId, // Ensure patientId is a number
date: format(data.date, 'yyyy-MM-dd'),
});
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
<FormField
control={form.control}
name="patientId"
render={({ field }) => (
<FormItem>
<FormLabel>Patient</FormLabel>
<Select
disabled={isLoading}
onValueChange={field.onChange}
value={field.value?.toString()}
defaultValue={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a patient" />
</SelectTrigger>
</FormControl>
<SelectContent>
{patients.map((patient) => (
<SelectItem key={patient.id} value={patient.id.toString()}>
{patient.firstName} {patient.lastName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Appointment Title <span className="text-muted-foreground text-xs">(optional)</span></FormLabel>
<FormControl>
<Input
placeholder="Leave blank to auto-fill with date"
{...field}
disabled={isLoading}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-full pl-3 text-left font-normal",
!field.value && "text-muted-foreground"
)}
disabled={isLoading}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
disabled={(date) =>
date < new Date(new Date().setHours(0, 0, 0, 0))
}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="startTime"
render={({ field }) => (
<FormItem>
<FormLabel>Start Time</FormLabel>
<FormControl>
<div className="relative">
<Clock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="09:00"
{...field}
disabled={isLoading}
className="pl-10"
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="endTime"
render={({ field }) => (
<FormItem>
<FormLabel>End Time</FormLabel>
<FormControl>
<div className="relative">
<Clock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="09:30"
{...field}
disabled={isLoading}
className="pl-10"
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Appointment Type</FormLabel>
<Select
disabled={isLoading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="checkup">Checkup</SelectItem>
<SelectItem value="cleaning">Cleaning</SelectItem>
<SelectItem value="filling">Filling</SelectItem>
<SelectItem value="extraction">Extraction</SelectItem>
<SelectItem value="root-canal">Root Canal</SelectItem>
<SelectItem value="crown">Crown</SelectItem>
<SelectItem value="dentures">Dentures</SelectItem>
<SelectItem value="consultation">Consultation</SelectItem>
<SelectItem value="emergency">Emergency</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="status"
render={({ field }) => (
<FormItem>
<FormLabel>Status</FormLabel>
<Select
disabled={isLoading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="scheduled">Scheduled</SelectItem>
<SelectItem value="confirmed">Confirmed</SelectItem>
<SelectItem value="completed">Completed</SelectItem>
<SelectItem value="cancelled">Cancelled</SelectItem>
<SelectItem value="no-show">No Show</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="staff"
render={({ field }) => (
<FormItem>
<FormLabel>Doctor/Hygienist</FormLabel>
<Select
disabled={isLoading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select staff member" />
</SelectTrigger>
</FormControl>
<SelectContent>
{staffMembers.map((staff) => (
<SelectItem key={staff.id} value={staff.id}>
{staff.name} ({staff.role})
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="notes"
render={({ field }) => (
<FormItem>
<FormLabel>Notes</FormLabel>
<FormControl>
<Textarea
placeholder="Enter any notes about the appointment"
{...field}
disabled={isLoading}
className="min-h-24"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={isLoading} className="w-full">
{appointment ? "Update Appointment" : "Create Appointment"}
</Button>
</form>
</Form>
);
}