Merge pull request 'feat: implement sorting and visibility changes' (#23) from feat/inactive-field into dev

Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
2026-05-11 05:35:39 +00:00
15 changed files with 554 additions and 231 deletions
@@ -0,0 +1,14 @@
-- AlterTable
ALTER TABLE "Career" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "sortOrder" INTEGER NOT NULL DEFAULT 0;
-- AlterTable
ALTER TABLE "Department" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "sortOrder" INTEGER NOT NULL DEFAULT 0;
-- AlterTable
ALTER TABLE "Doctor" ADD COLUMN "globalSortOrder" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "DoctorDepartment" ADD COLUMN "sortOrder" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,11 @@
-- AlterTable
ALTER TABLE "Career" ALTER COLUMN "sortOrder" SET DEFAULT 1000;
-- AlterTable
ALTER TABLE "Department" ALTER COLUMN "sortOrder" SET DEFAULT 1000;
-- AlterTable
ALTER TABLE "Doctor" ALTER COLUMN "globalSortOrder" SET DEFAULT 1000;
-- AlterTable
ALTER TABLE "DoctorDepartment" ALTER COLUMN "sortOrder" SET DEFAULT 1000;
+8 -2
View File
@@ -25,6 +25,8 @@ model Doctor {
designation String? designation String?
workingStatus String? workingStatus String?
qualification String? qualification String?
isActive Boolean @default(true)
globalSortOrder Int @default(1000)
departments DoctorDepartment[] departments DoctorDepartment[]
appointments Appointment[] appointments Appointment[]
@@ -46,6 +48,9 @@ model Department {
facilities String? facilities String?
services String? services String?
isActive Boolean @default(true)
sortOrder Int @default(1000)
doctors DoctorDepartment[] doctors DoctorDepartment[]
appointments Appointment[] appointments Appointment[]
@@ -61,7 +66,7 @@ model DoctorDepartment {
doctor Doctor @relation(fields: [doctorId], references: [id]) doctor Doctor @relation(fields: [doctorId], references: [id])
department Department @relation(fields: [departmentId], references: [id]) department Department @relation(fields: [departmentId], references: [id])
sortOrder Int @default(1000)
timing DoctorTiming? timing DoctorTiming?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -111,7 +116,8 @@ model Career {
email String? email String?
number String? number String?
status String @default("new") status String @default("new")
isActive Boolean @default(true)
sortOrder Int @default(1000)
candidates Candidate[] candidates Candidate[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
+16 -2
View File
@@ -4,8 +4,11 @@ import prisma from "../prisma/client.js";
export const getAllCareers = async (req, res) => { export const getAllCareers = async (req, res) => {
try { try {
const { admin } = req.query;
const careers = await prisma.career.findMany({ const careers = await prisma.career.findMany({
orderBy: {createdAt: "desc"}, where: admin === "true" ? {} : { isActive: true },
orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }],
}); });
const response = careers.map((c) => ({ const response = careers.map((c) => ({
@@ -17,6 +20,8 @@ export const getAllCareers = async (req, res) => {
email: c.email, email: c.email,
number: c.number, number: c.number,
status: c.status, status: c.status,
isActive: c.isActive,
sortOrder: c.sortOrder,
})); }));
return res.status(200).json({ return res.status(200).json({
@@ -44,6 +49,8 @@ export const createCareer = async (req, res) => {
email, email,
number, number,
status, status,
isActive,
sortOrder,
} = req.body; } = req.body;
if (!post || !designation) { if (!post || !designation) {
@@ -62,6 +69,8 @@ export const createCareer = async (req, res) => {
email, email,
number, number,
status, status,
isActive: isActive !== undefined ? isActive : true,
sortOrder: sortOrder !== undefined ? Number(sortOrder) : 0,
}, },
}); });
@@ -84,10 +93,15 @@ export const createCareer = async (req, res) => {
export const updateCareer = async (req, res) => { export const updateCareer = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const updateData = { ...req.body };
if (updateData.sortOrder !== undefined) {
updateData.sortOrder = Number(updateData.sortOrder);
}
const career = await prisma.career.update({ const career = await prisma.career.update({
where: { id: Number(id) }, where: { id: Number(id) },
data: req.body, data: updateData,
}); });
return res.status(200).json({ return res.status(200).json({
@@ -2,8 +2,11 @@ import prisma from "../prisma/client.js";
export const getAllDepartments = async (req, res) => { export const getAllDepartments = async (req, res) => {
try { try {
const {admin} = req.query;
const departments = await prisma.department.findMany({ const departments = await prisma.department.findMany({
orderBy: {name: "asc"}, where: admin === "true" ? {} : {isActive: true},
orderBy: [{sortOrder: "asc"}, {name: "asc"}],
}); });
const response = departments.map((dep) => ({ const response = departments.map((dep) => ({
@@ -15,6 +18,8 @@ export const getAllDepartments = async (req, res) => {
para3: dep.para3 ?? "", para3: dep.para3 ?? "",
facilities: dep.facilities ?? "", facilities: dep.facilities ?? "",
services: dep.services ?? "", services: dep.services ?? "",
isActive: dep.isActive,
sortOrder: dep.sortOrder,
})); }));
return res.status(200).json({ return res.status(200).json({
@@ -44,13 +49,14 @@ export const getDepartmentByName = async (req, res) => {
const department = await prisma.department.findFirst({ const department = await prisma.department.findFirst({
where: { where: {
name: name, name: name,
isActive: true,
}, },
}); });
if (!department) { if (!department) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: "Department not found", message: "Department not found or inactive",
}); });
} }
@@ -63,6 +69,8 @@ export const getDepartmentByName = async (req, res) => {
para3: department.para3 ?? "", para3: department.para3 ?? "",
facilities: department.facilities ?? "", facilities: department.facilities ?? "",
services: department.services ?? "", services: department.services ?? "",
isActive: department.isActive,
sortOrder: department.sortOrder,
}; };
return res.status(200).json({ return res.status(200).json({
@@ -89,6 +97,8 @@ export async function createDepartment(req, res) {
para3, para3,
facilities, facilities,
services, services,
isActive,
sortOrder,
} = req.body; } = req.body;
if (!departmentId || !name) { if (!departmentId || !name) {
@@ -107,6 +117,8 @@ export async function createDepartment(req, res) {
para3, para3,
facilities, facilities,
services, services,
isActive: isActive !== undefined ? isActive : true,
sortOrder: sortOrder !== undefined ? Number(sortOrder) : 0,
}, },
}); });
@@ -118,7 +130,7 @@ export async function createDepartment(req, res) {
if (error.code === "P2002") { if (error.code === "P2002") {
return res.status(409).json({error: "Department already exists"}); return res.status(409).json({error: "Department already exists"});
} }
console.error(error);
res.status(500).json({error: "Failed to create department"}); res.status(500).json({error: "Failed to create department"});
} }
} }
@@ -126,20 +138,15 @@ export async function createDepartment(req, res) {
export const updateDepartment = async (req, res) => { export const updateDepartment = async (req, res) => {
try { try {
const {departmentId} = req.params; const {departmentId} = req.params;
const updateData = {...req.body};
const {name, image, para1, para2, para3, facilities, services} = req.body; if (updateData.sortOrder !== undefined) {
updateData.sortOrder = Number(updateData.sortOrder);
}
const department = await prisma.department.update({ const department = await prisma.department.update({
where: {departmentId}, where: {departmentId},
data: { data: updateData,
name,
image,
para1,
para2,
para3,
facilities,
services,
},
}); });
return res.status(200).json({ return res.status(200).json({
+43 -22
View File
@@ -4,7 +4,10 @@ import prisma from "../prisma/client.js";
export const getAllDoctors = async (req, res) => { export const getAllDoctors = async (req, res) => {
try { try {
const {admin} = req.query;
const doctors = await prisma.doctor.findMany({ const doctors = await prisma.doctor.findMany({
where: admin === "true" ? {} : {isActive: true},
include: { include: {
departments: { departments: {
include: { include: {
@@ -13,7 +16,7 @@ export const getAllDoctors = async (req, res) => {
}, },
}, },
}, },
orderBy: {name: "asc"}, orderBy: [{globalSortOrder: "asc"}, {name: "asc"}],
}); });
const formatted = doctors.map((doc, index) => ({ const formatted = doctors.map((doc, index) => ({
@@ -24,10 +27,10 @@ export const getAllDoctors = async (req, res) => {
designation: doc.designation, designation: doc.designation,
workingStatus: doc.workingStatus, workingStatus: doc.workingStatus,
qualification: doc.qualification, qualification: doc.qualification,
isActive: doc.isActive,
globalSortOrder: doc.globalSortOrder,
departments: doc.departments.map((d) => { departments: doc.departments.map((d) => {
const t = d.timing || {}; const t = d.timing || {};
const timingArray = [ const timingArray = [
t.monday && `Monday ${t.monday}`, t.monday && `Monday ${t.monday}`,
t.tuesday && `Tuesday ${t.tuesday}`, t.tuesday && `Tuesday ${t.tuesday}`,
@@ -43,6 +46,7 @@ export const getAllDoctors = async (req, res) => {
departmentId: d.department.departmentId, departmentId: d.department.departmentId,
departmentName: d.department.name, departmentName: d.department.name,
timing: timingArray.join(" & "), timing: timingArray.join(" & "),
deptSortOrder: d.sortOrder,
}; };
}), }),
})); }));
@@ -135,17 +139,23 @@ export const getDoctorsByDepartmentId = async (req, res) => {
}); });
} }
const doctors = await prisma.doctorDepartment.findMany({ const doctorsInDept = await prisma.doctorDepartment.findMany({
where: {departmentId: department.id}, where: {
departmentId: department.id,
doctor: {isActive: true},
},
include: { include: {
doctor: true, doctor: true,
}, },
orderBy: {sortOrder: "asc"},
}); });
const result = doctors.map((d) => ({ const result = doctorsInDept.map((d) => ({
GG_ID: d.doctor.doctorId, GG_ID: d.doctor.doctorId,
Name: d.doctor.name, Name: d.doctor.name,
image: d.doctor.image ?? "", image: d.doctor.image ?? "",
designation: d.doctor.designation,
hierarchyOrder: d.sortOrder,
})); }));
res.status(200).json({ res.status(200).json({
@@ -171,6 +181,8 @@ export const createDoctor = async (req, res) => {
designation, designation,
workingStatus, workingStatus,
qualification, qualification,
isActive,
globalSortOrder,
departments, departments,
} = req.body; } = req.body;
@@ -182,6 +194,9 @@ export const createDoctor = async (req, res) => {
designation, designation,
workingStatus, workingStatus,
qualification, qualification,
isActive: isActive !== undefined ? isActive : true,
globalSortOrder:
globalSortOrder !== undefined ? Number(globalSortOrder) : 0,
}, },
}); });
@@ -196,6 +211,7 @@ export const createDoctor = async (req, res) => {
data: { data: {
doctorId: doctor.id, doctorId: doctor.id,
departmentId: department.id, departmentId: department.id,
sortOrder: dep.sortOrder !== undefined ? Number(dep.sortOrder) : 0,
}, },
}); });
@@ -232,22 +248,29 @@ export const updateDoctor = async (req, res) => {
image, image,
workingStatus, workingStatus,
qualification, qualification,
isActive,
globalSortOrder,
departments, departments,
} = req.body; } = req.body;
const doctor = await prisma.doctor.findUnique({ const doctor = await prisma.doctor.findUnique({where: {doctorId}});
where: {doctorId}, if (!doctor)
});
if (!doctor) {
return res return res
.status(404) .status(404)
.json({success: false, message: "Doctor not found"}); .json({success: false, message: "Doctor not found"});
}
await prisma.doctor.update({ await prisma.doctor.update({
where: {id: doctor.id}, where: {id: doctor.id},
data: {name, designation, image, workingStatus, qualification}, data: {
name,
designation,
image,
workingStatus,
qualification,
isActive,
globalSortOrder:
globalSortOrder !== undefined ? Number(globalSortOrder) : undefined,
},
}); });
const oldRelations = await prisma.doctorDepartment.findMany({ const oldRelations = await prisma.doctorDepartment.findMany({
@@ -265,16 +288,16 @@ export const updateDoctor = async (req, res) => {
}); });
for (const dep of departments) { for (const dep of departments) {
const department = await prisma.department.findUnique({ const targetDept = await prisma.department.findUnique({
where: {departmentId: dep.departmentId}, where: {departmentId: dep.departmentId},
}); });
if (!targetDept) continue;
if (!department) continue; const newDD = await prisma.doctorDepartment.create({
const doctorDepartment = await prisma.doctorDepartment.create({
data: { data: {
doctorId: doctor.id, doctorId: doctor.id,
departmentId: department.id, departmentId: targetDept.id,
sortOrder: dep.sortOrder !== undefined ? Number(dep.sortOrder) : 0,
}, },
}); });
@@ -283,10 +306,7 @@ export const updateDoctor = async (req, res) => {
dep.timing; dep.timing;
await prisma.doctorTiming.create({ await prisma.doctorTiming.create({
data: { data: {doctorDepartmentId: newDD.id, ...cleanTiming},
doctorDepartmentId: doctorDepartment.id,
...cleanTiming,
},
}); });
} }
} }
@@ -421,6 +441,7 @@ export const getDoctorTimingById = async (req, res) => {
departments: doctor.departments.map((d) => ({ departments: doctor.departments.map((d) => ({
departmentId: d.department.departmentId, departmentId: d.department.departmentId,
departmentName: d.department.name, departmentName: d.department.name,
deptSortOrder: d.sortOrder,
timing: d.timing || {}, timing: d.timing || {},
})), })),
}; };
+27 -1
View File
@@ -28,6 +28,7 @@
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.13.1", "react-router-dom": "^7.13.1",
"shadcn": "^4.0.5", "shadcn": "^4.0.5",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
@@ -5187,7 +5188,6 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/data-uri-to-buffer": { "node_modules/data-uri-to-buffer": {
@@ -6352,6 +6352,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -8136,6 +8145,23 @@
"react": "^19.2.4" "react": "^19.2.4"
} }
}, },
"node_modules/react-hot-toast": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.18.0", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+1
View File
@@ -30,6 +30,7 @@
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.13.1", "react-router-dom": "^7.13.1",
"shadcn": "^4.0.5", "shadcn": "^4.0.5",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
+3
View File
@@ -1,4 +1,5 @@
import {BrowserRouter, Routes, Route, Navigate} from "react-router-dom"; import {BrowserRouter, Routes, Route, Navigate} from "react-router-dom";
import {Toaster} from "react-hot-toast";
import Login from "@/pages/Login"; import Login from "@/pages/Login";
@@ -26,6 +27,8 @@ import ImportData from "./pages/ImportData";
export default function App() { export default function App() {
return ( return (
<BrowserRouter> <BrowserRouter>
<Toaster position="top-right" />
<AuthProvider> <AuthProvider>
<Routes> <Routes>
<Route element={<PublicRoute />}> <Route element={<PublicRoute />}>
+39 -1
View File
@@ -1,11 +1,49 @@
import apiClient from "@/api/client"; import apiClient from "@/api/client";
import toast from "react-hot-toast";
export const getCareersApi = async () => { export const getCareersApi = async () => {
const res = await apiClient.get("/careers/getAll"); const res = await apiClient.get("/careers/getAll?admin=true");
return res.data; return res.data;
}; };
export const createCareerApi = async (data: any) => {
try {
const res = await apiClient.post("/careers", data);
toast.success("Career created successfully");
return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to create career");
throw error;
}
};
export const updateCareerApi = async (id: number, data: any) => {
try {
const res = await apiClient.patch(`/careers/${id}`, data);
toast.success("Career updated successfully");
return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to update career");
throw error;
}
};
export const deleteCareerApi = async (id: number) => { export const deleteCareerApi = async (id: number) => {
try {
const res = await apiClient.delete(`/careers/${id}`); const res = await apiClient.delete(`/careers/${id}`);
toast.success("Career deleted successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to delete career");
throw error;
}
}; };
+37 -1
View File
@@ -1,4 +1,5 @@
import apiClient from "@/api/client"; import apiClient from "@/api/client";
import toast from "react-hot-toast";
export interface Department { export interface Department {
departmentId: string; departmentId: string;
@@ -9,10 +10,12 @@ export interface Department {
para3: string; para3: string;
facilities: string; facilities: string;
services: string; services: string;
isActive?: boolean;
sortOrder?: number;
} }
export const getDepartmentsApi = async () => { export const getDepartmentsApi = async () => {
const res = await apiClient.get("/departments/getAll"); const res = await apiClient.get("/departments/getAll?admin=true");
return res.data; return res.data;
}; };
@@ -25,8 +28,19 @@ export const createDepartmentApi = async (data: {
facilities?: string; facilities?: string;
services?: string; services?: string;
}) => { }) => {
try {
const res = await apiClient.post("/departments", data); const res = await apiClient.post("/departments", data);
toast.success("Department created successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(
error?.response?.data?.message || "Failed to create department",
);
throw error;
}
}; };
export const updateDepartmentApi = async ( export const updateDepartmentApi = async (
@@ -40,11 +54,33 @@ export const updateDepartmentApi = async (
services?: string; services?: string;
}, },
) => { ) => {
try {
const res = await apiClient.put(`/departments/${departmentId}`, data); const res = await apiClient.put(`/departments/${departmentId}`, data);
toast.success("Department updated successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(
error?.response?.data?.message || "Failed to update department",
);
throw error;
}
}; };
export const deleteDepartmentApi = async (departmentId: string) => { export const deleteDepartmentApi = async (departmentId: string) => {
try {
const res = await apiClient.delete(`/departments/${departmentId}`); const res = await apiClient.delete(`/departments/${departmentId}`);
toast.success("Department deleted successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(
error?.response?.data?.message || "Failed to delete department",
);
throw error;
}
}; };
+29 -1
View File
@@ -1,4 +1,5 @@
import apiClient from "@/api/client"; import apiClient from "@/api/client";
import toast from "react-hot-toast";
export interface Doctor { export interface Doctor {
doctorId: string; doctorId: string;
@@ -24,7 +25,7 @@ export interface Doctor {
} }
export const getDoctorsApi = async () => { export const getDoctorsApi = async () => {
const res = await apiClient.get("/doctors/getAll"); const res = await apiClient.get("/doctors/getAll?admin=true");
return res.data; return res.data;
}; };
@@ -34,21 +35,48 @@ export const getDoctorByIdApi = async (doctorId: string) => {
}; };
export const createDoctorApi = async (data: Doctor) => { export const createDoctorApi = async (data: Doctor) => {
try {
const res = await apiClient.post("/doctors", data); const res = await apiClient.post("/doctors", data);
toast.success("Doctor created successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to create doctor");
throw error;
}
}; };
export const updateDoctorApi = async ( export const updateDoctorApi = async (
doctorId: string, doctorId: string,
data: Partial<Doctor>, data: Partial<Doctor>,
) => { ) => {
try {
const res = await apiClient.patch(`/doctors/${doctorId}`, data); const res = await apiClient.patch(`/doctors/${doctorId}`, data);
toast.success("Doctor updated successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to update doctor");
throw error;
}
}; };
export const deleteDoctorApi = async (doctorId: string) => { export const deleteDoctorApi = async (doctorId: string) => {
try {
const res = await apiClient.delete(`/doctors/${doctorId}`); const res = await apiClient.delete(`/doctors/${doctorId}`);
toast.success("Doctor deleted successfully");
return res.data; return res.data;
} catch (error: any) {
toast.error(error?.response?.data?.message || "Failed to delete doctor");
throw error;
}
}; };
export const getDoctorTimingApi = async (doctorId: string) => { export const getDoctorTimingApi = async (doctorId: string) => {
+66 -43
View File
@@ -1,7 +1,6 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { getCareersApi, deleteCareerApi } from "@/api/career"; import { getCareersApi, updateCareerApi, createCareerApi } from "@/api/career";
import apiClient from "@/api/client";
import { import {
Table, Table,
@@ -24,12 +23,13 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { import {
Loader2, Loader2,
Plus, Plus,
Pencil, Pencil,
Trash,
RefreshCw, RefreshCw,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
@@ -55,6 +55,8 @@ export default function CareerPage() {
email: "", email: "",
number: "", number: "",
status: "new", status: "new",
isActive: true,
sortOrder: 0,
}); });
const fetchAll = useCallback(async () => { const fetchAll = useCallback(async () => {
@@ -92,6 +94,18 @@ export default function CareerPage() {
setForm({ ...form, [e.target.name]: e.target.value }); setForm({ ...form, [e.target.name]: e.target.value });
} }
const handleToggleStatus = async (item: any) => {
try {
await updateCareerApi(item.id, {
...item,
isActive: !item.isActive,
} as any);
fetchAll();
} catch (error) {
console.error("Failed to toggle status", error);
}
};
function openAdd() { function openAdd() {
setEditing(null); setEditing(null);
setForm({ setForm({
@@ -102,6 +116,8 @@ export default function CareerPage() {
email: "", email: "",
number: "", number: "",
status: "new", status: "new",
isActive: true,
sortOrder: 0,
}); });
setOpenModal(true); setOpenModal(true);
} }
@@ -116,6 +132,8 @@ export default function CareerPage() {
email: item.email || "", email: item.email || "",
number: item.number || "", number: item.number || "",
status: item.status || "new", status: item.status || "new",
isActive: item.isActive ?? true,
sortOrder: item.sortOrder ?? 0,
}); });
setOpenModal(true); setOpenModal(true);
} }
@@ -123,10 +141,11 @@ export default function CareerPage() {
async function handleSubmit() { async function handleSubmit() {
try { try {
if (editing) { if (editing) {
await apiClient.patch(`/careers/${editing.id}`, form); await updateCareerApi(editing.id, form);
} else { } else {
await apiClient.post("/careers", form); await createCareerApi(form);
} }
setOpenModal(false); setOpenModal(false);
fetchAll(); fetchAll();
} catch (err) { } catch (err) {
@@ -134,12 +153,6 @@ export default function CareerPage() {
} }
} }
async function handleDelete(id: number) {
if (!confirm("Delete career?")) return;
await deleteCareerApi(id);
fetchAll();
}
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4"> <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
@@ -177,13 +190,13 @@ export default function CareerPage() {
<CardContent className="p-0 sm:p-6 space-y-4"> <CardContent className="p-0 sm:p-6 space-y-4">
<div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative"> <div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative">
<Table className="w-full min-w-[1000px] table-fixed border-separate border-spacing-0"> <Table className="w-full min-w-[800px] table-fixed border-separate border-spacing-0">
<TableHeader className="sticky top-0 z-20 bg-background shadow-sm"> <TableHeader className="sticky top-0 z-20 bg-background shadow-sm">
<TableRow> <TableRow>
<TableHead className="w-[60px] bg-background font-bold text-sm"> <TableHead className="w-[80px] bg-background font-bold text-sm">
ID Priority
</TableHead> </TableHead>
<TableHead className="w-[200px] bg-background font-bold text-sm"> <TableHead className="w-[250px] bg-background font-bold text-sm">
Post & Designation Post & Designation
</TableHead> </TableHead>
<TableHead className="w-[200px] bg-background font-bold text-sm"> <TableHead className="w-[200px] bg-background font-bold text-sm">
@@ -192,13 +205,10 @@ export default function CareerPage() {
<TableHead className="w-[120px] bg-background font-bold text-sm"> <TableHead className="w-[120px] bg-background font-bold text-sm">
Experience Experience
</TableHead> </TableHead>
<TableHead className="w-[200px] bg-background font-bold text-sm"> <TableHead className="w-[80px] bg-background font-bold text-sm">
Contact Info Status (Active)
</TableHead> </TableHead>
<TableHead className="w-[100px] bg-background font-bold text-sm"> <TableHead className="w-[80px] bg-background font-bold text-right text-sm">
Status
</TableHead>
<TableHead className="w-[120px] bg-background font-bold text-right text-sm">
Actions Actions
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -207,14 +217,14 @@ export default function CareerPage() {
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow> <TableRow>
<TableCell colSpan={7} className="text-center py-10"> <TableCell colSpan={6} className="text-center py-10">
<Loader2 className="h-8 w-8 animate-spin mx-auto" /> <Loader2 className="h-8 w-8 animate-spin mx-auto" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) : currentItems.length === 0 ? ( ) : currentItems.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={7} colSpan={6}
className="text-center text-muted-foreground py-10 text-base" className="text-center text-muted-foreground py-10 text-base"
> >
No careers found No careers found
@@ -224,7 +234,7 @@ export default function CareerPage() {
currentItems.map((item) => ( currentItems.map((item) => (
<TableRow key={item.id} className="hover:bg-muted/50"> <TableRow key={item.id} className="hover:bg-muted/50">
<TableCell className="font-mono text-xs"> <TableCell className="font-mono text-xs">
{item.id} {item.sortOrder}
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="font-semibold text-base truncate"> <div className="font-semibold text-base truncate">
@@ -243,20 +253,18 @@ export default function CareerPage() {
{item.experienceNeed} {item.experienceNeed}
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-sm font-medium">{item.email}</div> <div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground"> <Switch
{item.number} checked={item.isActive}
</div> onCheckedChange={() => handleToggleStatus(item)}
</TableCell> />
<TableCell>
<Badge <Badge
variant={ variant={item.isActive ? "default" : "secondary"}
item.status === "active" ? "default" : "secondary"
}
className="capitalize" className="capitalize"
> >
{item.status} {item.isActive ? "Active" : "Hidden"}
</Badge> </Badge>
</div>
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
@@ -269,15 +277,6 @@ export default function CareerPage() {
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
</Button> </Button>
<Button
size="icon"
variant="ghost"
className="h-9 w-9 text-destructive hover:text-destructive hover:bg-destructive/10"
onClick={() => handleDelete(item.id)}
>
<Trash className="h-4 w-4" />
</Button>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -393,6 +392,30 @@ export default function CareerPage() {
onChange={handleChange} onChange={handleChange}
className="text-base" className="text-base"
/> />
<div className="flex items-center justify-between p-2 border rounded-md">
<Label htmlFor="isActive" className="text-base">
Active
</Label>
<Switch
id="isActive"
checked={form.isActive}
onCheckedChange={(val) => setForm({ ...form, isActive: val })}
/>
</div>
<div className="space-y-1">
<Label htmlFor="sortOrder" className="text-sm">
Sort Priority (Lower numbers show first)
</Label>
<Input
id="sortOrder"
name="sortOrder"
type="number"
placeholder="Sort Order"
value={form.sortOrder}
onChange={handleChange}
className="text-base"
/>
</div>
</div> </div>
</div> </div>
+85 -58
View File
@@ -6,7 +6,6 @@ import {
getDepartmentsApi, getDepartmentsApi,
createDepartmentApi, createDepartmentApi,
updateDepartmentApi, updateDepartmentApi,
deleteDepartmentApi,
} from "@/api/department"; } from "@/api/department";
import { import {
@@ -31,13 +30,15 @@ import {
import {Input} from "@/components/ui/input"; import {Input} from "@/components/ui/input";
import {Textarea} from "@/components/ui/textarea"; import {Textarea} from "@/components/ui/textarea";
import {Switch} from "@/components/ui/switch";
import {Label} from "@/components/ui/label";
import {Badge} from "@/components/ui/badge";
import { import {
Loader2, Loader2,
RefreshCw, RefreshCw,
Plus, Plus,
Pencil, Pencil,
Trash,
Eye, Eye,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
@@ -52,6 +53,8 @@ interface Department {
para3: string; para3: string;
facilities: string; facilities: string;
services: string; services: string;
isActive: boolean;
sortOrder: number;
} }
export default function DepartmentPage() { export default function DepartmentPage() {
@@ -79,6 +82,8 @@ export default function DepartmentPage() {
para3: "", para3: "",
facilities: "", facilities: "",
services: "", services: "",
isActive: true,
sortOrder: 0,
}); });
const fetchDepartments = useCallback(async () => { const fetchDepartments = useCallback(async () => {
@@ -122,13 +127,23 @@ export default function DepartmentPage() {
); );
function handleChange(e: any) { function handleChange(e: any) {
setForm({...form, [e.target.name]: e.target.value}); const value =
e.target.type === "number" ? Number(e.target.value) : e.target.value;
setForm({...form, [e.target.name]: value});
} }
function truncate(text: string, limit = 60) { const handleToggleStatus = async (dep: Department) => {
if (!text) return "-"; try {
return text.length > limit ? text.substring(0, limit) + "..." : text; const {departmentId, ...updateData} = dep;
await updateDepartmentApi(departmentId, {
...updateData,
isActive: !dep.isActive,
} as any);
fetchDepartments();
} catch (error) {
console.error("Failed to toggle status", error);
} }
};
function openAdd() { function openAdd() {
setEditing(null); setEditing(null);
@@ -141,13 +156,19 @@ export default function DepartmentPage() {
para3: "", para3: "",
facilities: "", facilities: "",
services: "", services: "",
isActive: true,
sortOrder: 0,
}); });
setOpenModal(true); setOpenModal(true);
} }
function openEdit(dep: Department) { function openEdit(dep: Department) {
setEditing(dep); setEditing(dep);
setForm(dep); setForm({
...dep,
isActive: dep.isActive ?? true,
sortOrder: dep.sortOrder ?? 0,
});
setOpenModal(true); setOpenModal(true);
} }
@@ -160,7 +181,7 @@ export default function DepartmentPage() {
try { try {
if (editing) { if (editing) {
const {departmentId, ...updateData} = form; const {departmentId, ...updateData} = form;
await updateDepartmentApi(editing.departmentId, updateData); await updateDepartmentApi(editing.departmentId, form as any);
} else { } else {
await createDepartmentApi(form); await createDepartmentApi(form);
} }
@@ -172,17 +193,6 @@ export default function DepartmentPage() {
} }
} }
async function handleDelete(id: string) {
if (!confirm("Delete this department?")) return;
try {
await deleteDepartmentApi(id);
fetchDepartments();
} catch (error) {
console.error(error);
}
}
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4"> <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
@@ -226,25 +236,19 @@ export default function DepartmentPage() {
<CardContent className="p-0 sm:p-6 space-y-4"> <CardContent className="p-0 sm:p-6 space-y-4">
<div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative"> <div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative">
<Table className="w-full min-w-[900px] table-fixed border-separate border-spacing-0"> <Table className="w-full min-w-[700px] table-fixed border-separate border-spacing-0">
<TableHeader className="sticky top-0 z-20 bg-background shadow-sm"> <TableHeader className="sticky top-0 z-20 bg-background shadow-sm">
<TableRow> <TableRow>
<TableHead className="w-[100px] bg-background text-sm font-bold"> <TableHead className="w-[100px] bg-background text-sm font-bold">
ID Priority
</TableHead> </TableHead>
<TableHead className="w-[200px] bg-background text-sm font-bold"> <TableHead className="w-[300px] bg-background text-sm font-bold">
Name Name
</TableHead> </TableHead>
<TableHead className="w-[250px] bg-background text-sm font-bold"> <TableHead className="w-[80px] bg-background text-sm font-bold">
Para 1 Status (Active)
</TableHead> </TableHead>
<TableHead className="w-[220px] bg-background text-sm font-bold"> <TableHead className="w-[80px] bg-background text-right text-sm font-bold">
Facilities
</TableHead>
<TableHead className="w-[220px] bg-background text-sm font-bold">
Services
</TableHead>
<TableHead className="w-[140px] bg-background text-right text-sm font-bold">
Actions Actions
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -253,14 +257,14 @@ export default function DepartmentPage() {
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow> <TableRow>
<TableCell colSpan={6} className="text-center py-10"> <TableCell colSpan={4} className="text-center py-10">
<Loader2 className="h-8 w-8 animate-spin mx-auto" /> <Loader2 className="h-8 w-8 animate-spin mx-auto" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) : currentItems.length === 0 ? ( ) : currentItems.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={6} colSpan={4}
className="text-center text-muted-foreground py-10 text-base" className="text-center text-muted-foreground py-10 text-base"
> >
No departments found No departments found
@@ -272,8 +276,8 @@ export default function DepartmentPage() {
key={dep.departmentId} key={dep.departmentId}
className="hover:bg-muted/50" className="hover:bg-muted/50"
> >
<TableCell className="font-mono text-xs"> <TableCell className="font-mono text-sm">
{dep.departmentId} {dep.sortOrder}
</TableCell> </TableCell>
<TableCell> <TableCell>
@@ -283,23 +287,21 @@ export default function DepartmentPage() {
> >
{dep.name} {dep.name}
</div> </div>
</TableCell> <div className="text-xs text-muted-foreground">
{dep.departmentId}
<TableCell>
<div className="text-sm break-words whitespace-normal">
{truncate(dep.para1)}
</div> </div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-sm break-words whitespace-normal"> <div className="flex items-center gap-2">
{truncate(dep.facilities)} <Switch
</div> checked={dep.isActive}
</TableCell> onCheckedChange={() => handleToggleStatus(dep)}
/>
<TableCell> <Badge
<div className="text-sm break-words whitespace-normal"> variant={dep.isActive ? "default" : "secondary"}
{truncate(dep.services)} >
{dep.isActive ? "Active" : "Hidden"}
</Badge>
</div> </div>
</TableCell> </TableCell>
@@ -322,15 +324,6 @@ export default function DepartmentPage() {
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
</Button> </Button>
<Button
size="icon"
variant="ghost"
className="h-9 w-9 text-destructive hover:text-destructive hover:bg-destructive/10"
onClick={() => handleDelete(dep.departmentId)}
>
<Trash className="h-4 w-4" />
</Button>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -450,6 +443,32 @@ export default function DepartmentPage() {
onChange={handleChange} onChange={handleChange}
placeholder="Services" placeholder="Services"
/> />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 border-t pt-4">
<div className="flex items-center justify-between p-3 border rounded-md">
<Label htmlFor="isActive" className="text-base cursor-pointer">
Active Visibility
</Label>
<Switch
id="isActive"
checked={form.isActive}
onCheckedChange={(val) => setForm({...form, isActive: val})}
/>
</div>
<div className="space-y-1">
<Label htmlFor="sortOrder">
Sort Priority (Lower numbers show first)
</Label>
<Input
id="sortOrder"
name="sortOrder"
type="number"
value={form.sortOrder}
onChange={handleChange}
placeholder="0"
/>
</div>
</div>
</div> </div>
<DialogFooter> <DialogFooter>
@@ -470,9 +489,17 @@ export default function DepartmentPage() {
</DialogHeader> </DialogHeader>
{viewData && ( {viewData && (
<div className="space-y-4 text-sm"> <div className="space-y-4 text-sm">
<div className="flex gap-4 items-center border-b pb-4">
<Badge variant={viewData.isActive ? "default" : "secondary"}>
{viewData.isActive ? "ACTIVE" : "HIDDEN"}
</Badge>
<p>
<b>Sort Order:</b> {viewData.sortOrder}
</p>
<p> <p>
<b>ID:</b> {viewData.departmentId} <b>ID:</b> {viewData.departmentId}
</p> </p>
</div>
<p> <p>
<b>Name:</b> {viewData.name} <b>Name:</b> {viewData.name}
</p> </p>
+128 -60
View File
@@ -7,7 +7,6 @@ import {
getDoctorsApi, getDoctorsApi,
createDoctorApi, createDoctorApi,
updateDoctorApi, updateDoctorApi,
deleteDoctorApi,
getDoctorTimingApi, getDoctorTimingApi,
} from "@/api/doctor"; } from "@/api/doctor";
import { getDepartmentsApi } from "@/api/department"; import { getDepartmentsApi } from "@/api/department";
@@ -31,15 +30,16 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { import {
Loader2, Loader2,
RefreshCw, RefreshCw,
Plus, Plus,
Pencil, Pencil,
Trash,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
User,
} from "lucide-react"; } from "lucide-react";
interface Department { interface Department {
@@ -80,6 +80,8 @@ export default function DoctorPage() {
designation: "", designation: "",
workingStatus: "", workingStatus: "",
qualification: "", qualification: "",
isActive: true,
globalSortOrder: 0,
departments: [], departments: [],
}); });
@@ -130,9 +132,21 @@ export default function DoctorPage() {
const currentItems = filteredDoctors.slice(indexOfFirstItem, indexOfLastItem); const currentItems = filteredDoctors.slice(indexOfFirstItem, indexOfLastItem);
function handleChange(e: any) { function handleChange(e: any) {
setForm({ ...form, [e.target.name]: e.target.value }); const value =
e.target.type === "number" ? Number(e.target.value) : e.target.value;
setForm({ ...form, [e.target.name]: value });
} }
const handleToggleStatus = async (doc: any) => {
try {
const updatedDoc = { ...doc, isActive: !doc.isActive };
await updateDoctorApi(doc.doctorId, updatedDoc);
fetchAll();
} catch (err) {
console.error("Failed to update status", err);
}
};
function handleDepartmentToggle(depId: string) { function handleDepartmentToggle(depId: string) {
const exists = form.departments.find((d: any) => d.departmentId === depId); const exists = form.departments.find((d: any) => d.departmentId === depId);
if (exists) { if (exists) {
@@ -145,11 +159,23 @@ export default function DoctorPage() {
} else { } else {
setForm({ setForm({
...form, ...form,
departments: [...form.departments, { departmentId: depId, timing: {} }], 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) { function handleTimingChange(depId: string, day: string, value: string) {
setForm({ setForm({
...form, ...form,
@@ -170,6 +196,8 @@ export default function DoctorPage() {
designation: "", designation: "",
workingStatus: "", workingStatus: "",
qualification: "", qualification: "",
isActive: true,
globalSortOrder: 0,
departments: [], departments: [],
}); });
setOpenModal(true); setOpenModal(true);
@@ -180,6 +208,7 @@ export default function DoctorPage() {
try { try {
const timingRes = await getDoctorTimingApi(doc.doctorId); const timingRes = await getDoctorTimingApi(doc.doctorId);
const timingData = timingRes?.data?.departments || []; const timingData = timingRes?.data?.departments || [];
setForm({ setForm({
doctorId: doc.doctorId, doctorId: doc.doctorId,
name: doc.name, name: doc.name,
@@ -187,14 +216,17 @@ export default function DoctorPage() {
designation: doc.designation, designation: doc.designation,
workingStatus: doc.workingStatus, workingStatus: doc.workingStatus,
qualification: doc.qualification, qualification: doc.qualification,
isActive: doc.isActive ?? true,
globalSortOrder: doc.globalSortOrder ?? 0,
departments: timingData.map((d: any) => ({ departments: timingData.map((d: any) => ({
departmentId: d.departmentId, departmentId: d.departmentId,
sortOrder: d.deptSortOrder ?? 0,
timing: d.timing || {}, timing: d.timing || {},
})), })),
}); });
setOpenModal(true); setOpenModal(true);
} catch (err) { } catch (err) {
console.error(err); console.error("Error fetching doctor details:", err);
} }
} }
@@ -212,16 +244,6 @@ export default function DoctorPage() {
} }
} }
async function handleDelete(id: string) {
if (!confirm("Delete this doctor?")) return;
try {
await deleteDoctorApi(id);
fetchAll();
} catch (error) {
console.error(error);
}
}
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4"> <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
@@ -278,25 +300,25 @@ export default function DoctorPage() {
<CardContent className="p-0 sm:p-6 space-y-4"> <CardContent className="p-0 sm:p-6 space-y-4">
<div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative"> <div className="rounded-md border overflow-x-auto overflow-y-auto max-h-[650px] relative">
<Table className="w-full min-w-[900px] table-fixed border-separate border-spacing-0"> <Table className="w-full min-w-[1100px] table-fixed border-separate border-spacing-0">
<TableHeader className="sticky top-0 z-20 bg-background shadow-sm"> <TableHeader className="sticky top-0 z-20 bg-background shadow-sm">
<TableRow> <TableRow>
<TableHead className="w-[100px] bg-background text-sm font-bold"> <TableHead className="w-[80px] bg-background text-sm font-bold">
ID Priority{" "}
</TableHead>
<TableHead className="w-[200px] bg-background text-sm font-bold">
Name
</TableHead> </TableHead>
<TableHead className="w-[180px] bg-background text-sm font-bold"> <TableHead className="w-[180px] bg-background text-sm font-bold">
Doctor Info
</TableHead>
<TableHead className="w-[150px] bg-background text-sm font-bold">
Designation Designation
</TableHead> </TableHead>
<TableHead className="w-[180px] bg-background text-sm font-bold">
Qualification
</TableHead>
<TableHead className="w-[220px] bg-background text-sm font-bold"> <TableHead className="w-[220px] bg-background text-sm font-bold">
Departments Departments (Hierarchy)
</TableHead> </TableHead>
<TableHead className="w-[120px] bg-background text-right text-sm font-bold"> <TableHead className="w-[80px] bg-background text-sm font-bold">
Status (Active)
</TableHead>
<TableHead className="w-[80px] bg-background text-right text-sm font-bold">
Actions Actions
</TableHead> </TableHead>
</TableRow> </TableRow>
@@ -321,8 +343,8 @@ export default function DoctorPage() {
) : ( ) : (
currentItems.map((doc) => ( currentItems.map((doc) => (
<TableRow key={doc.doctorId} className="hover:bg-muted/50"> <TableRow key={doc.doctorId} className="hover:bg-muted/50">
<TableCell className="truncate font-mono text-xs"> <TableCell className="font-mono text-sm">
{doc.doctorId} {doc.globalSortOrder}
</TableCell> </TableCell>
<TableCell> <TableCell>
@@ -332,26 +354,20 @@ export default function DoctorPage() {
> >
{doc.name} {doc.name}
</div> </div>
<div className="text-xs text-muted-foreground truncate italic"> <div className="text-xs text-muted-foreground truncate font-mono">
{doc.workingStatus} {doc.doctorId}
</div> </div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div <div
className="truncate text-sm" className="truncate text-sm font-medium"
title={doc.designation} title={doc.designation}
> >
{doc.designation || "-"} {doc.designation || "-"}
</div> </div>
</TableCell> <div className="text-xs italic text-muted-foreground truncate">
{doc.workingStatus}
<TableCell>
<div
className="truncate text-sm"
title={doc.qualification}
>
{doc.qualification || "-"}
</div> </div>
</TableCell> </TableCell>
@@ -361,14 +377,28 @@ export default function DoctorPage() {
<Badge <Badge
key={d.departmentId} key={d.departmentId}
variant="secondary" variant="secondary"
className="text-xs px-2 h-5 leading-none" className="text-xs px-2 h-6 leading-none flex items-center gap-1"
> >
{d.departmentName} {d.departmentName}
<span className="bg-primary text-primary-foreground px-1 rounded-full text-[10px]">
{d.deptSortOrder}
</span>
</Badge> </Badge>
))} ))}
{doc.departments?.length === 0 && ( </div>
<span className="text-muted-foreground">-</span> </TableCell>
)}
<TableCell>
<div className="flex items-center gap-2">
<Switch
checked={doc.isActive}
onCheckedChange={() => handleToggleStatus(doc)}
/>
<Badge
variant={doc.isActive ? "default" : "secondary"}
>
{doc.isActive ? "Active" : "Hidden"}
</Badge>
</div> </div>
</TableCell> </TableCell>
@@ -382,14 +412,6 @@ export default function DoctorPage() {
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
</Button> </Button>
<Button
size="icon"
variant="ghost"
className="h-9 w-9 text-destructive hover:text-destructive hover:bg-destructive/10"
onClick={() => handleDelete(doc.doctorId)}
>
<Trash className="h-4 w-4" />
</Button>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -457,7 +479,7 @@ export default function DoctorPage() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-6"> <div className="space-y-6">
<h3 className="font-bold text-base border-b pb-2"> <h3 className="font-bold text-base border-b pb-2">
Basic Information Profile & Visibility
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
@@ -471,6 +493,39 @@ export default function DoctorPage() {
/> />
</div> </div>
<div className="flex items-center justify-between p-3 border rounded-md bg-muted/30">
<Label
htmlFor="isActive"
className="text-base font-semibold cursor-pointer"
>
Active
</Label>
<Switch
id="isActive"
checked={form.isActive}
onCheckedChange={(val) =>
setForm({ ...form, isActive: val })
}
/>
</div>
<div className="space-y-1">
<Label
htmlFor="globalSortOrder"
className="text-sm font-semibold"
>
Sort Priority (Lower numbers show first)
</Label>
<Input
id="globalSortOrder"
name="globalSortOrder"
type="number"
value={form.globalSortOrder}
onChange={handleChange}
className="text-base"
/>
</div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm font-semibold">Doctor ID</label> <label className="text-sm font-semibold">Doctor ID</label>
<Input <Input
@@ -556,11 +611,11 @@ export default function DoctorPage() {
<div className="space-y-6"> <div className="space-y-6">
<h3 className="font-bold text-base border-b pb-2"> <h3 className="font-bold text-base border-b pb-2">
Working Hours / Timing Department Hierarchy & Timing
</h3> </h3>
{form.departments.length === 0 ? ( {form.departments.length === 0 ? (
<div className="text-base text-muted-foreground italic py-24 text-center border-2 border-dashed rounded-lg"> <div className="text-base text-muted-foreground italic py-24 text-center border-2 border-dashed rounded-lg">
Select a department to configure timing slots Select a department to configure hierarchy and timing
</div> </div>
) : ( ) : (
<div className="space-y-8"> <div className="space-y-8">
@@ -571,15 +626,28 @@ export default function DoctorPage() {
return ( return (
<div <div
key={dep.departmentId} key={dep.departmentId}
className="space-y-4 p-5 border rounded-lg bg-background shadow-sm" className="space-y-4 p-5 border rounded-lg bg-background shadow-sm border-primary/20"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between border-b pb-2">
<p className="font-bold text-base text-primary"> <p className="font-bold text-base text-primary">
{depName} {depName}
</p> </p>
<Badge variant="outline" className="text-xs"> <div className="flex items-center gap-2">
Timing Slot <Label className="text-xs font-bold">
</Badge> Hierarchy Order:
</Label>
<Input
type="number"
className="w-20 h-8 text-sm"
value={dep.sortOrder}
onChange={(e) =>
handleDeptSortChange(
dep.departmentId,
e.target.value,
)
}
/>
</div>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3">
{DAYS.map((day) => ( {DAYS.map((day) => (