frontend tailwind half working without external styling

This commit is contained in:
2025-05-09 21:51:02 +05:30
parent ae99e25228
commit 9a431e63db
42 changed files with 1112 additions and 273 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Dental Management</title>
</head>
<body>
<div id="root"></div>

View File

@@ -10,8 +10,6 @@
"preview": "vite preview"
},
"dependencies": {
"@repo/ui": "*",
"@repo/db": "*",
"@hookform/resolvers": "^3.10.0",
"@jridgewell/trace-mapping": "^0.3.25",
"@radix-ui/react-accordion": "^1.2.4",
@@ -42,8 +40,13 @@
"@radix-ui/react-toggle-group": "^1.1.3",
"@radix-ui/react-tooltip": "^1.2.0",
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
"@repo/db": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@repo/ui": "*",
"@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.60.5",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -59,6 +62,7 @@
"next-themes": "^0.4.6",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"postcss": "^8.5.3",
"react": "^19.1.0",
"react-contexify": "^6.0.0",
"react-day-picker": "^8.10.1",
@@ -70,6 +74,7 @@
"react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^4.1.5",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5",
"vaul": "^1.1.2",

View File

@@ -0,0 +1,3 @@
import { postcssConfig } from "@repo/tailwind-config/postcss";
export default postcssConfig;

View File

@@ -1,7 +1,28 @@
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { AppointmentForm } from "./appointment-form";
import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@shared/schema";
// import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@repo/db/shared/schemas";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface AddAppointmentModalProps {
open: boolean;

View File

@@ -1,9 +1,30 @@
import { useState, useEffect } from "react";
import { 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";
// import { InsertAppointment, UpdateAppointment, Appointment, Patient } from "@repo/db/shared/schemas";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
// Define staff members (should match those in appointments-page.tsx)
const staffMembers = [
@@ -36,7 +57,6 @@ 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(),
@@ -52,7 +72,7 @@ const appointmentSchema = z.object({
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),
staff: z.string().default(staffMembers?.[0]?.id ?? "default-id"),
});
export type AppointmentFormValues = z.infer<typeof appointmentSchema>;
@@ -96,8 +116,8 @@ export function AppointmentForm({
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
startTime: typeof appointment.startTime === 'string' ? appointment.startTime.slice(0, 5) : "",
endTime: typeof appointment.endTime === 'string' ? appointment.endTime.slice(0, 5) : "",
type: appointment.type,
notes: appointment.notes || "",
status: appointment.status || "scheduled",
@@ -112,7 +132,7 @@ export function AppointmentForm({
type: parsedStoredData.type || "checkup",
status: parsedStoredData.status || "scheduled",
notes: parsedStoredData.notes || "",
staff: parsedStoredData.staff || staffMembers[0].id,
staff: parsedStoredData.staff || (staffMembers?.[0]?.id ?? "default-id")
}
: {
date: new Date(),
@@ -187,7 +207,7 @@ export function AppointmentForm({
// 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}`;
notes = notes ? `${notes}\nAppointment with ${selectedStaff?.name}` : `Appointment with ${selectedStaff?.name}`;
}
onSubmit({

View File

@@ -1,6 +1,4 @@
import { useState } from "react";
import { format } from "date-fns";
import { Appointment, Patient } from "@shared/schema";
import {
Table,
TableBody,
@@ -11,13 +9,13 @@ import {
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
MoreHorizontal,
Edit,
Trash2,
import {
MoreHorizontal,
Edit,
Trash2,
Eye,
Calendar,
Clock
Clock,
} from "lucide-react";
import {
DropdownMenu,
@@ -27,6 +25,21 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// import { Appointment, Patient } from "@repo/db/shared/schemas";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/shared/schemas";
import { z } from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface AppointmentTableProps {
appointments: Appointment[];
@@ -35,21 +48,34 @@ interface AppointmentTableProps {
onDelete: (id: number) => void;
}
export function AppointmentTable({
appointments,
patients,
onEdit,
onDelete
export function AppointmentTable({
appointments,
patients,
onEdit,
onDelete,
}: AppointmentTableProps) {
// Helper function to get patient name
const getPatientName = (patientId: number) => {
const patient = patients.find(p => p.id === patientId);
return patient ? `${patient.firstName} ${patient.lastName}` : "Unknown Patient";
const patient = patients.find((p) => p.id === patientId);
return patient
? `${patient.firstName} ${patient.lastName}`
: "Unknown Patient";
};
// Helper function to get status badge
const getStatusBadge = (status: string) => {
const statusConfig: Record<string, { variant: "default" | "secondary" | "destructive" | "outline" | "success"; label: string }> = {
const statusConfig: Record<
string,
{
variant:
| "default"
| "secondary"
| "destructive"
| "outline"
| "success";
label: string;
}
> = {
scheduled: { variant: "default", label: "Scheduled" },
confirmed: { variant: "secondary", label: "Confirmed" },
completed: { variant: "success", label: "Completed" },
@@ -57,20 +83,20 @@ export function AppointmentTable({
"no-show": { variant: "outline", label: "No Show" },
};
const config = statusConfig[status] || { variant: "default", label: status };
return (
<Badge variant={config.variant as any}>
{config.label}
</Badge>
);
const config = statusConfig[status] || {
variant: "default",
label: status,
};
return <Badge variant={config.variant as any}>{config.label}</Badge>;
};
// Sort appointments by date and time (newest first)
const sortedAppointments = [...appointments].sort((a, b) => {
const dateComparison = new Date(b.date).getTime() - new Date(a.date).getTime();
const dateComparison =
new Date(b.date).getTime() - new Date(a.date).getTime();
if (dateComparison !== 0) return dateComparison;
return a.startTime.localeCompare(b.startTime);
return a.startTime.toString().localeCompare(b.startTime.toString());
});
return (
@@ -108,15 +134,20 @@ export function AppointmentTable({
<TableCell>
<div className="flex items-center">
<Clock className="mr-2 h-4 w-4 text-muted-foreground" />
{appointment.startTime.slice(0, 5)} - {appointment.endTime.slice(0, 5)}
{appointment.startTime instanceof Date
? appointment.startTime.toISOString().slice(11, 16)
: appointment.startTime.slice(0, 5)}{" "}
-
{appointment.endTime instanceof Date
? appointment.endTime.toISOString().slice(11, 16)
: appointment.endTime.slice(0, 5)}
{/* {appointment.startTime.slice(0, 5)} - {appointment.endTime.slice(0, 5)} */}
</div>
</TableCell>
<TableCell className="capitalize">
{appointment.type.replace('-', ' ')}
</TableCell>
<TableCell>
{getStatusBadge(appointment.status!)}
{appointment.type.replace("-", " ")}
</TableCell>
<TableCell>{getStatusBadge(appointment.status!)}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -132,8 +163,14 @@ export function AppointmentTable({
Edit
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => onDelete(appointment.id)}
<DropdownMenuItem
onClick={() => {
if (typeof appointment.id === "number") {
onDelete(appointment.id);
} else {
console.error("Invalid appointment ID");
}
}}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
@@ -149,4 +186,4 @@ export function AppointmentTable({
</Table>
</div>
);
}
}

View File

@@ -1,21 +1,28 @@
import { useState, useEffect } from "react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
} from "@/components/ui/select";
import { format, parse } from "date-fns";
import { Patient } from "@shared/schema";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Label } from "../ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { X, Calendar as CalendarIcon } from "lucide-react";
import { useToast } from "../../hooks/use-toast";
import { Calendar } from "../ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { useToast } from "@/hooks/use-toast";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
// import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface ClaimFormProps {
patientId: number;

View File

@@ -1,6 +1,13 @@
import { useState, useEffect } from "react";
import { ClaimForm } from "./claim-form";
import { Patient } from "@shared/schema";
// import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface ClaimModalProps {
open: boolean;

View File

@@ -1,6 +1,5 @@
import { Link, useLocation } from "wouter";
import { Search, LayoutDashboard, Users, Calendar, FileText, Settings } from "lucide-react";
import { Input } from "@/components/ui/input";
import { LayoutDashboard, Users, Calendar, FileText, Settings } from "lucide-react";
import { cn } from "@/lib/utils";
interface SidebarProps {

View File

@@ -1,5 +1,4 @@
import { Bell, Menu, Search } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Bell, Menu} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useAuth } from "@/hooks/use-auth";

View File

@@ -9,10 +9,32 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { PatientForm } from "./patient-form";
import { InsertPatient, Patient, UpdatePatient } from "@shared/schema";
import { useToast } from "@/hooks/use-toast";
import { X, Calendar } from "lucide-react";
import { useLocation } from "wouter";
// import { InsertPatient, Patient, UpdatePatient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
interface AddPatientModalProps {
open: boolean;

View File

@@ -1,7 +1,8 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { insertPatientSchema, InsertPatient, Patient, updatePatientSchema, UpdatePatient } from "@shared/schema";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { insertPatientSchema, InsertPatient, Patient, updatePatientSchema, UpdatePatient } from "@repo/db/shared/schemas";
import { useAuth } from "@/hooks/use-auth";
import {
Form,
@@ -21,6 +22,26 @@ import {
SelectValue,
} from "@/components/ui/select";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
interface PatientFormProps {
patient?: Patient;
extractedInfo?: {

View File

@@ -1,5 +1,4 @@
import { useState } from "react";
import { Patient } from "@shared/schema";
import {
Table,
TableBody,
@@ -26,6 +25,15 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
// import { Patient } from "@repo/db/shared/schemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface PatientTableProps {
patients: Patient[];

View File

@@ -15,6 +15,8 @@ const badgeVariants = cva(
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
success: "border-transparent bg-success text-success-foreground hover:bg-success/80", // ✅ Added success variant
warning: "border-transparent bg-warning text-warning-foreground hover:bg-warning/80", // ✅ Added warning variant
},
},
defaultVariants: {

View File

@@ -4,9 +4,25 @@ import {
useMutation,
UseMutationResult,
} from "@tanstack/react-query";
import { insertUserSchema, User as SelectUser, InsertUser } from "@shared/schema";
// import { insertUserSchema, User as SelectUser, InsertUser } from "@repo/db/shared/schemas";
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import {z} from "zod";
import { getQueryFn, apiRequest, queryClient } from "../lib/queryClient";
import { useToast } from "../hooks/use-toast";
import { useToast } from "@/hooks/use-toast";
type SelectUser = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
const insertUserSchema = (UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<{
username: z.ZodString;
password: z.ZodString;
}>).pick({
username: true,
password: true,
});
type InsertUser = z.infer<typeof insertUserSchema>;
type AuthContextType = {
user: SelectUser | null;
@@ -17,7 +33,11 @@ type AuthContextType = {
registerMutation: UseMutationResult<SelectUser, Error, InsertUser>;
};
type LoginData = Pick<InsertUser, "username" | "password">;
// type LoginData = Pick<InsertUser, "username" | "password">;
type LoginData = {
username: InsertUser["username"];
password: InsertUser["password"];
};
export const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {

View File

@@ -3,7 +3,7 @@ import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "../components/ui/toast"
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000

View File

@@ -1,3 +1,3 @@
@import "tailwindcss";
@import "@repo/tailwind-config";
@import "@repo/ui/styles.css";
@import "@repo/ui/styles.css";

View File

@@ -1,4 +1,4 @@
import { useAuth } from "../hooks/use-auth";
import { useAuth } from "@/hooks/use-auth";
import { Loader2 } from "lucide-react";
import { Redirect, Route } from "wouter";

View File

@@ -1,6 +1,6 @@
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import App from './App'
document.title = "DentalConnect - Patient Management System";

View File

@@ -1,15 +1,15 @@
import { useState, useEffect } from "react";
import { useState, useRef, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { format, addDays, startOfToday, addMinutes } from "date-fns";
import { TopAppBar } from "../components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar";
import { AddAppointmentModal } from "../components/appointments/add-appointment-modal";
import { ClaimModal } from "../components/claims/claim-modal";
import { Button } from "../components/ui/button";
import { z } from "zod";
import { format, addDays, parse, startOfToday, startOfDay, addMinutes, isEqual } from "date-fns";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import { ClaimModal } from "@/components/claims/claim-modal";
import { Button } from "@/components/ui/button";
import {
Calendar as CalendarIcon,
Plus,
Users,
ChevronLeft,
ChevronRight,
RefreshCw,
@@ -17,17 +17,42 @@ import {
Trash2,
FileText
} from "lucide-react";
import { useToast } from "../hooks/use-toast";
import { Calendar } from "../components/ui/calendar";
import { apiRequest, queryClient } from "../lib/queryClient";
import { useAuth } from "../hooks/use-auth";
import { Card, CardContent, CardHeader, CardDescription, CardTitle } from "../components/ui/card";
import { useToast } from "@/hooks/use-toast";
import { z } from "zod";
import { Calendar } from "@/components/ui/calendar";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { Appointment, InsertAppointment, UpdateAppointment, Patient } from "@repo/db/shared/schemas";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import { Card, CardContent, CardHeader, CardDescription, CardTitle } from "@/components/ui/card";
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Menu, Item, useContextMenu } from 'react-contexify';
import 'react-contexify/ReactContexify.css';
import { useLocation } from "wouter";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
// Define types for scheduling
interface TimeSlot {
time: string;
@@ -42,13 +67,13 @@ interface Staff {
}
interface ScheduledAppointment {
id: number;
id?: number;
patientId: number;
patientName: string;
staffId: string;
date: string;
startTime: string;
endTime: string;
date: string | Date; // Allow both string and Date
startTime: string | Date; // Allow both string and Date
endTime: string | Date; // Allow both string and Date
status: string | null;
type: string;
}
@@ -63,7 +88,7 @@ export default function AppointmentsPage() {
const [isClaimModalOpen, setIsClaimModalOpen] = useState(false);
const [claimAppointmentId, setClaimAppointmentId] = useState<number | null>(null);
const [claimPatientId, setClaimPatientId] = useState<number | null>(null);
const [editingAppointment, setEditingAppointment] = useState<any | undefined>(undefined);
const [editingAppointment, setEditingAppointment] = useState<Appointment | undefined>(undefined);
const [selectedDate, setSelectedDate] = useState<Date>(startOfToday());
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [location] = useLocation();
@@ -96,10 +121,10 @@ export default function AppointmentsPage() {
// Fetch appointments
const {
data: appointments,
data: appointments = [] as Appointment[],
isLoading: isLoadingAppointments,
refetch: refetchAppointments,
} = useQuery<any>({
} = useQuery<Appointment[]>({
queryKey: ["/api/appointments"],
enabled: !!user,
});
@@ -108,7 +133,7 @@ export default function AppointmentsPage() {
const {
data: patients = [],
isLoading: isLoadingPatients,
} = useQuery<any>({
} = useQuery<Patient[]>({
queryKey: ["/api/patients"],
enabled: !!user,
});
@@ -167,8 +192,7 @@ export default function AppointmentsPage() {
if (newPatientId) {
const patientId = parseInt(newPatientId);
// Find the patient in our list
const patient = patients.find((p: { id: number }) => p.id === patientId) ?? undefined;
const patient = (patients as Patient[]).find(p => p.id === patientId);
if (patient) {
toast({
@@ -177,7 +201,6 @@ export default function AppointmentsPage() {
});
// Select first available staff member
const staffId = staffMembers[0]!.id;
// Find first time slot today (9:00 AM is a common starting time)
@@ -207,7 +230,7 @@ export default function AppointmentsPage() {
// Create appointment mutation
const createAppointmentMutation = useMutation({
mutationFn: async (appointment: any) => {
mutationFn: async (appointment: InsertAppointment) => {
const res = await apiRequest("POST", "/api/appointments", appointment);
return await res.json();
},
@@ -230,7 +253,7 @@ export default function AppointmentsPage() {
// Update appointment mutation
const updateAppointmentMutation = useMutation({
mutationFn: async ({ id, appointment }: { id: number; appointment: any }) => {
mutationFn: async ({ id, appointment }: { id: number; appointment: UpdateAppointment }) => {
const res = await apiRequest("PUT", `/api/appointments/${id}`, appointment);
return await res.json();
},
@@ -274,7 +297,7 @@ export default function AppointmentsPage() {
});
// Handle appointment submission (create or update)
const handleAppointmentSubmit = (appointmentData: any) => {
const handleAppointmentSubmit = (appointmentData: InsertAppointment | UpdateAppointment) => {
// Make sure the date is for the selected date
const updatedData = {
...appointmentData,
@@ -285,13 +308,13 @@ export default function AppointmentsPage() {
if (editingAppointment && 'id' in editingAppointment && typeof editingAppointment.id === 'number') {
updateAppointmentMutation.mutate({
id: editingAppointment.id,
appointment: updatedData as any,
appointment: updatedData as unknown as UpdateAppointment,
});
} else {
// This is a new appointment
if (user) {
createAppointmentMutation.mutate({
...updatedData as any,
...updatedData as unknown as InsertAppointment,
userId: user.id
});
}
@@ -299,7 +322,7 @@ export default function AppointmentsPage() {
};
// Handle edit appointment
const handleEditAppointment = (appointment: any) => {
const handleEditAppointment = (appointment: Appointment) => {
setEditingAppointment(appointment);
setIsAddModalOpen(true);
};
@@ -319,21 +342,19 @@ export default function AppointmentsPage() {
const formattedDate = format(selectedDate, 'MMMM d, yyyy');
// Filter appointments for the selected date
const selectedDateAppointments = appointments.filter((apt: { date: string }) =>
const selectedDateAppointments = appointments.filter(apt =>
apt.date === format(selectedDate, 'yyyy-MM-dd')
);
// Add debugging logs
console.log("Selected date:", format(selectedDate, 'yyyy-MM-dd'));
console.log("All appointments:", appointments);
console.log("Filtered appointments for selected date:", selectedDateAppointments);
// Process appointments for the scheduler view
const processedAppointments: ScheduledAppointment[] = selectedDateAppointments.map((apt: any) => {
const processedAppointments: ScheduledAppointment[] = selectedDateAppointments.map(apt => {
// Find patient name
const patient = patients.find((p: any) => p.id === apt.patientId);
const patient = patients.find(p => p.id === apt.patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient';
// Try to determine the staff from the notes or title
@@ -376,7 +397,9 @@ export default function AppointmentsPage() {
const processed = {
...apt,
patientName,
staffId
staffId,
status: apt.status ?? null, // Default to null if status is undefined
date: apt.date instanceof Date ? apt.date.toISOString() : apt.date, // Ensure d
};
console.log("Processed appointment:", processed);
@@ -393,7 +416,8 @@ export default function AppointmentsPage() {
// In a real application, you might want to show multiple or stack them
const appointmentsAtSlot = processedAppointments.filter(apt => {
// Fix time format comparison - the database adds ":00" seconds to the time
const dbTime = apt.startTime.substring(0, 5); // Get just HH:MM, removing seconds
const dbTime = typeof apt.startTime === 'string' ? apt.startTime.substring(0, 5) : '';
const timeMatches = dbTime === timeSlot.time;
const staffMatches = apt.staffId === staffId;
@@ -417,7 +441,7 @@ export default function AppointmentsPage() {
// Handle moving an appointment to a new time slot and staff
const handleMoveAppointment = (appointmentId: number, newTimeSlot: TimeSlot, newStaffId: string) => {
const appointment = appointments.find((a:any) => a.id === appointmentId);
const appointment = appointments.find(a => a.id === appointmentId);
if (!appointment) return;
// Calculate new end time (30 minutes from start)
@@ -434,7 +458,7 @@ export default function AppointmentsPage() {
// Update appointment data
// Make sure we handle the time format correctly - backend expects HH:MM but stores as HH:MM:SS
const updatedAppointment: any = {
const updatedAppointment: UpdateAppointment = {
...appointment,
startTime: newTimeSlot.time, // Already in HH:MM format
endTime: endTime, // Already in HH:MM format
@@ -475,7 +499,6 @@ export default function AppointmentsPage() {
return (
<div
ref={drag as unknown as React.RefObject<HTMLDivElement>} // Type assertion to make TypeScript happy
className={`${staff.color} border border-white shadow-md text-white rounded p-1 text-xs h-full overflow-hidden cursor-move relative ${
isDragging ? 'opacity-50' : 'opacity-100'
}`}
@@ -483,14 +506,14 @@ export default function AppointmentsPage() {
onClick={(e) => {
// Only allow edit on click if we're not dragging
if (!isDragging) {
const fullAppointment = appointments.find((a:any) => a.id === appointment.id);
const fullAppointment = appointments.find(a => a.id === appointment.id);
if (fullAppointment) {
e.stopPropagation();
handleEditAppointment(fullAppointment);
}
}
}}
onContextMenu={(e) => handleContextMenu(e, appointment.id)}
onContextMenu={(e) => handleContextMenu(e, appointment.id ?? 0)}
>
<div className="font-bold truncate flex items-center gap-1">
<Move className="h-3 w-3" />
@@ -531,8 +554,6 @@ export default function AppointmentsPage() {
return (
<td
ref={drop as unknown as React.RefObject<HTMLTableCellElement>}
key={`${timeSlot.time}-${staffId}`}
className={`px-1 py-1 border relative h-14 ${isOver && canDrop ? 'bg-green-100' : ''}`}
>
@@ -583,7 +604,7 @@ export default function AppointmentsPage() {
<Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade">
<Item
onClick={({ props }) => {
const fullAppointment = appointments.find((a:any) => a.id === props.appointmentId);
const fullAppointment = appointments.find(a => a.id === props.appointmentId);
if (fullAppointment) {
handleEditAppointment(fullAppointment);
}
@@ -596,14 +617,14 @@ export default function AppointmentsPage() {
</Item>
<Item
onClick={({ props }) => {
const fullAppointment = appointments.find((a:any) => a.id === props.appointmentId);
const fullAppointment = appointments.find(a => a.id === props.appointmentId);
if (fullAppointment) {
// Set the appointment and patient IDs for the claim modal
setClaimAppointmentId(fullAppointment.id);
setClaimAppointmentId(fullAppointment.id ?? null);
setClaimPatientId(fullAppointment.patientId);
// Find the patient name for the toast notification
const patient = patients.find((p:any) => p.id === fullAppointment.patientId);
const patient = patients.find(p => p.id === fullAppointment.patientId);
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient #${fullAppointment.patientId}`;
// Show a toast notification

View File

@@ -1,11 +1,12 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { UserCreateOneSchema } from "@repo/db/shared";
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { insertUserSchema } from "@repo/db/shared/schemas";
import { useState } from "react";
import { useAuth } from "../hooks/use-auth";
import { useAuth } from "@/hooks/use-auth";
import { Redirect } from "wouter";
import { Button } from "../components/ui/button";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@@ -13,18 +14,25 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "../components/ui/form";
import { Input } from "../components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs";
import { Checkbox } from "../components/ui/checkbox";
import { Card, CardContent } from "../components/ui/card";
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Checkbox } from "@/components/ui/checkbox";
import { Card, CardContent } from "@/components/ui/card";
import { BriefcaseMedical, CheckCircle, Torus } from "lucide-react";
import { CheckedState } from "@radix-ui/react-checkbox";
const loginSchema = UserCreateOneSchema.extend({
const insertUserSchema = (UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).pick({
username: true,
password: true,
});
const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
rememberMe: z.boolean().optional(),
});
const registerSchema = UserCreateOne.extend({
const registerSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
confirmPassword: z.string().min(6, {
message: "Password must be at least 6 characters long",
}),
@@ -143,7 +151,7 @@ export default function AuthPage() {
<div className="flex items-center space-x-2">
<Checkbox
id="remember-me"
checked={field.value}
checked={field.value as CheckedState}
onCheckedChange={field.onChange}
/>
<label
@@ -216,6 +224,8 @@ export default function AuthPage() {
placeholder="••••••••"
type="password"
{...field}
value={typeof field.value === 'string' ? field.value : ''}
/>
</FormControl>
<FormMessage />
@@ -230,7 +240,7 @@ export default function AuthPage() {
<FormItem className="flex items-start space-x-2 mt-4">
<FormControl>
<Checkbox
checked={field.value}
checked={field.value as CheckedState}
onCheckedChange={field.onChange}
/>
</FormControl>

View File

@@ -1,18 +1,19 @@
import { useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { format } from "date-fns";
import { TopAppBar } from "../components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar";
import { StatCard } from "../components/ui/stat-card";
import { PatientTable } from "../components/patients/patient-table";
import { AddPatientModal } from "../components/patients/add-patient-modal";
import { AddAppointmentModal } from "../components/appointments/add-appointment-modal";
import { Card, CardContent } from "../components/ui/card";
import { Button } from "../components/ui/button";
import { useToast } from "../hooks/use-toast";
import { useAuth } from "../hooks/use-auth";
import { apiRequest, queryClient } from "../lib/queryClient";
import { InsertPatient, Patient, UpdatePatient, Appointment, InsertAppointment, UpdateAppointment } from "@repo/db/schema";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { StatCard } from "@/components/ui/stat-card";
import { PatientTable } from "@/components/patients/patient-table";
import { AddPatientModal } from "@/components/patients/add-patient-modal";
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/hooks/use-auth";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { InsertPatient, Patient, UpdatePatient, Appointment, InsertAppointment, UpdateAppointment } from "@repo/db/shared/schemas";
import { Users, Calendar, CheckCircle, CreditCard, Plus, Clock } from "lucide-react";
import { Link } from "wouter";
import {
@@ -21,7 +22,43 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "../components/ui/dialog";
} from "@/components/ui/dialog";
import {z} from "zod";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
}).partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
export default function Dashboard() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -174,7 +211,7 @@ export default function Dashboard() {
// Handle appointment submission (create or update)
const handleAppointmentSubmit = (appointmentData: InsertAppointment | UpdateAppointment) => {
if (selectedAppointment) {
if (selectedAppointment && typeof selectedAppointment.id === 'number') {
updateAppointmentMutation.mutate({
id: selectedAppointment.id,
appointment: appointmentData as UpdateAppointment,
@@ -274,7 +311,7 @@ export default function Dashboard() {
{patient ? `${patient.firstName} ${patient.lastName}` : 'Unknown Patient'}
</h3>
<div className="text-sm text-gray-500 flex items-center space-x-2">
<span>{appointment.startTime} - {appointment.endTime}</span>
<span>{new Date(appointment.startTime).toLocaleString()} - {new Date(appointment.endTime).toLocaleString()}</span>
<span>•</span>
<span>{appointment.type.charAt(0).toUpperCase() + appointment.type.slice(1)}</span>
</div>

View File

@@ -1,4 +1,4 @@
import { Card, CardContent } from "../components/ui/card";
import { Card, CardContent } from "@/components/ui/card";
import { AlertCircle } from "lucide-react";
export default function NotFound() {

View File

@@ -1,18 +1,41 @@
import { useState, useMemo, useRef } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { TopAppBar } from "../components/layout/top-app-bar";
import { Sidebar } from "../components/layout/sidebar";
import { PatientTable } from "../components/patients/patient-table";
import { AddPatientModal } from "../components/patients/add-patient-modal";
import { PatientSearch, SearchCriteria } from "../components/patients/patient-search";
import { FileUploadZone } from "../components/file-upload/file-upload-zone";
import { Button } from "../components/ui/button";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { PatientTable } from "@/components/patients/patient-table";
import { AddPatientModal } from "@/components/patients/add-patient-modal";
import { PatientSearch, SearchCriteria } from "@/components/patients/patient-search";
import { FileUploadZone } from "@/components/file-upload/file-upload-zone";
import { Button } from "@/components/ui/button";
import { Plus, RefreshCw, File, FilePlus } from "lucide-react";
import { useToast } from "../hooks/use-toast";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { Patient, InsertPatient, UpdatePatient } from "@shared/schema";
import { apiRequest, queryClient } from "../lib/queryClient";
import { useAuth } from "../hooks/use-auth";
import { useToast } from "@/hooks/use-toast";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
// import { Patient, InsertPatient, UpdatePatient } from "@repo/db/shared/schemas";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Type for the ref to access modal methods
type AddPatientModalRef = {

View File

@@ -0,0 +1,4 @@
const tailwindConfig = require("tailwindcss");
type Config = typeof tailwindConfig;
export default Config;

View File

@@ -1,11 +1,13 @@
{
"extends": "@repo/typescript-config/nextjs.json",
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
]
"moduleResolution": "node",
"module": "ESNext",
"jsx": "preserve",
"baseUrl": ".", // This tells TypeScript where to start resolving paths
"paths": {
"@/*": ["src/*"] // The @ alias will point to src folder inside the Frontend app
}
},
"include": [
"**/*.ts",

View File

@@ -1,7 +1,13 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
});