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

1
.npmrc
View File

@@ -1 +0,0 @@
auto-install-peers = true

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')
}
}
});

518
package-lock.json generated
View File

@@ -12,7 +12,9 @@
"packages/*"
],
"dependencies": {
"prisma": "^6.7.0"
"@tailwindcss/postcss": "^4.1.6",
"@tailwindcss/typography": "^0.5.16",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"prettier": "^3.5.3",
@@ -83,9 +85,12 @@
"@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",
@@ -101,6 +106,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",
@@ -112,6 +118,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",
@@ -195,11 +202,22 @@
"typescript": "5.8.2"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -1187,11 +1205,22 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@@ -1215,7 +1244,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1648,6 +1676,7 @@
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz",
"integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"esbuild": ">=0.12 <1",
@@ -1658,12 +1687,14 @@
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz",
"integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz",
"integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1677,12 +1708,14 @@
"version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz",
"integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz",
"integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.7.0",
@@ -1754,6 +1787,7 @@
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz",
"integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.7.0"
@@ -3844,6 +3878,297 @@
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.6.tgz",
"integrity": "sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==",
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@tailwindcss/node": "4.1.6",
"@tailwindcss/oxide": "4.1.6",
"postcss": "^8.4.41",
"tailwindcss": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/node": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz",
"integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"enhanced-resolve": "^5.18.1",
"jiti": "^2.4.2",
"lightningcss": "1.29.2",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz",
"integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.4",
"tar": "^7.4.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.6",
"@tailwindcss/oxide-darwin-arm64": "4.1.6",
"@tailwindcss/oxide-darwin-x64": "4.1.6",
"@tailwindcss/oxide-freebsd-x64": "4.1.6",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.6",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.6",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.6",
"@tailwindcss/oxide-linux-x64-musl": "4.1.6",
"@tailwindcss/oxide-wasm32-wasi": "4.1.6",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.6",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.6"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz",
"integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz",
"integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz",
"integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz",
"integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz",
"integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz",
"integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz",
"integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz",
"integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz",
"integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz",
"integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.9",
"@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz",
"integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz",
"integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/postcss/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/@tailwindcss/postcss/node_modules/tailwindcss": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz",
"integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==",
"license": "MIT"
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
"license": "MIT",
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.5.tgz",
@@ -4736,6 +5061,43 @@
"node": ">= 0.4"
}
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -4836,7 +5198,6 @@
"version": "4.24.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -4972,7 +5333,6 @@
"version": "1.0.30001717",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz",
"integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -5021,6 +5381,15 @@
"uuid": "9.0.0"
}
},
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/ci-info": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -5297,6 +5666,18 @@
"node": ">=8"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -5701,7 +6082,6 @@
"version": "1.5.151",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz",
"integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==",
"dev": true,
"license": "ISC"
},
"node_modules/embla-carousel": {
@@ -6006,6 +6386,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
@@ -6018,7 +6399,6 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -6615,6 +6995,19 @@
"integrity": "sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==",
"license": "MIT"
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": {
"version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
@@ -8295,6 +8688,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"license": "MIT"
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -8323,7 +8722,6 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.union": {
@@ -8363,6 +8761,15 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -8521,12 +8928,48 @@
"node": "*"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/motion-dom": {
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
@@ -8643,7 +9086,6 @@
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
"license": "MIT"
},
"node_modules/normalize-package-data": {
@@ -8696,6 +9138,15 @@
"node": ">=0.10.0"
}
},
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-bundled": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz",
@@ -9426,6 +9877,25 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@@ -9574,6 +10044,7 @@
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
"integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -11067,6 +11538,23 @@
"node": ">=6"
}
},
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"license": "ISC",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@@ -11083,6 +11571,15 @@
"node": ">=6"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -11630,7 +12127,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [
{
"type": "opencollective",

View File

@@ -23,6 +23,8 @@
"packages/*"
],
"dependencies": {
"prisma": "^6.7.0"
"@tailwindcss/postcss": "^4.1.6",
"@tailwindcss/typography": "^0.5.16",
"tailwindcss-animate": "^1.0.7"
}
}

View File

@@ -12,7 +12,7 @@
"type": "commonjs",
"exports": {
"./client": "./src/index.ts",
"./shared" : "./shared/schemas/index.ts"
"./shared/schemas" : "./shared/schemas/index.ts"
},
"dependencies": {
"@prisma/client": "^6.7.0",

View File

@@ -1,85 +0,0 @@
import { pgTable, text, serial, integer, boolean, date, timestamp, time } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
// User schema
export const users = pgTable("users", {
id: serial("id").primaryKey(),
username: text("username").notNull().unique(),
password: text("password").notNull(),
});
export const insertUserSchema = createInsertSchema(users).pick({
username: true,
password: true,
});
export type InsertUser = z.infer<typeof insertUserSchema>;
export type User = typeof users.$inferSelect;
// Patient schema
export const patients = pgTable("patients", {
id: serial("id").primaryKey(),
firstName: text("first_name").notNull(),
lastName: text("last_name").notNull(),
dateOfBirth: date("date_of_birth").notNull(),
gender: text("gender").notNull(),
phone: text("phone").notNull(),
email: text("email"),
address: text("address"),
city: text("city"),
zipCode: text("zip_code"),
insuranceProvider: text("insurance_provider"),
insuranceId: text("insurance_id"),
groupNumber: text("group_number"),
policyHolder: text("policy_holder"),
allergies: text("allergies"),
medicalConditions: text("medical_conditions"),
status: text("status").default("active"),
userId: integer("user_id").notNull().references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
});
export const insertPatientSchema = createInsertSchema(patients).omit({
id: true,
createdAt: true,
});
export const updatePatientSchema = createInsertSchema(patients).omit({
id: true,
createdAt: true,
userId: true,
}).partial();
export type InsertPatient = z.infer<typeof insertPatientSchema>;
export type UpdatePatient = z.infer<typeof updatePatientSchema>;
export type Patient = typeof patients.$inferSelect;
// Appointment schema
export const appointments = pgTable("appointments", {
id: serial("id").primaryKey(),
patientId: integer("patient_id").notNull().references(() => patients.id),
userId: integer("user_id").notNull().references(() => users.id),
title: text("title").notNull(), // Added title field
date: date("date").notNull(),
startTime: time("start_time").notNull(),
endTime: time("end_time").notNull(),
type: text("type").notNull(), // e.g., "checkup", "cleaning", "filling", etc.
notes: text("notes"),
status: text("status").default("scheduled"), // "scheduled", "completed", "cancelled", "no-show"
createdAt: timestamp("created_at").defaultNow(),
});
export const insertAppointmentSchema = createInsertSchema(appointments).omit({
id: true,
createdAt: true,
});
export const updateAppointmentSchema = createInsertSchema(appointments).omit({
id: true,
createdAt: true,
}).partial();
export type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
export type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
export type Appointment = typeof appointments.$inferSelect;

View File

@@ -1,6 +1,6 @@
// Optional PostCSS configuration for applications that need it
import tailwindcss from "@tailwindcss/postcss";
import autoprefixer from "autoprefixer";
export const postcssConfig = {
plugins: {
"@tailwindcss/postcss": {},
},
plugins: [tailwindcss(), autoprefixer()]
};

View File

@@ -1,7 +1,2 @@
@import "tailwindcss";
@theme {
--blue-1000: #2a8af6;
--purple-1000: #a853ba;
--red-1000: #e92a67;
}

View File

@@ -0,0 +1,4 @@
import { config } from "@repo/eslint-config/react-internal";
/** @type {import("eslint").Linter.Config} */
export default config;

36
packages/ui/package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "@repo/ui",
"version": "0.0.0",
"sideEffects": [
"**/*.css"
],
"files": [
"dist"
],
"exports": {
"./styles.css": "./dist/index.css",
"./*": "./dist/*.js"
},
"license": "MIT",
"scripts": {
"build:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css",
"build:components": "tsc",
"check-types": "tsc --noEmit",
"dev:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css --watch",
"dev:components": "tsc --watch",
"lint": "eslint src --max-warnings 0"
},
"peerDependencies": {
"react": "^19"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@tailwindcss/cli": "^4.1.5",
"@types/react": "^19.1.0",
"eslint": "^9.26.0",
"tailwindcss": "^4.1.5",
"typescript": "5.8.2"
}
}

28
packages/ui/src/card.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { type ReactNode } from "react";
export function Card({
title,
children,
href,
}: {
title: string;
children: ReactNode;
href: string;
}) {
return (
<a
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-neutral-700 hover:bg-neutral-800/30"
href={`${href}?utm_source=create-turbo&utm_medium=with-tailwind&utm_campaign=create-turbo"`}
rel="noopener noreferrer"
target="_blank"
>
<h2 className="mb-3 text-2xl font-semibold">
{title}{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">{children}</p>
</a>
);
}

View File

@@ -0,0 +1,21 @@
export function Gradient({
conic,
className,
small,
}: {
small?: boolean;
conic?: boolean;
className?: string;
}) {
return (
<span
className={`absolute mix-blend-normal will-change-[filter] rounded-[100%] ${
small ? "blur-[32px]" : "blur-[75px]"
} ${
conic
? "bg-[conic-gradient(from_180deg_at_50%_50%,var(--red-1000)_0deg,_var(--purple-1000)_180deg,_var(--blue-1000)_360deg)]"
: ""
} ${className ?? ""}`}
/>
);
}

View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -0,0 +1,35 @@
export const TurborepoLogo = () => {
return (
<svg
aria-label="Turbo logomark"
height="80"
role="img"
viewBox="0 0 40 40"
width="80"
>
<path
d="M19.9845 6.99291C12.818 6.99291 6.98755 12.8279 6.98755 19.9999C6.98755 27.1721 12.818 33.0071 19.9845 33.0071C27.1509 33.0071 32.9814 27.1721 32.9814 19.9999C32.9814 12.8279 27.1509 6.99291 19.9845 6.99291ZM19.9845 26.7313C16.2694 26.7313 13.2585 23.718 13.2585 19.9999C13.2585 16.282 16.2694 13.2687 19.9845 13.2687C23.6996 13.2687 26.7105 16.282 26.7105 19.9999C26.7105 23.718 23.6996 26.7313 19.9845 26.7313Z"
fill="currentcolor"
></path>
<path
clipRule="evenodd"
d="M21.0734 4.85648V0C31.621 0.564369 40 9.30362 40 19.9999C40 30.6963 31.621 39.4332 21.0734 40V35.1435C28.9344 34.5815 35.1594 28.0078 35.1594 19.9999C35.1594 11.9922 28.9344 5.41843 21.0734 4.85648ZM8.52181 29.931C6.43794 27.5233 5.09469 24.4568 4.85508 21.09H0C0.251709 25.8011 2.13468 30.0763 5.08501 33.368L8.51938 29.931H8.52181ZM18.8951 40V35.1435C15.5285 34.9037 12.4644 33.5619 10.0587 31.4739L6.62435 34.9109C9.91593 37.866 14.1876 39.7481 18.8927 40H18.8951Z"
fill="url(#:Sb:paint0_linear_902_224)"
fillRule="evenodd"
></path>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id=":Sb:paint0_linear_902_224"
x1="21.8576"
x2="2.17018"
y1="2.81244"
y2="22.4844"
>
<stop stopColor="#0096FF"></stop>
<stop offset="1" stopColor="#FF1E56"></stop>
</linearGradient>
</defs>
</svg>
);
};

View File

@@ -0,0 +1,8 @@
{
"extends": "@repo/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["dist", "build", "node_modules"]
}

25
packages/ui/turbo.json Normal file
View File

@@ -0,0 +1,25 @@
{
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["build:styles", "build:components"]
},
"build:styles": {
"outputs": ["dist/**"]
},
"build:components": {
"outputs": ["dist/**"]
},
"dev": {
"with": ["dev:styles", "dev:components"]
},
"dev:styles": {
"cache": false,
"persistent": true
},
"dev:components": {
"cache": false,
"persistent": true
}
}
}