Merge pull request '[1.0.4]' (#29) from dev into main

Reviewed-on: #29
This commit was merged in pull request #29.
This commit is contained in:
2026-05-14 03:53:43 +00:00
7 changed files with 237 additions and 86 deletions
@@ -84,8 +84,12 @@ export const createAppointment = async (req, res) => {
<tr>
<td style="padding: 8px 0;"><b>Date:</b></td>
<td style="padding: 8px 0;">
${new Date(date).toLocaleDateString()}
</td>
${new Date(date).toLocaleDateString("en-GB", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</td>
</tr>
</table>
@@ -143,18 +147,51 @@ export const getAppointments = async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const { date, search } = req.query;
const { date, startDate, endDate, search } = req.query;
const where = {};
if (date) {
const hasSingleDate = date && date.trim() !== "";
const hasRange =
(startDate && startDate.trim() !== "") ||
(endDate && endDate.trim() !== "");
if (hasSingleDate) {
const start = new Date(date);
start.setHours(0, 0, 0, 0);
const end = new Date(date);
end.setDate(end.getDate() + 1);
where.date = { gte: start, lt: end };
end.setHours(23, 59, 59, 999);
where.date = {
gte: start,
lte: end,
};
}
if (search) {
if (!hasSingleDate && hasRange) {
const dateFilter = {};
if (startDate && startDate.trim() !== "") {
const start = new Date(startDate);
start.setHours(0, 0, 0, 0);
dateFilter.gte = start;
}
if (endDate && endDate.trim() !== "") {
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);
dateFilter.lte = end;
}
where.date = dateFilter;
}
if (search && search.trim() !== "") {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ mobileNumber: { contains: search } },
@@ -165,24 +202,39 @@ export const getAppointments = async (req, res) => {
const [appointments, total] = await Promise.all([
prisma.appointment.findMany({
where,
include: { doctor: true, department: true },
orderBy: { createdAt: "desc" },
include: {
doctor: true,
department: true,
},
orderBy: {
createdAt: "desc",
},
skip,
take: limit,
}),
prisma.appointment.count({ where }),
prisma.appointment.count({
where,
}),
]);
res.status(200).json({
success: true,
data: appointments,
pagination: { total, page, limit, totalPages: Math.ceil(total / limit) },
pagination: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
});
} catch (error) {
console.error(error);
res
.status(500)
.json({ success: false, message: "Failed to fetch appointments" });
res.status(500).json({
success: false,
message: "Failed to fetch appointments",
});
}
};
+36 -31
View File
@@ -273,42 +273,47 @@ export const updateDoctor = async (req, res) => {
},
});
const oldRelations = await prisma.doctorDepartment.findMany({
where: {doctorId: doctor.id},
});
const hasTimingData = departments?.some(
(dep) => dep.timing && Object.keys(dep.timing).length > 0,
);
for (const rel of oldRelations) {
await prisma.doctorTiming.deleteMany({
where: {doctorDepartmentId: rel.id},
});
}
await prisma.doctorDepartment.deleteMany({
where: {doctorId: doctor.id},
});
for (const dep of departments) {
const targetDept = await prisma.department.findUnique({
where: {departmentId: dep.departmentId},
});
if (!targetDept) continue;
const newDD = await prisma.doctorDepartment.create({
data: {
doctorId: doctor.id,
departmentId: targetDept.id,
sortOrder: dep.sortOrder !== undefined ? Number(dep.sortOrder) : 0,
},
if (departments && Array.isArray(departments) && hasTimingData) {
const oldRelations = await prisma.doctorDepartment.findMany({
where: {doctorId: doctor.id},
});
if (dep.timing) {
const {id, doctorDepartmentId, createdAt, updatedAt, ...cleanTiming} =
dep.timing;
await prisma.doctorTiming.create({
data: {doctorDepartmentId: newDD.id, ...cleanTiming},
for (const rel of oldRelations) {
await prisma.doctorTiming.deleteMany({
where: {doctorDepartmentId: rel.id},
});
}
await prisma.doctorDepartment.deleteMany({
where: {doctorId: doctor.id},
});
for (const dep of departments) {
const targetDept = await prisma.department.findUnique({
where: {departmentId: dep.departmentId},
});
if (!targetDept) continue;
const newDD = await prisma.doctorDepartment.create({
data: {
doctorId: doctor.id,
departmentId: targetDept.id,
sortOrder: dep.sortOrder !== undefined ? Number(dep.sortOrder) : 0,
},
});
if (dep.timing) {
const {id, doctorDepartmentId, createdAt, updatedAt, ...cleanTiming} =
dep.timing;
await prisma.doctorTiming.create({
data: {doctorDepartmentId: newDD.id, ...cleanTiming},
});
}
}
}
res
+4
View File
@@ -4,12 +4,16 @@ export const getAppointmentsApi = async (
page = 1,
limit = 10,
date = "",
startDate = "",
endDate = "",
search = "",
) => {
const params = new URLSearchParams({
page: String(page),
limit: String(limit),
...(date && { date }),
...(startDate && { startDate }),
...(endDate && { endDate }),
...(search && { search }),
});
const res = await apiClient.get(`/appointments/getall?${params}`);
+3 -1
View File
@@ -8,8 +8,10 @@ export interface Doctor {
designation?: string;
workingStatus?: string;
qualification?: string;
isActive: boolean;
globalSortOrder: number;
departments: {
departments?: {
departmentId: string;
timing?: {
monday?: string;
+84 -33
View File
@@ -40,7 +40,8 @@ export default function AppointmentPage() {
const [searchText, setSearchText] = useState("");
const [filterDoctor, setFilterDoctor] = useState("");
const [filterDate, setFilterDate] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [viewOpen, setViewOpen] = useState(false);
const [viewData, setViewData] = useState<any>(null);
@@ -56,6 +57,8 @@ export default function AppointmentPage() {
currentPage,
itemsPerPage,
filterDate,
startDate,
endDate,
searchText,
);
setAppointments(res?.data || []);
@@ -66,7 +69,7 @@ export default function AppointmentPage() {
} finally {
setLoading(false);
}
}, [currentPage, itemsPerPage, filterDate, searchText]);
}, [currentPage, itemsPerPage, filterDate, startDate, endDate, searchText]);
useEffect(() => {
fetchAll();
@@ -116,39 +119,87 @@ export default function AppointmentPage() {
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<h1 className="text-3xl font-bold">Appointments</h1>
<div className="flex flex-wrap gap-3">
<Input
placeholder="Search name / phone..."
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
setCurrentPage(1);
}}
className="w-[220px] text-base"
/>
<div className="flex flex-wrap gap-4 items-end">
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
Search
</label>
<Input
placeholder="Search name / phone..."
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
setCurrentPage(1);
}}
className="w-[220px] text-base"
/>
</div>
<Input
type="date"
value={filterDate}
onChange={(e) => {
setFilterDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
/>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
Date
</label>
<Input
type="date"
value={filterDate}
onChange={(e) => {
setFilterDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
disabled={!!startDate || !!endDate}
/>
</div>
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-primary"
>
<option value={5}>5 / page</option>
<option value={10}>10 / page</option>
<option value={20}>20 / page</option>
</select>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
From
</label>
<Input
type="date"
value={startDate}
onChange={(e) => {
setStartDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
disabled={!!filterDate}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
To
</label>
<Input
type="date"
value={endDate}
onChange={(e) => {
setEndDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
disabled={!!filterDate}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
Rows
</label>
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-primary"
>
<option value={5}>5 / page</option>
<option value={10}>10 / page</option>
<option value={20}>20 / page</option>
</select>
</div>
<Button
variant="outline"
+36 -5
View File
@@ -10,6 +10,7 @@ import Table from "@editorjs/table";
import CodeTool from "@editorjs/code";
import Embed from "@editorjs/embed";
import Delimiter from "@editorjs/delimiter";
import axios from "axios";
import {
createBlogApi,
@@ -23,6 +24,7 @@ import {Input} from "@/components/ui/input";
import {Button} from "@/components/ui/button";
export default function BlogEditorPage() {
const baseURL = import.meta.env.VITE_API_URL;
const {id} = useParams();
const navigate = useNavigate();
@@ -79,12 +81,41 @@ export default function BlogEditorPage() {
config: {
uploader: {
uploadByFile: async (file: File) => {
const res = await uploadImageApi(file);
if (file.size > 5 * 1024 * 1024) {
alert("File is too large (Max 5MB)");
return {success: 0, file: {url: ""}};
}
return {
success: 1,
file: {url: res.file.url},
};
const formData = new FormData();
formData.append("file", file);
formData.append("folderPath", "/blog");
try {
const response = await axios.post(
`${baseURL}/upload`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
},
);
return {
success: 1,
file: {url: response.data.fileUrl},
};
} catch (e: any) {
console.error("EditorJS Image Upload Error:", e);
const errorMessage =
e.response?.data?.error || e.message || "Upload failed";
alert(`Upload Error: ${errorMessage}`);
return {
success: 0,
file: {url: ""},
};
}
},
},
},
+8 -2
View File
@@ -158,8 +158,14 @@ export default function DoctorPage() {
const handleToggleStatus = async (doc: any) => {
try {
const updatedDoc = { ...doc, isActive: !doc.isActive };
await updateDoctorApi(doc.doctorId, updatedDoc);
const newStatus = !doc.isActive;
const payload = {
isActive: newStatus,
};
await updateDoctorApi(doc.doctorId, payload);
fetchAll();
} catch (err) {
console.error("Failed to update status", err);