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:
@@ -84,7 +84,11 @@ export const createAppointment = async (req, res) => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 8px 0;"><b>Date:</b></td>
|
<td style="padding: 8px 0;"><b>Date:</b></td>
|
||||||
<td style="padding: 8px 0;">
|
<td style="padding: 8px 0;">
|
||||||
${new Date(date).toLocaleDateString()}
|
${new Date(date).toLocaleDateString("en-GB", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -143,18 +147,51 @@ export const getAppointments = async (req, res) => {
|
|||||||
const page = parseInt(req.query.page) || 1;
|
const page = parseInt(req.query.page) || 1;
|
||||||
const limit = parseInt(req.query.limit) || 10;
|
const limit = parseInt(req.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
const { date, search } = req.query;
|
|
||||||
|
const { date, startDate, endDate, search } = req.query;
|
||||||
|
|
||||||
const where = {};
|
const where = {};
|
||||||
|
|
||||||
if (date) {
|
const hasSingleDate = date && date.trim() !== "";
|
||||||
|
|
||||||
|
const hasRange =
|
||||||
|
(startDate && startDate.trim() !== "") ||
|
||||||
|
(endDate && endDate.trim() !== "");
|
||||||
|
|
||||||
|
if (hasSingleDate) {
|
||||||
const start = new Date(date);
|
const start = new Date(date);
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const end = new Date(date);
|
const end = new Date(date);
|
||||||
end.setDate(end.getDate() + 1);
|
end.setHours(23, 59, 59, 999);
|
||||||
where.date = { gte: start, lt: end };
|
|
||||||
|
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 = [
|
where.OR = [
|
||||||
{ name: { contains: search, mode: "insensitive" } },
|
{ name: { contains: search, mode: "insensitive" } },
|
||||||
{ mobileNumber: { contains: search } },
|
{ mobileNumber: { contains: search } },
|
||||||
@@ -165,24 +202,39 @@ export const getAppointments = async (req, res) => {
|
|||||||
const [appointments, total] = await Promise.all([
|
const [appointments, total] = await Promise.all([
|
||||||
prisma.appointment.findMany({
|
prisma.appointment.findMany({
|
||||||
where,
|
where,
|
||||||
include: { doctor: true, department: true },
|
include: {
|
||||||
orderBy: { createdAt: "desc" },
|
doctor: true,
|
||||||
|
department: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
}),
|
}),
|
||||||
prisma.appointment.count({ where }),
|
|
||||||
|
prisma.appointment.count({
|
||||||
|
where,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: appointments,
|
data: appointments,
|
||||||
pagination: { total, page, limit, totalPages: Math.ceil(total / limit) },
|
pagination: {
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
res
|
|
||||||
.status(500)
|
res.status(500).json({
|
||||||
.json({ success: false, message: "Failed to fetch appointments" });
|
success: false,
|
||||||
|
message: "Failed to fetch appointments",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -273,6 +273,11 @@ export const updateDoctor = async (req, res) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasTimingData = departments?.some(
|
||||||
|
(dep) => dep.timing && Object.keys(dep.timing).length > 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (departments && Array.isArray(departments) && hasTimingData) {
|
||||||
const oldRelations = await prisma.doctorDepartment.findMany({
|
const oldRelations = await prisma.doctorDepartment.findMany({
|
||||||
where: {doctorId: doctor.id},
|
where: {doctorId: doctor.id},
|
||||||
});
|
});
|
||||||
@@ -304,12 +309,12 @@ export const updateDoctor = async (req, res) => {
|
|||||||
if (dep.timing) {
|
if (dep.timing) {
|
||||||
const {id, doctorDepartmentId, createdAt, updatedAt, ...cleanTiming} =
|
const {id, doctorDepartmentId, createdAt, updatedAt, ...cleanTiming} =
|
||||||
dep.timing;
|
dep.timing;
|
||||||
|
|
||||||
await prisma.doctorTiming.create({
|
await prisma.doctorTiming.create({
|
||||||
data: {doctorDepartmentId: newDD.id, ...cleanTiming},
|
data: {doctorDepartmentId: newDD.id, ...cleanTiming},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
.status(200)
|
.status(200)
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ export const getAppointmentsApi = async (
|
|||||||
page = 1,
|
page = 1,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
date = "",
|
date = "",
|
||||||
|
startDate = "",
|
||||||
|
endDate = "",
|
||||||
search = "",
|
search = "",
|
||||||
) => {
|
) => {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
page: String(page),
|
page: String(page),
|
||||||
limit: String(limit),
|
limit: String(limit),
|
||||||
...(date && { date }),
|
...(date && { date }),
|
||||||
|
...(startDate && { startDate }),
|
||||||
|
...(endDate && { endDate }),
|
||||||
...(search && { search }),
|
...(search && { search }),
|
||||||
});
|
});
|
||||||
const res = await apiClient.get(`/appointments/getall?${params}`);
|
const res = await apiClient.get(`/appointments/getall?${params}`);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ export interface Doctor {
|
|||||||
designation?: string;
|
designation?: string;
|
||||||
workingStatus?: string;
|
workingStatus?: string;
|
||||||
qualification?: string;
|
qualification?: string;
|
||||||
|
isActive: boolean;
|
||||||
|
globalSortOrder: number;
|
||||||
|
|
||||||
departments: {
|
departments?: {
|
||||||
departmentId: string;
|
departmentId: string;
|
||||||
timing?: {
|
timing?: {
|
||||||
monday?: string;
|
monday?: string;
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export default function AppointmentPage() {
|
|||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [filterDoctor, setFilterDoctor] = useState("");
|
const [filterDoctor, setFilterDoctor] = useState("");
|
||||||
const [filterDate, setFilterDate] = useState("");
|
const [filterDate, setFilterDate] = useState("");
|
||||||
|
const [startDate, setStartDate] = useState("");
|
||||||
|
const [endDate, setEndDate] = useState("");
|
||||||
const [viewOpen, setViewOpen] = useState(false);
|
const [viewOpen, setViewOpen] = useState(false);
|
||||||
const [viewData, setViewData] = useState<any>(null);
|
const [viewData, setViewData] = useState<any>(null);
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ export default function AppointmentPage() {
|
|||||||
currentPage,
|
currentPage,
|
||||||
itemsPerPage,
|
itemsPerPage,
|
||||||
filterDate,
|
filterDate,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
searchText,
|
searchText,
|
||||||
);
|
);
|
||||||
setAppointments(res?.data || []);
|
setAppointments(res?.data || []);
|
||||||
@@ -66,7 +69,7 @@ export default function AppointmentPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [currentPage, itemsPerPage, filterDate, searchText]);
|
}, [currentPage, itemsPerPage, filterDate, startDate, endDate, searchText]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAll();
|
fetchAll();
|
||||||
@@ -116,7 +119,11 @@ export default function AppointmentPage() {
|
|||||||
<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">
|
||||||
<h1 className="text-3xl font-bold">Appointments</h1>
|
<h1 className="text-3xl font-bold">Appointments</h1>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-3">
|
<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
|
<Input
|
||||||
placeholder="Search name / phone..."
|
placeholder="Search name / phone..."
|
||||||
value={searchText}
|
value={searchText}
|
||||||
@@ -126,7 +133,12 @@ export default function AppointmentPage() {
|
|||||||
}}
|
}}
|
||||||
className="w-[220px] text-base"
|
className="w-[220px] text-base"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
|
Date
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={filterDate}
|
value={filterDate}
|
||||||
@@ -135,8 +147,46 @@ export default function AppointmentPage() {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
className="w-[160px] text-base"
|
className="w-[160px] text-base"
|
||||||
|
disabled={!!startDate || !!endDate}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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
|
<select
|
||||||
value={itemsPerPage}
|
value={itemsPerPage}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -149,6 +199,7 @@ export default function AppointmentPage() {
|
|||||||
<option value={10}>10 / page</option>
|
<option value={10}>10 / page</option>
|
||||||
<option value={20}>20 / page</option>
|
<option value={20}>20 / page</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Table from "@editorjs/table";
|
|||||||
import CodeTool from "@editorjs/code";
|
import CodeTool from "@editorjs/code";
|
||||||
import Embed from "@editorjs/embed";
|
import Embed from "@editorjs/embed";
|
||||||
import Delimiter from "@editorjs/delimiter";
|
import Delimiter from "@editorjs/delimiter";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createBlogApi,
|
createBlogApi,
|
||||||
@@ -23,6 +24,7 @@ import {Input} from "@/components/ui/input";
|
|||||||
import {Button} from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
|
|
||||||
export default function BlogEditorPage() {
|
export default function BlogEditorPage() {
|
||||||
|
const baseURL = import.meta.env.VITE_API_URL;
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -79,12 +81,41 @@ export default function BlogEditorPage() {
|
|||||||
config: {
|
config: {
|
||||||
uploader: {
|
uploader: {
|
||||||
uploadByFile: async (file: File) => {
|
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: ""}};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
success: 1,
|
success: 1,
|
||||||
file: {url: res.file.url},
|
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: ""},
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -158,8 +158,14 @@ export default function DoctorPage() {
|
|||||||
|
|
||||||
const handleToggleStatus = async (doc: any) => {
|
const handleToggleStatus = async (doc: any) => {
|
||||||
try {
|
try {
|
||||||
const updatedDoc = { ...doc, isActive: !doc.isActive };
|
const newStatus = !doc.isActive;
|
||||||
await updateDoctorApi(doc.doctorId, updatedDoc);
|
|
||||||
|
const payload = {
|
||||||
|
isActive: newStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateDoctorApi(doc.doctorId, payload);
|
||||||
|
|
||||||
fetchAll();
|
fetchAll();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update status", err);
|
console.error("Failed to update status", err);
|
||||||
|
|||||||
Reference in New Issue
Block a user