From 1717507555ebdc5977c431613aab7adb6889b672 Mon Sep 17 00:00:00 2001 From: Kailasdevdas Date: Mon, 11 May 2026 00:04:22 +0530 Subject: [PATCH 1/5] feat: implement sorting and visibility changes --- .../migration.sql | 14 ++ .../migration.sql | 11 ++ backend/prisma/schema.prisma | 10 +- backend/src/controllers/career.controller.js | 26 +++- .../src/controllers/department.controller.js | 33 ++-- backend/src/controllers/doctor.controller.js | 65 +++++--- frontend/src/api/career.ts | 2 +- frontend/src/api/department.ts | 2 +- frontend/src/api/doctor.ts | 2 +- frontend/src/pages/Career.tsx | 44 +++++- frontend/src/pages/Department.tsx | 93 ++++++++--- frontend/src/pages/Doctor.tsx | 145 +++++++++++++----- 12 files changed, 335 insertions(+), 112 deletions(-) create mode 100644 backend/prisma/migrations/20260510161939_sort_inactive_fields/migration.sql create mode 100644 backend/prisma/migrations/20260510171736_sort_order_default1000/migration.sql diff --git a/backend/prisma/migrations/20260510161939_sort_inactive_fields/migration.sql b/backend/prisma/migrations/20260510161939_sort_inactive_fields/migration.sql new file mode 100644 index 0000000..9ced581 --- /dev/null +++ b/backend/prisma/migrations/20260510161939_sort_inactive_fields/migration.sql @@ -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; diff --git a/backend/prisma/migrations/20260510171736_sort_order_default1000/migration.sql b/backend/prisma/migrations/20260510171736_sort_order_default1000/migration.sql new file mode 100644 index 0000000..840956d --- /dev/null +++ b/backend/prisma/migrations/20260510171736_sort_order_default1000/migration.sql @@ -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; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 69d24db..b9988d4 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -25,6 +25,8 @@ model Doctor { designation String? workingStatus String? qualification String? + isActive Boolean @default(true) + globalSortOrder Int @default(1000) departments DoctorDepartment[] appointments Appointment[] @@ -46,6 +48,9 @@ model Department { facilities String? services String? + isActive Boolean @default(true) + sortOrder Int @default(1000) + doctors DoctorDepartment[] appointments Appointment[] @@ -61,7 +66,7 @@ model DoctorDepartment { doctor Doctor @relation(fields: [doctorId], references: [id]) department Department @relation(fields: [departmentId], references: [id]) - + sortOrder Int @default(1000) timing DoctorTiming? createdAt DateTime @default(now()) @@ -111,7 +116,8 @@ model Career { email String? number String? status String @default("new") - + isActive Boolean @default(true) + sortOrder Int @default(1000) candidates Candidate[] createdAt DateTime @default(now()) diff --git a/backend/src/controllers/career.controller.js b/backend/src/controllers/career.controller.js index 2b365de..7740132 100644 --- a/backend/src/controllers/career.controller.js +++ b/backend/src/controllers/career.controller.js @@ -4,8 +4,11 @@ import prisma from "../prisma/client.js"; export const getAllCareers = async (req, res) => { try { + const { admin } = req.query; + const careers = await prisma.career.findMany({ - orderBy: {createdAt: "desc"}, + where: admin === "true" ? {} : { isActive: true }, + orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }], }); const response = careers.map((c) => ({ @@ -17,6 +20,8 @@ export const getAllCareers = async (req, res) => { email: c.email, number: c.number, status: c.status, + isActive: c.isActive, + sortOrder: c.sortOrder, })); return res.status(200).json({ @@ -44,6 +49,8 @@ export const createCareer = async (req, res) => { email, number, status, + isActive, + sortOrder, } = req.body; if (!post || !designation) { @@ -62,6 +69,8 @@ export const createCareer = async (req, res) => { email, number, status, + isActive: isActive !== undefined ? isActive : true, + sortOrder: sortOrder !== undefined ? Number(sortOrder) : 0, }, }); @@ -83,11 +92,16 @@ export const createCareer = async (req, res) => { export const updateCareer = async (req, res) => { 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({ - where: {id: Number(id)}, - data: req.body, + where: { id: Number(id) }, + data: updateData, }); return res.status(200).json({ @@ -108,10 +122,10 @@ export const updateCareer = async (req, res) => { export const deleteCareer = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; await prisma.career.delete({ - where: {id: Number(id)}, + where: { id: Number(id) }, }); return res.status(200).json({ diff --git a/backend/src/controllers/department.controller.js b/backend/src/controllers/department.controller.js index 4ff80c2..a14d26f 100644 --- a/backend/src/controllers/department.controller.js +++ b/backend/src/controllers/department.controller.js @@ -2,8 +2,11 @@ import prisma from "../prisma/client.js"; export const getAllDepartments = async (req, res) => { try { + const {admin} = req.query; + const departments = await prisma.department.findMany({ - orderBy: {name: "asc"}, + where: admin === "true" ? {} : {isActive: true}, + orderBy: [{sortOrder: "asc"}, {name: "asc"}], }); const response = departments.map((dep) => ({ @@ -15,6 +18,8 @@ export const getAllDepartments = async (req, res) => { para3: dep.para3 ?? "", facilities: dep.facilities ?? "", services: dep.services ?? "", + isActive: dep.isActive, + sortOrder: dep.sortOrder, })); return res.status(200).json({ @@ -44,13 +49,14 @@ export const getDepartmentByName = async (req, res) => { const department = await prisma.department.findFirst({ where: { name: name, + isActive: true, }, }); if (!department) { return res.status(404).json({ 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 ?? "", facilities: department.facilities ?? "", services: department.services ?? "", + isActive: department.isActive, + sortOrder: department.sortOrder, }; return res.status(200).json({ @@ -89,6 +97,8 @@ export async function createDepartment(req, res) { para3, facilities, services, + isActive, + sortOrder, } = req.body; if (!departmentId || !name) { @@ -107,6 +117,8 @@ export async function createDepartment(req, res) { para3, facilities, 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") { return res.status(409).json({error: "Department already exists"}); } - + console.error(error); 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) => { try { 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({ where: {departmentId}, - data: { - name, - image, - para1, - para2, - para3, - facilities, - services, - }, + data: updateData, }); return res.status(200).json({ diff --git a/backend/src/controllers/doctor.controller.js b/backend/src/controllers/doctor.controller.js index 34007ed..52e619d 100644 --- a/backend/src/controllers/doctor.controller.js +++ b/backend/src/controllers/doctor.controller.js @@ -4,7 +4,10 @@ import prisma from "../prisma/client.js"; export const getAllDoctors = async (req, res) => { try { + const {admin} = req.query; + const doctors = await prisma.doctor.findMany({ + where: admin === "true" ? {} : {isActive: true}, include: { departments: { 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) => ({ @@ -24,10 +27,10 @@ export const getAllDoctors = async (req, res) => { designation: doc.designation, workingStatus: doc.workingStatus, qualification: doc.qualification, - + isActive: doc.isActive, + globalSortOrder: doc.globalSortOrder, departments: doc.departments.map((d) => { const t = d.timing || {}; - const timingArray = [ t.monday && `Monday ${t.monday}`, t.tuesday && `Tuesday ${t.tuesday}`, @@ -43,6 +46,7 @@ export const getAllDoctors = async (req, res) => { departmentId: d.department.departmentId, departmentName: d.department.name, timing: timingArray.join(" & "), + deptSortOrder: d.sortOrder, }; }), })); @@ -135,17 +139,23 @@ export const getDoctorsByDepartmentId = async (req, res) => { }); } - const doctors = await prisma.doctorDepartment.findMany({ - where: {departmentId: department.id}, + const doctorsInDept = await prisma.doctorDepartment.findMany({ + where: { + departmentId: department.id, + doctor: {isActive: true}, + }, include: { doctor: true, }, + orderBy: {sortOrder: "asc"}, }); - const result = doctors.map((d) => ({ + const result = doctorsInDept.map((d) => ({ GG_ID: d.doctor.doctorId, Name: d.doctor.name, image: d.doctor.image ?? "", + designation: d.doctor.designation, + hierarchyOrder: d.sortOrder, })); res.status(200).json({ @@ -171,6 +181,8 @@ export const createDoctor = async (req, res) => { designation, workingStatus, qualification, + isActive, + globalSortOrder, departments, } = req.body; @@ -182,6 +194,9 @@ export const createDoctor = async (req, res) => { designation, workingStatus, qualification, + isActive: isActive !== undefined ? isActive : true, + globalSortOrder: + globalSortOrder !== undefined ? Number(globalSortOrder) : 0, }, }); @@ -196,6 +211,7 @@ export const createDoctor = async (req, res) => { data: { doctorId: doctor.id, departmentId: department.id, + sortOrder: dep.sortOrder !== undefined ? Number(dep.sortOrder) : 0, }, }); @@ -232,22 +248,29 @@ export const updateDoctor = async (req, res) => { image, workingStatus, qualification, + isActive, + globalSortOrder, departments, } = req.body; - const doctor = await prisma.doctor.findUnique({ - where: {doctorId}, - }); - - if (!doctor) { + const doctor = await prisma.doctor.findUnique({where: {doctorId}}); + if (!doctor) return res .status(404) .json({success: false, message: "Doctor not found"}); - } await prisma.doctor.update({ 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({ @@ -265,16 +288,16 @@ export const updateDoctor = async (req, res) => { }); for (const dep of departments) { - const department = await prisma.department.findUnique({ + const targetDept = await prisma.department.findUnique({ where: {departmentId: dep.departmentId}, }); + if (!targetDept) continue; - if (!department) continue; - - const doctorDepartment = await prisma.doctorDepartment.create({ + const newDD = await prisma.doctorDepartment.create({ data: { 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; await prisma.doctorTiming.create({ - data: { - doctorDepartmentId: doctorDepartment.id, - ...cleanTiming, - }, + data: {doctorDepartmentId: newDD.id, ...cleanTiming}, }); } } @@ -421,6 +441,7 @@ export const getDoctorTimingById = async (req, res) => { departments: doctor.departments.map((d) => ({ departmentId: d.department.departmentId, departmentName: d.department.name, + deptSortOrder: d.sortOrder, timing: d.timing || {}, })), }; diff --git a/frontend/src/api/career.ts b/frontend/src/api/career.ts index 0ded9f0..cf1ec36 100644 --- a/frontend/src/api/career.ts +++ b/frontend/src/api/career.ts @@ -1,7 +1,7 @@ import apiClient from "@/api/client"; export const getCareersApi = async () => { - const res = await apiClient.get("/careers/getAll"); + const res = await apiClient.get("/careers/getAll?admin=true"); return res.data; }; diff --git a/frontend/src/api/department.ts b/frontend/src/api/department.ts index 7de2e4c..716e8c5 100644 --- a/frontend/src/api/department.ts +++ b/frontend/src/api/department.ts @@ -12,7 +12,7 @@ export interface Department { } export const getDepartmentsApi = async () => { - const res = await apiClient.get("/departments/getAll"); + const res = await apiClient.get("/departments/getAll?admin=true"); return res.data; }; diff --git a/frontend/src/api/doctor.ts b/frontend/src/api/doctor.ts index 82d2065..b2c652a 100644 --- a/frontend/src/api/doctor.ts +++ b/frontend/src/api/doctor.ts @@ -24,7 +24,7 @@ export interface Doctor { } export const getDoctorsApi = async () => { - const res = await apiClient.get("/doctors/getAll"); + const res = await apiClient.get("/doctors/getAll?admin=true"); return res.data; }; diff --git a/frontend/src/pages/Career.tsx b/frontend/src/pages/Career.tsx index ec60512..2656530 100644 --- a/frontend/src/pages/Career.tsx +++ b/frontend/src/pages/Career.tsx @@ -24,6 +24,8 @@ import { 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, @@ -55,6 +57,8 @@ export default function CareerPage() { email: "", number: "", status: "new", + isActive: true, + sortOrder: 0, }); const fetchAll = useCallback(async () => { @@ -102,6 +106,8 @@ export default function CareerPage() { email: "", number: "", status: "new", + isActive: true, + sortOrder: 0, }); setOpenModal(true); } @@ -116,6 +122,8 @@ export default function CareerPage() { email: item.email || "", number: item.number || "", status: item.status || "new", + isActive: item.isActive ?? true, + sortOrder: item.sortOrder ?? 0, }); setOpenModal(true); } @@ -181,7 +189,7 @@ export default function CareerPage() { - ID + Priority Post & Designation @@ -196,7 +204,7 @@ export default function CareerPage() { Contact Info - Status + Visibility Actions @@ -224,7 +232,7 @@ export default function CareerPage() { currentItems.map((item) => ( - {item.id} + {item.sortOrder}
@@ -250,12 +258,10 @@ export default function CareerPage() { - {item.status} + {item.isActive ? "Active" : "Hidden"} @@ -393,6 +399,30 @@ export default function CareerPage() { onChange={handleChange} className="text-base" /> +
+ + setForm({ ...form, isActive: val })} + /> +
+
+ + +
diff --git a/frontend/src/pages/Department.tsx b/frontend/src/pages/Department.tsx index 354c9e0..51eef61 100644 --- a/frontend/src/pages/Department.tsx +++ b/frontend/src/pages/Department.tsx @@ -31,6 +31,9 @@ import { import {Input} from "@/components/ui/input"; 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 { Loader2, @@ -52,6 +55,8 @@ interface Department { para3: string; facilities: string; services: string; + isActive: boolean; + sortOrder: number; } export default function DepartmentPage() { @@ -79,6 +84,8 @@ export default function DepartmentPage() { para3: "", facilities: "", services: "", + isActive: true, + sortOrder: 0, }); const fetchDepartments = useCallback(async () => { @@ -122,7 +129,9 @@ export default function DepartmentPage() { ); 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) { @@ -141,13 +150,19 @@ export default function DepartmentPage() { para3: "", facilities: "", services: "", + isActive: true, + sortOrder: 0, }); setOpenModal(true); } function openEdit(dep: Department) { setEditing(dep); - setForm(dep); + setForm({ + ...dep, + isActive: dep.isActive ?? true, + sortOrder: dep.sortOrder ?? 0, + }); setOpenModal(true); } @@ -226,24 +241,24 @@ export default function DepartmentPage() {
- +
- - ID + + Priority - + Name + + Status + Para 1 - + Facilities - - Services - Actions @@ -272,8 +287,8 @@ export default function DepartmentPage() { key={dep.departmentId} className="hover:bg-muted/50" > - - {dep.departmentId} + + {dep.sortOrder} @@ -283,6 +298,15 @@ export default function DepartmentPage() { > {dep.name} +
+ {dep.departmentId} +
+
+ + + + {dep.isActive ? "Active" : "Hidden"} + @@ -297,12 +321,6 @@ export default function DepartmentPage() { - -
- {truncate(dep.services)} -
-
-
@@ -470,9 +513,17 @@ export default function DepartmentPage() { {viewData && (
-

- ID: {viewData.departmentId} -

+
+ + {viewData.isActive ? "ACTIVE" : "HIDDEN"} + +

+ Sort Order: {viewData.sortOrder} +

+

+ ID: {viewData.departmentId} +

+

Name: {viewData.name}

diff --git a/frontend/src/pages/Doctor.tsx b/frontend/src/pages/Doctor.tsx index 35556fb..87ae4a2 100644 --- a/frontend/src/pages/Doctor.tsx +++ b/frontend/src/pages/Doctor.tsx @@ -31,6 +31,9 @@ import { } from "@/components/ui/dialog"; 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, @@ -39,7 +42,6 @@ import { Trash, ChevronLeft, ChevronRight, - User, } from "lucide-react"; interface Department { @@ -80,6 +82,8 @@ export default function DoctorPage() { designation: "", workingStatus: "", qualification: "", + isActive: true, + globalSortOrder: 0, departments: [], }); @@ -130,7 +134,9 @@ export default function DoctorPage() { const currentItems = filteredDoctors.slice(indexOfFirstItem, indexOfLastItem); 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 handleDepartmentToggle(depId: string) { @@ -145,11 +151,23 @@ export default function DoctorPage() { } else { setForm({ ...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) { setForm({ ...form, @@ -170,6 +188,8 @@ export default function DoctorPage() { designation: "", workingStatus: "", qualification: "", + isActive: true, + globalSortOrder: 0, departments: [], }); setOpenModal(true); @@ -180,6 +200,7 @@ export default function DoctorPage() { try { const timingRes = await getDoctorTimingApi(doc.doctorId); const timingData = timingRes?.data?.departments || []; + setForm({ doctorId: doc.doctorId, name: doc.name, @@ -187,14 +208,17 @@ export default function DoctorPage() { designation: doc.designation, workingStatus: doc.workingStatus, qualification: doc.qualification, + isActive: doc.isActive ?? true, + globalSortOrder: doc.globalSortOrder ?? 0, departments: timingData.map((d: any) => ({ departmentId: d.departmentId, + sortOrder: d.deptSortOrder ?? 0, timing: d.timing || {}, })), }); setOpenModal(true); } catch (err) { - console.error(err); + console.error("Error fetching doctor details:", err); } } @@ -278,23 +302,23 @@ export default function DoctorPage() {
-
+
- - ID + + Priority{" "} - Name + Doctor Info - + + Status + + Designation - - Qualification - - Departments + Departments (Hierarchy) Actions @@ -321,8 +345,8 @@ export default function DoctorPage() { ) : ( currentItems.map((doc) => ( - - {doc.doctorId} + + {doc.globalSortOrder} @@ -332,26 +356,26 @@ export default function DoctorPage() { > {doc.name} -
- {doc.workingStatus} +
+ {doc.doctorId}
+ + + {doc.isActive ? "Active" : "Hidden"} + + +
{doc.designation || "-"}
-
- - -
- {doc.qualification || "-"} +
+ {doc.workingStatus}
@@ -361,14 +385,14 @@ export default function DoctorPage() { {d.departmentName} + + {d.deptSortOrder} + ))} - {doc.departments?.length === 0 && ( - - - )}
@@ -457,7 +481,7 @@ export default function DoctorPage() {

- Basic Information + Profile & Visibility

@@ -471,6 +495,36 @@ export default function DoctorPage() { />
+
+ + + setForm({ ...form, isActive: val }) + } + /> +
+ +
+ + +
+

- Working Hours / Timing + Department Hierarchy & Timing

{form.departments.length === 0 ? (
- Select a department to configure timing slots + Select a department to configure hierarchy and timing
) : (
@@ -568,18 +622,33 @@ export default function DoctorPage() { const depName = departments.find( (d) => d.departmentId === dep.departmentId, )?.name; + console.log("doctor department sortOrder", dep.sortOrder); + console.log("dep", dep); return (
-
+

{depName}

- - Timing Slot - +
+ + + handleDeptSortChange( + dep.departmentId, + e.target.value, + ) + } + /> +
{DAYS.map((day) => ( -- 2.43.0 From 2c6da93dfb61e2d3ac846aa469e66393f3982582 Mon Sep 17 00:00:00 2001 From: Kailasdevdas Date: Mon, 11 May 2026 10:51:34 +0530 Subject: [PATCH 2/5] feat: add toast --- frontend/package-lock.json | 28 +++++++++++++++++++- frontend/package.json | 1 + frontend/src/App.tsx | 7 +++-- frontend/src/api/career.ts | 44 ++++++++++++++++++++++++++++--- frontend/src/api/department.ts | 48 +++++++++++++++++++++++++++++----- frontend/src/api/doctor.ts | 40 +++++++++++++++++++++++----- 6 files changed, 150 insertions(+), 18 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46ed472..e46e2ab 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,6 +28,7 @@ "radix-ui": "^1.4.3", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hot-toast": "^2.6.0", "react-router-dom": "^7.13.1", "shadcn": "^4.0.5", "tailwind-merge": "^3.5.0", @@ -5187,7 +5188,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/data-uri-to-buffer": { @@ -6352,6 +6352,15 @@ "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -8136,6 +8145,23 @@ "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": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a97269e..7fd66ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "radix-ui": "^1.4.3", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-hot-toast": "^2.6.0", "react-router-dom": "^7.13.1", "shadcn": "^4.0.5", "tailwind-merge": "^3.5.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a57396c..54fcdf9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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"; @@ -8,7 +9,7 @@ import DashboardLayout from "./layouts/DashboardLayout"; import ProtectedRoute from "./auth/ProtectedRoute"; import PublicRoute from "./auth/PublicRoute"; -import { AuthProvider } from "./context/AuthContext"; +import {AuthProvider} from "./context/AuthContext"; import Department from "./pages/Department"; import Doctor from "./pages/Doctor"; import Blog from "./pages/Blog"; @@ -26,6 +27,8 @@ import ImportData from "./pages/ImportData"; export default function App() { return ( + + }> diff --git a/frontend/src/api/career.ts b/frontend/src/api/career.ts index cf1ec36..2f35a22 100644 --- a/frontend/src/api/career.ts +++ b/frontend/src/api/career.ts @@ -1,11 +1,49 @@ import apiClient from "@/api/client"; +import toast from "react-hot-toast"; export const getCareersApi = async () => { const res = await apiClient.get("/careers/getAll?admin=true"); return res.data; }; -export const deleteCareerApi = async (id: number) => { - const res = await apiClient.delete(`/careers/${id}`); - 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) => { + try { + const res = await apiClient.delete(`/careers/${id}`); + + toast.success("Career deleted successfully"); + + return res.data; + } catch (error: any) { + toast.error(error?.response?.data?.message || "Failed to delete career"); + + throw error; + } }; diff --git a/frontend/src/api/department.ts b/frontend/src/api/department.ts index 716e8c5..406d33e 100644 --- a/frontend/src/api/department.ts +++ b/frontend/src/api/department.ts @@ -1,4 +1,5 @@ import apiClient from "@/api/client"; +import toast from "react-hot-toast"; export interface Department { departmentId: string; @@ -9,6 +10,8 @@ export interface Department { para3: string; facilities: string; services: string; + isActive?: boolean; + sortOrder?: number; } export const getDepartmentsApi = async () => { @@ -25,8 +28,19 @@ export const createDepartmentApi = async (data: { facilities?: string; services?: string; }) => { - const res = await apiClient.post("/departments", data); - return res.data; + try { + const res = await apiClient.post("/departments", data); + + toast.success("Department created successfully"); + + return res.data; + } catch (error: any) { + toast.error( + error?.response?.data?.message || "Failed to create department", + ); + + throw error; + } }; export const updateDepartmentApi = async ( @@ -40,11 +54,33 @@ export const updateDepartmentApi = async ( services?: string; }, ) => { - const res = await apiClient.put(`/departments/${departmentId}`, data); - return res.data; + try { + const res = await apiClient.put(`/departments/${departmentId}`, data); + + toast.success("Department updated successfully"); + + return res.data; + } catch (error: any) { + toast.error( + error?.response?.data?.message || "Failed to update department", + ); + + throw error; + } }; export const deleteDepartmentApi = async (departmentId: string) => { - const res = await apiClient.delete(`/departments/${departmentId}`); - return res.data; + try { + const res = await apiClient.delete(`/departments/${departmentId}`); + + toast.success("Department deleted successfully"); + + return res.data; + } catch (error: any) { + toast.error( + error?.response?.data?.message || "Failed to delete department", + ); + + throw error; + } }; diff --git a/frontend/src/api/doctor.ts b/frontend/src/api/doctor.ts index b2c652a..7e1b1df 100644 --- a/frontend/src/api/doctor.ts +++ b/frontend/src/api/doctor.ts @@ -1,4 +1,5 @@ import apiClient from "@/api/client"; +import toast from "react-hot-toast"; export interface Doctor { doctorId: string; @@ -34,21 +35,48 @@ export const getDoctorByIdApi = async (doctorId: string) => { }; export const createDoctorApi = async (data: Doctor) => { - const res = await apiClient.post("/doctors", data); - return res.data; + try { + const res = await apiClient.post("/doctors", data); + + toast.success("Doctor created successfully"); + + return res.data; + } catch (error: any) { + toast.error(error?.response?.data?.message || "Failed to create doctor"); + + throw error; + } }; export const updateDoctorApi = async ( doctorId: string, data: Partial, ) => { - const res = await apiClient.patch(`/doctors/${doctorId}`, data); - return res.data; + try { + const res = await apiClient.patch(`/doctors/${doctorId}`, data); + + toast.success("Doctor updated successfully"); + + return res.data; + } catch (error: any) { + toast.error(error?.response?.data?.message || "Failed to update doctor"); + + throw error; + } }; export const deleteDoctorApi = async (doctorId: string) => { - const res = await apiClient.delete(`/doctors/${doctorId}`); - return res.data; + try { + const res = await apiClient.delete(`/doctors/${doctorId}`); + + toast.success("Doctor deleted successfully"); + + return res.data; + } catch (error: any) { + toast.error(error?.response?.data?.message || "Failed to delete doctor"); + + throw error; + } }; export const getDoctorTimingApi = async (doctorId: string) => { -- 2.43.0 From 4808e99ae558a9b952bc894ad041398d76b0ec3a Mon Sep 17 00:00:00 2001 From: Kailasdevdas Date: Mon, 11 May 2026 10:52:30 +0530 Subject: [PATCH 3/5] feat: remove delete action and add status toggle to tables --- frontend/src/pages/Career.tsx | 77 ++++++++++++--------------- frontend/src/pages/Department.tsx | 88 +++++++++++-------------------- frontend/src/pages/Doctor.tsx | 62 ++++++++++------------ 3 files changed, 95 insertions(+), 132 deletions(-) diff --git a/frontend/src/pages/Career.tsx b/frontend/src/pages/Career.tsx index 2656530..c42656f 100644 --- a/frontend/src/pages/Career.tsx +++ b/frontend/src/pages/Career.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useCallback } from "react"; -import { getCareersApi, deleteCareerApi } from "@/api/career"; -import apiClient from "@/api/client"; +import { getCareersApi, updateCareerApi, createCareerApi } from "@/api/career"; import { Table, @@ -31,7 +30,6 @@ import { Loader2, Plus, Pencil, - Trash, RefreshCw, ChevronLeft, ChevronRight, @@ -96,6 +94,18 @@ export default function CareerPage() { 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() { setEditing(null); setForm({ @@ -131,10 +141,11 @@ export default function CareerPage() { async function handleSubmit() { try { if (editing) { - await apiClient.patch(`/careers/${editing.id}`, form); + await updateCareerApi(editing.id, form); } else { - await apiClient.post("/careers", form); + await createCareerApi(form); } + setOpenModal(false); fetchAll(); } catch (err) { @@ -142,12 +153,6 @@ export default function CareerPage() { } } - async function handleDelete(id: number) { - if (!confirm("Delete career?")) return; - await deleteCareerApi(id); - fetchAll(); - } - return (
@@ -185,13 +190,13 @@ export default function CareerPage() {
-
+
- + Priority - + Post & Designation @@ -200,13 +205,10 @@ export default function CareerPage() { Experience - - Contact Info + + Status (Active) - - Visibility - - + Actions @@ -215,14 +217,14 @@ export default function CareerPage() { {loading ? ( - + ) : currentItems.length === 0 ? ( No careers found @@ -251,19 +253,19 @@ export default function CareerPage() { {item.experienceNeed} -
{item.email}
-
- {item.number} +
+ handleToggleStatus(item)} + /> + + {item.isActive ? "Active" : "Hidden"} +
- - - {item.isActive ? "Active" : "Hidden"} - -
@@ -275,15 +277,6 @@ export default function CareerPage() { > - -
diff --git a/frontend/src/pages/Department.tsx b/frontend/src/pages/Department.tsx index 51eef61..d2b415a 100644 --- a/frontend/src/pages/Department.tsx +++ b/frontend/src/pages/Department.tsx @@ -6,7 +6,6 @@ import { getDepartmentsApi, createDepartmentApi, updateDepartmentApi, - deleteDepartmentApi, } from "@/api/department"; import { @@ -40,7 +39,6 @@ import { RefreshCw, Plus, Pencil, - Trash, Eye, ChevronLeft, ChevronRight, @@ -134,10 +132,18 @@ export default function DepartmentPage() { setForm({...form, [e.target.name]: value}); } - function truncate(text: string, limit = 60) { - if (!text) return "-"; - return text.length > limit ? text.substring(0, limit) + "..." : text; - } + const handleToggleStatus = async (dep: Department) => { + try { + 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() { setEditing(null); @@ -175,7 +181,7 @@ export default function DepartmentPage() { try { if (editing) { const {departmentId, ...updateData} = form; - await updateDepartmentApi(editing.departmentId, updateData); + await updateDepartmentApi(editing.departmentId, form as any); } else { await createDepartmentApi(form); } @@ -187,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 (
@@ -241,25 +236,19 @@ export default function DepartmentPage() {
-
+
- + Priority - + Name - - Status + + Status (Active) - - Para 1 - - - Facilities - - + Actions @@ -268,14 +257,14 @@ export default function DepartmentPage() { {loading ? ( - + ) : currentItems.length === 0 ? ( No departments found @@ -302,22 +291,17 @@ export default function DepartmentPage() { {dep.departmentId} - - - {dep.isActive ? "Active" : "Hidden"} - - - - -
- {truncate(dep.para1)} -
-
- - -
- {truncate(dep.facilities)} +
+ handleToggleStatus(dep)} + /> + + {dep.isActive ? "Active" : "Hidden"} +
@@ -340,15 +324,6 @@ export default function DepartmentPage() { > - -
@@ -469,7 +444,6 @@ export default function DepartmentPage() { placeholder="Services" /> - {/* Status and Priority at bottom to keep original UI flow */}