import { useState, useEffect, useCallback } from "react"; import { AxiosError } from "axios"; import { Eye } from "lucide-react"; import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader"; import { getDoctorsApi, createDoctorApi, updateDoctorApi, getDoctorTimingApi, } from "@/api/doctor"; import { getDepartmentsApi } from "@/api/department"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; 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"; import SeoPreview from "@/components/SeoPreview/SeoPreview"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Loader2, RefreshCw, Plus, Pencil, ChevronLeft, ChevronRight, } from "lucide-react"; import { Textarea } from "@/components/ui/textarea"; interface Department { departmentId: string; name: string; } const DAYS = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "additional", ]; export default function DoctorPage() { const WEBSITE_URL = import.meta.env.VITE_WEBSITE_URL; 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); const [searchText, setSearchText] = useState(""); const [filterDepartment, setFilterDepartment] = useState(""); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 10; const [form, setForm] = useState({ doctorId: "", name: "", image: "", designation: "", workingStatus: "", qualification: "", isActive: true, globalSortOrder: 0, departments: [], professionalSummary: "", seoTitle: "", metaDescription: "", ogTitle: "", ogDescription: "", ogImage: "", specializations: [ { name: "", description: "", }, ], focusKeyphrase: "", slug: "", tags: [], }); const [openOgPreview, setOpenOgPreview] = useState(false); const [previewDoctor, setPreviewDoctor] = useState(null); const fetchAll = useCallback(async () => { setLoading(true); setError(""); try { const [docRes, depRes] = await Promise.all([ getDoctorsApi(), getDepartmentsApi(), ]); setDoctors(docRes?.data || []); setDepartments(depRes?.data || []); } catch (err) { if (err instanceof AxiosError) { setError(err.response?.data?.message || "Failed to load data"); } else { setError("Something went wrong"); } } finally { setLoading(false); } }, []); useEffect(() => { fetchAll(); }, [fetchAll]); const filteredDoctors = doctors .filter((doc) => { const matchesSearch = doc.name.toLowerCase().includes(searchText.toLowerCase()) || doc.doctorId.toLowerCase().includes(searchText.toLowerCase()); const matchesDepartment = filterDepartment ? doc.departments?.some((d: any) => d.departmentId === filterDepartment) : true; return matchesSearch && matchesDepartment; }) .sort((a, b) => { if (!filterDepartment) { return a.globalSortOrder - b.globalSortOrder; } const aDept = a.departments.find( (d: any) => d.departmentId === filterDepartment, ); const bDept = b.departments.find( (d: any) => d.departmentId === filterDepartment, ); return ( (aDept?.deptSortOrder ?? Number.MAX_SAFE_INTEGER) - (bDept?.deptSortOrder ?? Number.MAX_SAFE_INTEGER) ); }); useEffect(() => { setCurrentPage(1); }, [searchText, filterDepartment]); const totalPages = Math.ceil(filteredDoctors.length / itemsPerPage); const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; const currentItems = filteredDoctors.slice(indexOfFirstItem, indexOfLastItem); function handleChange(e: any) { let value = e.target.type === "number" ? Number(e.target.value) : e.target.value; if (e.target.name === "slug") { value = value .toLowerCase() .replace(/\s+/g, "-") // replace spaces with - .replace(/[^\w-]+/g, "") // remove special chars .replace(/--+/g, "-"); // remove duplicate - } setForm({ ...form, [e.target.name]: value }); } const handleToggleStatus = async (doc: any) => { try { const newStatus = !doc.isActive; const payload = { isActive: newStatus, }; await updateDoctorApi(doc.doctorId, payload, "toggleStatus"); fetchAll(); } catch (err) { console.error("Failed to update status", err); } }; function handleDepartmentToggle(depId: string) { const exists = form.departments.find((d: any) => d.departmentId === depId); if (exists) { setForm({ ...form, departments: form.departments.filter( (d: any) => d.departmentId !== depId, ), }); } else { setForm({ ...form, departments: [ ...form.departments, { departmentId: depId, sortOrder: 0, timing: {} }, ], }); } } function handleDeptSortChange(depId: string, value: string) { setForm({ ...form, departments: form.departments.map((d: any) => d.departmentId === depId ? { ...d, sortOrder: Number(value) } : d, ), }); } function handleTimingChange(depId: string, day: string, value: string) { setForm({ ...form, departments: form.departments.map((d: any) => d.departmentId === depId ? { ...d, timing: { ...d.timing, [day]: value } } : d, ), }); } function openAdd() { setEditing(null); setForm({ doctorId: "", name: "", image: "", designation: "", workingStatus: "", qualification: "", experience: "", professionalSummary: "", isActive: true, globalSortOrder: 0, specializations: [ { name: "", description: "", }, ], departments: [], seoTitle: "", metaDescription: "", focusKeyphrase: "", slug: "", tags: [], ogTitle: "", ogDescription: "", ogImage: "", }); setOpenModal(true); } async function openEdit(doc: any) { setEditing(doc); try { const timingRes = await getDoctorTimingApi(doc.doctorId); const timingData = timingRes?.data?.departments || []; setForm({ doctorId: doc.doctorId, name: doc.name, image: doc.image || "", designation: doc.designation, workingStatus: doc.workingStatus, qualification: doc.qualification, isActive: doc.isActive ?? true, globalSortOrder: doc.globalSortOrder ?? 0, experience: doc.experience || "", professionalSummary: doc.professionalSummary || "", seoTitle: doc.seo?.seoTitle || "", metaDescription: doc.seo?.metaDescription || "", focusKeyphrase: doc.seo?.focusKeyphrase || "", slug: doc.seo?.slug || "", tags: doc.seo?.tags || [], ogTitle: doc.seo?.ogTitle || "", ogDescription: doc.seo?.ogDescription || "", ogImage: doc.seo?.ogImage || "", specializations: doc.specializations?.length ? doc.specializations.map((item: any) => ({ name: item.name || "", description: item.description || "", })) : [ { name: "", description: "", }, ], departments: timingData.map((d: any) => ({ departmentId: d.departmentId, sortOrder: d.deptSortOrder ?? 0, timing: d.timing || {}, })), }); setOpenModal(true); } catch (err) { console.error("Error fetching doctor details:", err); } } function handlePreview(doc: any) { setPreviewDoctor(doc); setOpenOgPreview(true); } async function handleSubmit() { try { if (editing) { await updateDoctorApi(editing.doctorId, form); } else { await createDoctorApi(form); } setOpenModal(false); fetchAll(); } catch (error) { console.error(error); } } const createSlug = (text: string) => { if (!text) return ""; return text .toString() .toLowerCase() .trim() .replace(/\s+/g, "-") .replace(/[^\w-]+/g, "") .replace(/--+/g, "-"); }; const getDoctorUrl = (doctor: any) => { const slug = doctor?.seo?.slug || createSlug(doctor?.name); return `${WEBSITE_URL}/${doctor?.doctorId}/${slug}`; }; return (

Doctors

setSearchText(e.target.value)} className="w-[250px] text-base" />
{error && (
{error}
)} Doctor List
Priority{" "} Doctor Info Designation Departments (Hierarchy) Status (Active) Actions {loading ? ( ) : currentItems.length === 0 ? ( No doctors found ) : ( currentItems.map((doc) => ( {doc.globalSortOrder}
{doc.name}
{doc.doctorId}
{doc.designation || "-"}
{doc.workingStatus}
{doc.departments?.map((d: any) => ( {d.departmentName} {d.deptSortOrder} ))}
handleToggleStatus(doc)} /> {doc.isActive ? "Active" : "Hidden"}
)) )}
{!loading && filteredDoctors.length > 0 && (
Showing{" "} {indexOfFirstItem + 1} to{" "} {Math.min(indexOfLastItem, filteredDoctors.length)} {" "} of{" "} {filteredDoctors.length}{" "} doctors
Page {currentPage} of {totalPages}
)}
{editing ? "Edit Doctor" : "Add Doctor"}

Profile & Visibility

setForm({ ...form, image: url })} />
setForm({ ...form, isActive: val }) } />