Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| daf0178330 | |||
| 3140d72e28 | |||
| 6117805467 | |||
| b002c053ae | |||
| e6044518d2 | |||
| 6889137164 | |||
| 988fbd28f1 | |||
| fa2b02ad23 | |||
| 7e39683aa2 | |||
| 199797fdf4 | |||
| 5efe049fbd | |||
| a008f09923 | |||
| f60dbe14e6 | |||
| 71da0243be | |||
| 86d41f6c2f |
@@ -84,7 +84,11 @@ 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()}
|
||||
${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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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({
|
||||
where: {doctorId: doctor.id},
|
||||
});
|
||||
@@ -304,12 +309,12 @@ export const updateDoctor = async (req, res) => {
|
||||
if (dep.timing) {
|
||||
const {id, doctorDepartmentId, createdAt, updatedAt, ...cleanTiming} =
|
||||
dep.timing;
|
||||
|
||||
await prisma.doctorTiming.create({
|
||||
data: {doctorDepartmentId: newDD.id, ...cleanTiming},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
.status(200)
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -8,8 +8,10 @@ export interface Doctor {
|
||||
designation?: string;
|
||||
workingStatus?: string;
|
||||
qualification?: string;
|
||||
isActive: boolean;
|
||||
globalSortOrder: number;
|
||||
|
||||
departments: {
|
||||
departments?: {
|
||||
departmentId: string;
|
||||
timing?: {
|
||||
monday?: string;
|
||||
|
||||
@@ -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,7 +119,11 @@ 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">
|
||||
<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}
|
||||
@@ -126,7 +133,12 @@ export default function AppointmentPage() {
|
||||
}}
|
||||
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
|
||||
type="date"
|
||||
value={filterDate}
|
||||
@@ -135,8 +147,46 @@ export default function AppointmentPage() {
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
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
|
||||
value={itemsPerPage}
|
||||
onChange={(e) => {
|
||||
@@ -149,6 +199,7 @@ export default function AppointmentPage() {
|
||||
<option value={10}>10 / page</option>
|
||||
<option value={20}>20 / page</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -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: ""}};
|
||||
}
|
||||
|
||||
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: 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: ""},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -110,7 +110,8 @@ export default function DoctorPage() {
|
||||
fetchAll();
|
||||
}, [fetchAll]);
|
||||
|
||||
const filteredDoctors = doctors.filter((doc) => {
|
||||
const filteredDoctors = doctors
|
||||
.filter((doc) => {
|
||||
const matchesSearch =
|
||||
doc.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
doc.doctorId.toLowerCase().includes(searchText.toLowerCase());
|
||||
@@ -120,6 +121,24 @@ export default function DoctorPage() {
|
||||
: true;
|
||||
|
||||
return matchesSearch && matchesDepartment;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!filterDepartment) {
|
||||
return a.globalSortOrder - b.globalSortOrder;
|
||||
}
|
||||
|
||||
const aDept = a.departments.find(
|
||||
(d: any) => d.departmentId === filterDepartment,
|
||||
);
|
||||
|
||||
const bDept = b.departments.find(
|
||||
(d: any) => d.departmentId === filterDepartment,
|
||||
);
|
||||
|
||||
return (
|
||||
(aDept?.deptSortOrder ?? Number.MAX_SAFE_INTEGER) -
|
||||
(bDept?.deptSortOrder ?? Number.MAX_SAFE_INTEGER)
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -139,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);
|
||||
|
||||
Reference in New Issue
Block a user