Files
gg-backend/frontend/src/pages/Doctor.tsx
T

440 lines
10 KiB
TypeScript
Raw Normal View History

2026-03-17 13:11:00 +05:30
import {useState, useEffect, useCallback} from "react";
import {
getDoctorsApi,
createDoctorApi,
updateDoctorApi,
deleteDoctorApi,
getDoctorTimingApi,
} from "@/api/doctor";
import {getDepartmentsApi} from "@/api/department";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
2026-03-17 16:22:37 +05:30
import {ScrollArea} from "@/components/ui/scroll-area";
2026-03-17 13:11:00 +05:30
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {Button} from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
2026-03-17 16:22:37 +05:30
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
import {
Command,
CommandGroup,
CommandItem,
CommandInput,
} from "@/components/ui/command";
2026-03-17 13:11:00 +05:30
import {Input} from "@/components/ui/input";
import {Loader2, Plus, Pencil, Trash} from "lucide-react";
interface Department {
departmentId: string;
name: string;
}
export default function DoctorPage() {
const [doctors, setDoctors] = useState<any[]>([]);
const [departments, setDepartments] = useState<Department[]>([]);
const [loading, setLoading] = useState(true);
const [openModal, setOpenModal] = useState(false);
const [editing, setEditing] = useState<any>(null);
const [search, setSearch] = useState("");
const [form, setForm] = useState<any>({
doctorId: "",
name: "",
designation: "",
workingStatus: "",
qualification: "",
departments: [],
});
const fetchAll = useCallback(async () => {
setLoading(true);
try {
const [docRes, depRes] = await Promise.all([
getDoctorsApi(),
getDepartmentsApi(),
]);
setDoctors(docRes?.data || []);
setDepartments(depRes?.data || []);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchAll();
}, [fetchAll]);
function handleChange(e: any) {
setForm({...form, [e.target.name]: e.target.value});
}
function handleDepartmentChange(depId: string) {
2026-03-17 16:22:37 +05:30
const exists = form.departments.find((d: any) => d.departmentId === depId);
2026-03-17 13:11:00 +05:30
2026-03-17 16:22:37 +05:30
if (exists) {
setForm({
...form,
departments: form.departments.filter(
(d: any) => d.departmentId !== depId,
),
});
} else {
setForm({
...form,
departments: [
...form.departments,
{
departmentId: depId,
timing: {},
},
],
});
}
2026-03-17 13:11:00 +05:30
}
2026-03-17 16:22:37 +05:30
function handleTimingChange(depId: string, day: string, value: string) {
2026-03-17 13:11:00 +05:30
setForm({
...form,
2026-03-17 16:22:37 +05:30
departments: form.departments.map((d: any) =>
d.departmentId === depId
? {
...d,
timing: {
...d.timing,
[day]: value,
},
}
: d,
),
2026-03-17 13:11:00 +05:30
});
}
function openAdd() {
setEditing(null);
setForm({
doctorId: "",
name: "",
designation: "",
workingStatus: "",
qualification: "",
departments: [],
});
setOpenModal(true);
}
async function openEdit(doc: any) {
setEditing(doc);
try {
2026-03-17 16:22:37 +05:30
const timingRes = await getDoctorTimingApi(doc.doctorId);
const timingData = timingRes?.data?.departments || [];
const mappedDepartments = timingData.map((d: any) => ({
departmentId: d.departmentId,
timing: d.timing || {},
}));
2026-03-17 13:11:00 +05:30
setForm({
2026-03-17 16:22:37 +05:30
doctorId: doc.doctorId,
name: doc.name,
designation: doc.designation,
workingStatus: doc.workingStatus,
qualification: doc.qualification,
departments: mappedDepartments,
2026-03-17 13:11:00 +05:30
});
setOpenModal(true);
} catch (err) {
console.error(err);
}
}
async function handleSubmit() {
try {
const payload = {
2026-03-17 16:22:37 +05:30
doctorId: form.doctorId,
name: form.name,
designation: form.designation,
workingStatus: form.workingStatus,
qualification: form.qualification,
departments: form.departments,
2026-03-17 13:11:00 +05:30
};
if (editing) {
2026-03-17 16:22:37 +05:30
await updateDoctorApi(editing.doctorId, payload);
2026-03-17 13:11:00 +05:30
} else {
await createDoctorApi(payload);
}
setOpenModal(false);
fetchAll();
} catch (err) {
console.error(err);
}
}
async function handleDelete(id: string) {
if (!confirm("Delete doctor?")) return;
await deleteDoctorApi(id);
fetchAll();
}
return (
<div className="p-6 space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">Doctors</h1>
<Button onClick={openAdd}>
<Plus className="mr-2 h-4 w-4" />
Add Doctor
</Button>
</div>
<Card>
<CardHeader>
<CardTitle>Doctor List</CardTitle>
</CardHeader>
<CardContent>
<div className="tw-overflow-x-auto">
<Table className="tw-min-w-[1000px]">
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Designation</TableHead>
<TableHead>Status</TableHead>
<TableHead>Qualification</TableHead>
<TableHead>Departments</TableHead>
<TableHead>Timing</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={8} className="text-center">
<Loader2 className="h-6 w-6 animate-spin mx-auto" />
</TableCell>
</TableRow>
) : (
doctors.map((doc) => (
2026-03-17 16:22:37 +05:30
<TableRow key={doc.doctorId}>
<TableCell>{doc.doctorId}</TableCell>
<TableCell>{doc.name}</TableCell>
<TableCell>{doc.designation}</TableCell>
<TableCell>{doc.workingStatus}</TableCell>
<TableCell>{doc.qualification}</TableCell>
<TableCell>
{doc.departments
?.map((d: any) => d.departmentName)
.join(", ")}
</TableCell>
2026-03-17 13:11:00 +05:30
2026-03-17 16:22:37 +05:30
<TableCell className="max-w-[250px] whitespace-normal">
{doc.departments?.map((d: any) => (
<div key={d.departmentId}>
<b>{d.departmentName}:</b> {d.timing}
</div>
))}
2026-03-17 13:11:00 +05:30
</TableCell>
<TableCell className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => openEdit(doc)}
>
<Pencil className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="destructive"
2026-03-17 16:22:37 +05:30
onClick={() => handleDelete(doc.doctorId)}
2026-03-17 13:11:00 +05:30
>
<Trash className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
{/* MODAL */}
<Dialog open={openModal} onOpenChange={setOpenModal}>
2026-03-17 16:22:37 +05:30
<DialogContent className="overflow-y-auto max-h-[80vh] ">
2026-03-17 13:11:00 +05:30
<DialogHeader>
<DialogTitle>{editing ? "Edit Doctor" : "Add Doctor"}</DialogTitle>
</DialogHeader>
2026-03-17 16:22:37 +05:30
<div className="space-y-4">
2026-03-17 13:11:00 +05:30
<Input
name="doctorId"
placeholder="Doctor ID"
value={form.doctorId}
onChange={handleChange}
disabled={!!editing}
/>
<Input
name="name"
placeholder="Name"
value={form.name}
onChange={handleChange}
/>
<Input
name="designation"
placeholder="Designation"
value={form.designation}
onChange={handleChange}
/>
<Input
name="workingStatus"
placeholder="Working Status"
value={form.workingStatus}
onChange={handleChange}
/>
2026-03-17 16:22:37 +05:30
<Input
name="qualification"
placeholder="Qualification"
value={form.qualification}
onChange={handleChange}
/>
2026-03-17 13:11:00 +05:30
2026-03-17 16:22:37 +05:30
{/* Departments */}
2026-03-17 13:11:00 +05:30
<div>
2026-03-17 16:22:37 +05:30
<p className="font-medium mb-2">Departments</p>
2026-03-17 13:11:00 +05:30
<Popover>
<PopoverTrigger asChild>
2026-03-17 16:22:37 +05:30
<Button className="w-full justify-between h-auto min-h-[40px]">
{form.departments.length > 0 ? (
<div className="flex flex-col items-start gap-1 text-left">
{form.departments.map((d: any) => {
const name = departments.find(
(dep) => dep.departmentId === d.departmentId,
)?.name;
2026-03-17 13:11:00 +05:30
return (
2026-03-17 16:22:37 +05:30
<span key={d.departmentId} className="text-sm">
{name}
</span>
2026-03-17 13:11:00 +05:30
);
})}
2026-03-17 16:22:37 +05:30
</div>
) : (
<span>Select Departments</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
<Command>
<CommandInput placeholder="Search department..." />
<CommandGroup className="max-h-[250px] overflow-y-auto">
{departments.map((dep) => {
const selected = form.departments.some(
(d: any) => d.departmentId === dep.departmentId,
);
return (
<CommandItem
key={dep.departmentId}
className="flex justify-between"
onSelect={() =>
handleDepartmentChange(dep.departmentId)
}
>
<span>{dep.name}</span>
{selected && <span></span>}
</CommandItem>
);
})}
2026-03-17 13:11:00 +05:30
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
2026-03-17 16:22:37 +05:30
{form.departments.map((dep: any) => {
const depName = departments.find(
(d) => d.departmentId === dep.departmentId,
)?.name;
return (
<div
key={dep.departmentId}
className="tw-border tw-p-3 tw-rounded"
>
<p className="tw-font-semibold">{depName}</p>
{[
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
"additional",
].map((day) => (
<Input
key={day}
placeholder={day}
value={dep.timing?.[day] || ""}
onChange={(e) =>
handleTimingChange(
dep.departmentId,
day,
e.target.value,
)
}
/>
))}
</div>
);
})}
2026-03-17 13:11:00 +05:30
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpenModal(false)}>
Cancel
</Button>
<Button onClick={handleSubmit}>
{editing ? "Update" : "Create"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}