all ui done
This commit is contained in:
@@ -17,13 +17,10 @@ app.use(express.json());
|
|||||||
app.use(express.urlencoded({ extended: true })); // For form data
|
app.use(express.urlencoded({ extended: true })); // For form data
|
||||||
app.use(apiLogger);
|
app.use(apiLogger);
|
||||||
|
|
||||||
|
|
||||||
console.log(FRONTEND_URL);
|
|
||||||
|
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: FRONTEND_URL, // Make sure this matches the frontend URL
|
origin: FRONTEND_URL,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allow these HTTP methods
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization'], // Allow necessary headers
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -111,16 +111,12 @@ router.post(
|
|||||||
|
|
||||||
async (req: Request, res: Response): Promise<any> => {
|
async (req: Request, res: Response): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
console.log("Appointment creation request body:", req.body);
|
|
||||||
|
|
||||||
// Validate request body
|
// Validate request body
|
||||||
const appointmentData = insertAppointmentSchema.parse({
|
const appointmentData = insertAppointmentSchema.parse({
|
||||||
...req.body,
|
...req.body,
|
||||||
userId: req.user!.id,
|
userId: req.user!.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Validated appointment data:", appointmentData);
|
|
||||||
|
|
||||||
// Verify patient exists and belongs to user
|
// Verify patient exists and belongs to user
|
||||||
const patient = await storage.getPatient(appointmentData.patientId);
|
const patient = await storage.getPatient(appointmentData.patientId);
|
||||||
if (!patient) {
|
if (!patient) {
|
||||||
@@ -165,7 +161,6 @@ router.post(
|
|||||||
|
|
||||||
// Create appointment
|
// Create appointment
|
||||||
const appointment = await storage.createAppointment(appointmentData);
|
const appointment = await storage.createAppointment(appointmentData);
|
||||||
console.log("Appointment created successfully:", appointment);
|
|
||||||
res.status(201).json(appointment);
|
res.status(201).json(appointment);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating appointment:", error);
|
console.error("Error creating appointment:", error);
|
||||||
@@ -201,12 +196,6 @@ router.put(
|
|||||||
}
|
}
|
||||||
const appointmentId = parseInt(appointmentIdParam);
|
const appointmentId = parseInt(appointmentIdParam);
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Update appointment request. ID:",
|
|
||||||
appointmentId,
|
|
||||||
"Body:",
|
|
||||||
req.body
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if appointment exists and belongs to user
|
// Check if appointment exists and belongs to user
|
||||||
const existingAppointment = await storage.getAppointment(appointmentId);
|
const existingAppointment = await storage.getAppointment(appointmentId);
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
||||||
"@repo/db": "*",
|
"@repo/db": "*",
|
||||||
"@repo/typescript-config": "*",
|
"@repo/typescript-config": "*",
|
||||||
"@tailwindcss/vite": "^4.1.6",
|
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@tailwindcss/vite": "^4.1.6",
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
"memorystore": "^1.6.7",
|
"memorystore": "^1.6.7",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-contexify": "^6.0.0",
|
"react-contexify": "^6.0.0",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
|||||||
path: "/patients",
|
path: "/patients",
|
||||||
icon: <Users className="h-5 w-5" />,
|
icon: <Users className="h-5 w-5" />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Reports",
|
|
||||||
path: "/reports",
|
|
||||||
icon: <FileText className="h-5 w-5" />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface TopAppBarProps {
|
interface TopAppBarProps {
|
||||||
toggleMobileMenu: () => void;
|
toggleMobileMenu: () => void;
|
||||||
@@ -16,6 +19,7 @@ interface TopAppBarProps {
|
|||||||
|
|
||||||
export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
||||||
const { user, logoutMutation } = useAuth();
|
const { user, logoutMutation } = useAuth();
|
||||||
|
const [location, setLocation] = useLocation();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logoutMutation.mutate();
|
logoutMutation.mutate();
|
||||||
@@ -68,7 +72,8 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
|||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
||||||
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
||||||
<DropdownMenuItem>Account Settings</DropdownMenuItem>
|
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
||||||
|
Account Settings</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={handleLogout}>
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
Log out
|
Log out
|
||||||
|
|||||||
@@ -110,8 +110,11 @@ export const AddPatientModal = forwardRef<
|
|||||||
|
|
||||||
const handleSaveAndSchedule = () => {
|
const handleSaveAndSchedule = () => {
|
||||||
setSaveAndSchedule(true);
|
setSaveAndSchedule(true);
|
||||||
document.querySelector("form")?.requestSubmit();
|
if (patientFormRef.current) {
|
||||||
};
|
patientFormRef.current.submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
@@ -137,6 +140,7 @@ export const AddPatientModal = forwardRef<
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<PatientForm
|
<PatientForm
|
||||||
|
ref={patientFormRef}
|
||||||
patient={patient}
|
patient={patient}
|
||||||
extractedInfo={extractedInfo}
|
extractedInfo={extractedInfo}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { forwardRef, useImperativeHandle } from "react";
|
||||||
|
|
||||||
const PatientSchema = (
|
const PatientSchema = (
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
@@ -64,11 +65,8 @@ export type PatientFormRef = {
|
|||||||
submit: () => void;
|
submit: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PatientForm({
|
export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
|
||||||
patient,
|
({ patient, extractedInfo, onSubmit }, ref) => {
|
||||||
extractedInfo,
|
|
||||||
onSubmit,
|
|
||||||
}: PatientFormProps) {
|
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const isEditing = !!patient;
|
const isEditing = !!patient;
|
||||||
|
|
||||||
@@ -90,7 +88,6 @@ export function PatientForm({
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
firstName: extractedInfo?.firstName || "",
|
firstName: extractedInfo?.firstName || "",
|
||||||
lastName: extractedInfo?.lastName || "",
|
lastName: extractedInfo?.lastName || "",
|
||||||
@@ -117,6 +114,12 @@ export function PatientForm({
|
|||||||
defaultValues: computedDefaultValues,
|
defaultValues: computedDefaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
submit() {
|
||||||
|
(document.getElementById("patient-form") as HTMLFormElement | null)?.requestSubmit();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// Debug form errors
|
// Debug form errors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const errors = form.formState.errors;
|
const errors = form.formState.errors;
|
||||||
@@ -255,7 +258,11 @@ export function PatientForm({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="email" {...field} value={field.value || ""} />
|
<Input
|
||||||
|
type="email"
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -393,4 +400,5 @@ export function PatientForm({
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getAvatarColor = (id: number) => {
|
const getAvatarColor = (id: number) => {
|
||||||
const colors = [
|
const colorClasses = [
|
||||||
"bg-blue-500",
|
"bg-blue-500",
|
||||||
"bg-teal-500",
|
"bg-teal-500",
|
||||||
"bg-amber-500",
|
"bg-amber-500",
|
||||||
@@ -65,8 +65,10 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
|||||||
"bg-green-500",
|
"bg-green-500",
|
||||||
"bg-purple-500",
|
"bg-purple-500",
|
||||||
];
|
];
|
||||||
return colors[id % colors.length];
|
|
||||||
};
|
// This returns a literal string from above — not a generated string
|
||||||
|
return colorClasses[id % colorClasses.length];
|
||||||
|
};
|
||||||
|
|
||||||
const formatDate = (dateString: string | Date) => {
|
const formatDate = (dateString: string | Date) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -108,6 +110,7 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
|||||||
{getInitials(patient.firstName, patient.lastName)}
|
{getInitials(patient.firstName, patient.lastName)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{patient.firstName} {patient.lastName}
|
{patient.firstName} {patient.lastName}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const AvatarFallback = React.forwardRef<
|
|||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
"flex h-full w-full items-center justify-center rounded-full",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,34 +1,37 @@
|
|||||||
import { LucideIcon } from "lucide-react";
|
import { LucideIcon } from "lucide-react";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
|
||||||
|
type ColorKey = "primary" | "secondary" | "success" | "warning" | "blue" | "teal" | "green" | "orange" | "rose" | "violet";
|
||||||
|
|
||||||
interface StatCardProps {
|
interface StatCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
value: number | string;
|
value: number | string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
color: string;
|
color: ColorKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorMap: Record<ColorKey, { bg: string; text: string }> = {
|
||||||
|
primary: { bg: "bg-primary bg-opacity-10", text: "text-primary" },
|
||||||
|
secondary: { bg: "bg-teal-500 bg-opacity-10", text: "text-teal-500" },
|
||||||
|
success: { bg: "bg-green-500 bg-opacity-10", text: "text-green-500" },
|
||||||
|
warning: { bg: "bg-orange-500 bg-opacity-10", text: "text-orange-500" },
|
||||||
|
blue: { bg: "bg-blue-100", text: "text-blue-600" },
|
||||||
|
teal: { bg: "bg-teal-100", text: "text-teal-600" },
|
||||||
|
green: { bg: "bg-green-100", text: "text-green-600" },
|
||||||
|
orange: { bg: "bg-orange-100", text: "text-orange-600" },
|
||||||
|
rose: { bg: "bg-rose-100", text: "text-rose-600" },
|
||||||
|
violet: { bg: "bg-violet-100", text: "text-violet-600" },
|
||||||
|
};
|
||||||
|
|
||||||
export function StatCard({ title, value, icon: Icon, color }: StatCardProps) {
|
export function StatCard({ title, value, icon: Icon, color }: StatCardProps) {
|
||||||
const getBackgroundColorClass = (color: string) => {
|
const { bg, text } = colorMap[color] ?? colorMap.primary;
|
||||||
switch(color) {
|
|
||||||
case 'primary':
|
|
||||||
return 'bg-primary bg-opacity-10 text-primary';
|
|
||||||
case 'secondary':
|
|
||||||
return 'bg-teal-500 bg-opacity-10 text-teal-500';
|
|
||||||
case 'success':
|
|
||||||
return 'bg-green-500 bg-opacity-10 text-green-500';
|
|
||||||
case 'warning':
|
|
||||||
return 'bg-orange-500 bg-opacity-10 text-orange-500';
|
|
||||||
default:
|
|
||||||
return 'bg-primary bg-opacity-10 text-primary';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="shadow-sm hover:shadow transition-shadow duration-200">
|
<Card className="shadow-sm hover:shadow transition-shadow duration-200">
|
||||||
<CardContent className="p-4 flex items-center space-x-4">
|
<CardContent className="p-4 flex items-center space-x-4">
|
||||||
<div className={`rounded-full ${getBackgroundColorClass(color)} p-3`}>
|
<div className={`rounded-full p-3 ${bg} ${text}`}>
|
||||||
<Icon className="h-5 w-5" />
|
<Icon className="h-5 w-5" stroke="currentColor" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-600">{title}</p>
|
<p className="text-sm text-gray-600">{title}</p>
|
||||||
|
|||||||
@@ -127,14 +127,20 @@ export default function AppointmentsPage() {
|
|||||||
enabled: !!user,
|
enabled: !!user,
|
||||||
});
|
});
|
||||||
|
|
||||||
const colorMap: Record<string, string> = {
|
const colors = [
|
||||||
"Dr. Kai Gao": "bg-blue-600",
|
"bg-blue-600",
|
||||||
"Dr. Jane Smith": "bg-emerald-600",
|
"bg-emerald-600",
|
||||||
};
|
"bg-purple-600",
|
||||||
|
"bg-pink-600",
|
||||||
|
"bg-yellow-500",
|
||||||
|
"bg-red-600",
|
||||||
|
];
|
||||||
|
|
||||||
const staffMembers = staffMembersRaw.map((staff) => ({
|
// Assign colors cycling through the list
|
||||||
|
const staffMembers = staffMembersRaw.map((staff, index) => ({
|
||||||
...staff,
|
...staff,
|
||||||
color: colorMap[staff.name] || "bg-gray-400",
|
|
||||||
|
color: colors[index % colors.length] || "bg-gray-400",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Generate time slots from 8:00 AM to 6:00 PM in 30-minute increments
|
// Generate time slots from 8:00 AM to 6:00 PM in 30-minute increments
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export default function Dashboard() {
|
|||||||
title="Total Patients"
|
title="Total Patients"
|
||||||
value={patients.length}
|
value={patients.length}
|
||||||
icon={Users}
|
icon={Users}
|
||||||
color="primary"
|
color="blue"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Today's Appointments"
|
title="Today's Appointments"
|
||||||
@@ -380,7 +380,7 @@ export default function Dashboard() {
|
|||||||
className="p-4 flex items-center justify-between"
|
className="p-4 flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="h-10 w-10 rounded-full bg-primary bg-opacity-10 text-primary flex items-center justify-center">
|
<div className="h-10 w-10 rounded-full bg-opacity-10 text-primary flex items-center justify-center">
|
||||||
<Clock className="h-5 w-5" />
|
<Clock className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -219,109 +219,6 @@ export default function PatientsPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process file and extract patient information
|
|
||||||
const handleExtractInfo = async () => {
|
|
||||||
if (!uploadedFile) {
|
|
||||||
toast({
|
|
||||||
title: "No file selected",
|
|
||||||
description: "Please select a file first.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsExtracting(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Read the file as base64
|
|
||||||
const reader = new FileReader();
|
|
||||||
|
|
||||||
// Set up a Promise to handle file reading
|
|
||||||
const fileReadPromise = new Promise<string>((resolve, reject) => {
|
|
||||||
reader.onload = (event) => {
|
|
||||||
if (event.target && typeof event.target.result === "string") {
|
|
||||||
resolve(event.target.result);
|
|
||||||
} else {
|
|
||||||
reject(new Error("Failed to read file as base64"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.onerror = () => {
|
|
||||||
reject(new Error("Error reading file"));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read the file as a data URL (base64)
|
|
||||||
reader.readAsDataURL(uploadedFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the base64 data
|
|
||||||
const base64Data = await fileReadPromise;
|
|
||||||
|
|
||||||
// Send file to server as base64
|
|
||||||
const response = await fetch("/api/upload-file", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
pdfData: base64Data,
|
|
||||||
filename: uploadedFile.name,
|
|
||||||
}),
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`Server returned ${response.status}: ${response.statusText}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
// Only keep firstName, lastName, dateOfBirth, and insuranceId from the extracted info
|
|
||||||
const simplifiedInfo = {
|
|
||||||
firstName: data.extractedInfo.firstName,
|
|
||||||
lastName: data.extractedInfo.lastName,
|
|
||||||
dateOfBirth: data.extractedInfo.dateOfBirth,
|
|
||||||
insuranceId: data.extractedInfo.insuranceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
setExtractedInfo(simplifiedInfo);
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
toast({
|
|
||||||
title: "Information Extracted",
|
|
||||||
description:
|
|
||||||
"Basic patient information (name, DOB, ID) has been extracted successfully.",
|
|
||||||
variant: "default",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open patient form pre-filled with extracted data
|
|
||||||
setCurrentPatient(undefined);
|
|
||||||
|
|
||||||
// Pre-fill the form by opening the modal with the extracted information
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsAddPatientOpen(true);
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
throw new Error(data.message || "Failed to extract information");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error extracting information:", error);
|
|
||||||
toast({
|
|
||||||
title: "Error",
|
|
||||||
description:
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to extract information from file",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsExtracting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter patients based on search criteria
|
// Filter patients based on search criteria
|
||||||
const filteredPatients = useMemo(() => {
|
const filteredPatients = useMemo(() => {
|
||||||
if (!searchCriteria || !searchCriteria.searchTerm) {
|
if (!searchCriteria || !searchCriteria.searchTerm) {
|
||||||
@@ -424,7 +321,6 @@ export default function PatientsPage() {
|
|||||||
<div className="md:col-span-1 flex items-end">
|
<div className="md:col-span-1 flex items-end">
|
||||||
<Button
|
<Button
|
||||||
className="w-full h-12 gap-2"
|
className="w-full h-12 gap-2"
|
||||||
onClick={handleExtractInfo}
|
|
||||||
disabled={!uploadedFile || isExtracting}
|
disabled={!uploadedFile || isExtracting}
|
||||||
>
|
>
|
||||||
{isExtracting ? (
|
{isExtracting ? (
|
||||||
|
|||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -471,6 +471,7 @@
|
|||||||
"react-hook-form": "^7.55.0",
|
"react-hook-form": "^7.55.0",
|
||||||
"react-icons": "^5.4.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
|
"react-router-dom": "^7.6.0",
|
||||||
"recharts": "^2.15.2",
|
"recharts": "^2.15.2",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
@@ -11571,6 +11572,53 @@
|
|||||||
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
|
||||||
|
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
|
||||||
|
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router/node_modules/cookie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-smooth": {
|
"node_modules/react-smooth": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
@@ -12218,6 +12266,12 @@
|
|||||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { PrismaClient } from '../generated/prisma';
|
import { PrismaClient } from "../generated/prisma";
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
console.log("Here")
|
function formatTime(date: Date): string {
|
||||||
|
return date.toTimeString().slice(0, 5); // "HH:MM"
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Create multiple users
|
// Create multiple users
|
||||||
const users = await prisma.user.createMany({
|
const users = await prisma.user.createMany({
|
||||||
data: [
|
data: [
|
||||||
{ username: 'admin2', password: '123456' },
|
{ username: "admin2", password: "123456" },
|
||||||
{ username: 'bob', password: '123456' },
|
{ username: "bob", password: "123456" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -16,8 +19,8 @@ async function main() {
|
|||||||
// Creatin staff
|
// Creatin staff
|
||||||
await prisma.staff.createMany({
|
await prisma.staff.createMany({
|
||||||
data: [
|
data: [
|
||||||
{ name: 'Dr. Kai Gao', role: 'Doctor' },
|
{ name: "Dr. Kai Gao", role: "Doctor" },
|
||||||
{ name: 'Dr. Jane Smith', role: 'Doctor' },
|
{ name: "Dr. Jane Smith", role: "Doctor" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,27 +30,27 @@ async function main() {
|
|||||||
const patients = await prisma.patient.createMany({
|
const patients = await prisma.patient.createMany({
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
firstName: 'Emily',
|
firstName: "Emily",
|
||||||
lastName: 'Clark',
|
lastName: "Clark",
|
||||||
dateOfBirth: new Date('1985-06-15'),
|
dateOfBirth: new Date("1985-06-15"),
|
||||||
gender: 'female',
|
gender: "female",
|
||||||
phone: '555-0001',
|
phone: "555-0001",
|
||||||
email: 'emily@example.com',
|
email: "emily@example.com",
|
||||||
address: '101 Apple Rd',
|
address: "101 Apple Rd",
|
||||||
city: 'Newtown',
|
city: "Newtown",
|
||||||
zipCode: '10001',
|
zipCode: "10001",
|
||||||
userId: createdUsers[0].id,
|
userId: createdUsers[0].id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
firstName: 'Michael',
|
firstName: "Michael",
|
||||||
lastName: 'Brown',
|
lastName: "Brown",
|
||||||
dateOfBirth: new Date('1979-09-10'),
|
dateOfBirth: new Date("1979-09-10"),
|
||||||
gender: 'male',
|
gender: "male",
|
||||||
phone: '555-0002',
|
phone: "555-0002",
|
||||||
email: 'michael@example.com',
|
email: "michael@example.com",
|
||||||
address: '202 Banana Ave',
|
address: "202 Banana Ave",
|
||||||
city: 'Oldtown',
|
city: "Oldtown",
|
||||||
zipCode: '10002',
|
zipCode: "10002",
|
||||||
userId: createdUsers[1].id,
|
userId: createdUsers[1].id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -61,25 +64,23 @@ async function main() {
|
|||||||
{
|
{
|
||||||
patientId: createdPatients[0].id,
|
patientId: createdPatients[0].id,
|
||||||
userId: createdUsers[0].id,
|
userId: createdUsers[0].id,
|
||||||
title: 'Initial Consultation',
|
title: "Initial Consultation",
|
||||||
date: new Date('2025-06-01'),
|
date: new Date("2025-06-01"),
|
||||||
startTime: new Date('2025-06-01T10:00:00'),
|
startTime: formatTime(new Date("2025-06-01T10:00:00")),
|
||||||
endTime: new Date('2025-06-01T10:30:00'),
|
endTime: formatTime(new Date("2025-06-01T10:30:00")),
|
||||||
type: 'consultation',
|
type: "consultation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
patientId: createdPatients[1].id,
|
patientId: createdPatients[1].id,
|
||||||
userId: createdUsers[1].id,
|
userId: createdUsers[1].id,
|
||||||
title: 'Follow-up',
|
title: "Follow-up",
|
||||||
date: new Date('2025-06-02'),
|
date: new Date("2025-06-02"),
|
||||||
startTime: new Date('2025-06-02T14:00:00'),
|
startTime: formatTime(new Date("2025-06-01T10:00:00")),
|
||||||
endTime: new Date('2025-06-02T14:30:00'),
|
endTime: formatTime(new Date("2025-06-01T10:30:00")),
|
||||||
type: 'checkup',
|
type: "checkup",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Seeded multiple users, patients, and appointments.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
@@ -88,6 +89,5 @@ main()
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
console.log("Done seeding logged.")
|
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user