diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000..cacff11 --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,49 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/frontend/src/pages/Doctor.tsx b/frontend/src/pages/Doctor.tsx index 8c94639..999d554 100644 --- a/frontend/src/pages/Doctor.tsx +++ b/frontend/src/pages/Doctor.tsx @@ -1,4 +1,5 @@ import {useState, useEffect, useCallback} from "react"; +import {AxiosError} from "axios"; import { getDoctorsApi, createDoctorApi, @@ -6,7 +7,6 @@ import { deleteDoctorApi, getDoctorTimingApi, } from "@/api/doctor"; - import {getDepartmentsApi} from "@/api/department"; import { @@ -17,10 +17,8 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; - import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; import {Button} from "@/components/ui/button"; - import { Dialog, DialogContent, @@ -28,27 +26,32 @@ import { DialogTitle, DialogFooter, } from "@/components/ui/dialog"; - -import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; -import { - Command, - CommandGroup, - CommandItem, - CommandInput, -} from "@/components/ui/command"; - import {Input} from "@/components/ui/input"; -import {Loader2, Plus, Pencil, Trash, RefreshCw} from "lucide-react"; +import {Badge} from "@/components/ui/badge"; +import {Loader2, RefreshCw, Plus, Pencil, Trash} from "lucide-react"; interface Department { departmentId: string; name: string; } +const DAYS = [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + "additional", +]; + export default function DoctorPage() { const [doctors, setDoctors] = useState([]); const [departments, setDepartments] = useState([]); const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + const [openModal, setOpenModal] = useState(false); const [editing, setEditing] = useState(null); @@ -66,16 +69,20 @@ export default function DoctorPage() { const fetchAll = useCallback(async () => { setLoading(true); + setError(""); try { const [docRes, depRes] = await Promise.all([ getDoctorsApi(), getDepartmentsApi(), ]); - setDoctors(docRes?.data || []); setDepartments(depRes?.data || []); } catch (err) { - console.error(err); + if (err instanceof AxiosError) { + setError(err.response?.data?.message || "Failed to load data"); + } else { + setError("Something went wrong"); + } } finally { setLoading(false); } @@ -101,9 +108,8 @@ export default function DoctorPage() { setForm({...form, [e.target.name]: e.target.value}); } - function handleDepartmentChange(depId: string) { + function handleDepartmentToggle(depId: string) { const exists = form.departments.find((d: any) => d.departmentId === depId); - if (exists) { setForm({ ...form, @@ -124,10 +130,7 @@ export default function DoctorPage() { ...form, departments: form.departments.map((d: any) => d.departmentId === depId - ? { - ...d, - timing: {...d.timing, [day]: value}, - } + ? {...d, timing: {...d.timing, [day]: value}} : d, ), }); @@ -148,25 +151,20 @@ export default function DoctorPage() { async function openEdit(doc: any) { setEditing(doc); - try { const timingRes = await getDoctorTimingApi(doc.doctorId); const timingData = timingRes?.data?.departments || []; - - const mappedDepartments = timingData.map((d: any) => ({ - departmentId: d.departmentId, - timing: d.timing || {}, - })); - setForm({ doctorId: doc.doctorId, name: doc.name, designation: doc.designation, workingStatus: doc.workingStatus, qualification: doc.qualification, - departments: mappedDepartments, + departments: timingData.map((d: any) => ({ + departmentId: d.departmentId, + timing: d.timing || {}, + })), }); - setOpenModal(true); } catch (err) { console.error(err); @@ -180,18 +178,21 @@ export default function DoctorPage() { } else { await createDoctorApi(form); } - setOpenModal(false); fetchAll(); - } catch (err) { - console.error(err); + } catch (error) { + console.error(error); } } async function handleDelete(id: string) { - if (!confirm("Delete doctor?")) return; - await deleteDoctorApi(id); - fetchAll(); + if (!confirm("Delete this doctor?")) return; + try { + await deleteDoctorApi(id); + fetchAll(); + } catch (error) { + console.error(error); + } } return ( @@ -200,7 +201,7 @@ export default function DoctorPage() {

Doctors

-
+
setFilterDepartment(e.target.value)} - className="border rounded px-2 py-1" + className="flex h-10 w-[200px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" > {departments.map((dep) => ( @@ -234,80 +235,109 @@ export default function DoctorPage() {
{/* TABLE */} + {error && ( +
+ {error} +
+ )} + Doctor List - -
- + +
+
- ID - Name - Designation - Status - Qualification - Departments - Timing - Actions + ID + Name + Designation + Qualification + Departments + Actions {loading ? ( - + ) : filteredDoctors.length === 0 ? ( - + No doctors found ) : ( filteredDoctors.map((doc) => ( - {doc.doctorId} - {doc.name} - {doc.designation} - {doc.workingStatus} - {doc.qualification} + + {doc.doctorId} + - {doc.departments - ?.map((d: any) => d.departmentName) - .join(", ")} +
+ {doc.name} +
+
+ {doc.workingStatus} +
- - {doc.departments?.map((d: any) => ( -
- {d.departmentName}:{" "} - {JSON.stringify(d.timing)} -
- ))} + +
+ {doc.designation || "-"} +
- - + +
+ {doc.qualification || "-"} +
+
- + +
+ {doc.departments?.map((d: any) => ( + + {d.departmentName} + + ))} + {doc.departments?.length === 0 && ( + - + )} +
+
+ + +
+ + +
)) @@ -318,150 +348,129 @@ export default function DoctorPage() { - {/* MODAL */} {/* MODAL */} - + {editing ? "Edit Doctor" : "Add Doctor"} -
- +
+
+

Basic Information

+ + + + + - - - - - - {/* Departments */} -
-

Departments

- - - - - - - - - - - - {departments.map((dep) => { - const selected = form.departments.some( - (d: any) => d.departmentId === dep.departmentId, - ); - - return ( - - handleDepartmentChange(dep.departmentId) - } - > - {dep.name} - {selected && } - - ); - })} - - - - +
+

Assign Departments

+
+ {departments.map((dep) => { + const isSelected = form.departments.some( + (d: any) => d.departmentId === dep.departmentId, + ); + return ( + + ); + })} +
+
- {form.departments.map((dep: any) => { - const depName = departments.find( - (d) => d.departmentId === dep.departmentId, - )?.name; - - return ( -
-

{depName}

- - {[ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday", - "additional", - ].map((day) => ( - - handleTimingChange( - dep.departmentId, - day, - e.target.value, - ) - } - /> - ))} +
+

+ Working Hours / Timing +

+ {form.departments.length === 0 ? ( +
+ Select a department to set timings
- ); - })} + ) : ( +
+ {form.departments.map((dep: any) => { + const depName = departments.find( + (d) => d.departmentId === dep.departmentId, + )?.name; + return ( +
+

+ {depName} +

+
+ {DAYS.map((day) => ( +
+ + + handleTimingChange( + dep.departmentId, + day, + e.target.value, + ) + } + /> +
+ ))} +
+
+ ); + })} +
+ )} +
- +