diff --git a/client/actions/form.js b/client/actions/form.js
index 5541cf7..00c82b5 100644
--- a/client/actions/form.js
+++ b/client/actions/form.js
@@ -86,12 +86,13 @@ export async function cancelAppointment(email) {
revalidatePath("/my-appointments");
}
-export async function updateVisitedOnServer(id) {
- const response = await fetch(serverUrl + `appointments/${id}`, {
+export async function updateStatusOnServer(id, newStatus) {
+ const response = await fetch(serverUrl + `appointments/${id}/status-change`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
+ body: JSON.stringify({ newStatus }),
});
const data = await response.json();
@@ -99,20 +100,6 @@ export async function updateVisitedOnServer(id) {
if (!response.ok) {
return { message: data.message };
}
-}
-// export async function updateStatusOnServer(address, status) {
-// const response = await fetch(serverUrl + "appointments/status-change", {
-// method: "PATCH",
-// headers: {
-// "Content-Type": "application/json",
-// },
-// body: JSON.stringify({ address, status }),
-// });
-
-// const data = await response.json();
-
-// if (!response.ok) {
-// return { message: data.message };
-// }
-// }
+ revalidatePath("/admin-dashboard");
+}
diff --git a/client/app/admin-dashboard/page.jsx b/client/app/admin-dashboard/page.jsx
index ac7b019..4a9d44a 100644
--- a/client/app/admin-dashboard/page.jsx
+++ b/client/app/admin-dashboard/page.jsx
@@ -6,14 +6,17 @@ import Spinner from "@/components/Spinner";
import { fetchAppointments } from "@/actions/form";
async function Dashboard() {
- const appointments = await fetchAppointments();
+ const initAppointments = await fetchAppointments();
+
+ console.log("dashboard", initAppointments);
+
return (
-
+
-
+
);
diff --git a/client/components/admin_dashboard/Grid.jsx b/client/components/admin_dashboard/Grid.jsx
index 44d09be..0e5b054 100644
--- a/client/components/admin_dashboard/Grid.jsx
+++ b/client/components/admin_dashboard/Grid.jsx
@@ -7,42 +7,9 @@ import {
GridToolbarContainer,
} from "@mui/x-data-grid";
import { Paper, Box, Tooltip } from "@mui/material";
-import Button from "@mui/material/Button";
import SearchBar from "./SearchBar";
-import StatusChange from "./StatusChange";
-import { fetchAppointments, updateVisitedOnServer } from "@/actions/form";
-
-const formatName = (name) => {
- const [firstName, ...rest] = name.split(" ");
- const lastName = rest.join(" ");
- if (!lastName) {
- return firstName;
- }
- return `${lastName}, ${firstName}`;
-};
-
-const formatTime = (hour) => {
- return hour <= 12 ? `${hour}a` : `${hour - 12}p`;
-};
-
-const formatTimeRange = (timeRange) => {
- return `${formatTime(timeRange?.preferredEarlyTime)} - ${formatTime(
- timeRange?.preferredLateTime
- )}`;
-};
-
-const formatDateCreated = (date) => {
- return `${date.toLocaleDateString()} ${date.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- })}`;
-};
-
-const formatPhone = (phone) => {
- const cleaned = ("" + phone).replace(/\D/g, "");
- const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
- return match ? `(${match[1]}) ${match[2]}-${match[3]}` : phone;
-};
+import { getColumns } from "./grid-components/columns";
+import { updateStatusOnServer } from "@/actions/form";
const paginationModel = { page: 0, pageSize: 15 };
@@ -52,81 +19,21 @@ const Toolbar = () => (
);
-export default function Grid({ rows }) {
+export default function Grid({ rows, refreshData }) {
const [searchText, setSearchText] = useState("");
- const [customRows, setCustomRows] = useState(rows);
- const columns = [
- {
- field: "visitOrder",
- headerName: "Visit Order",
- width: 190,
- valueGetter: (_, row) => row.schedule.order,
- },
- {
- field: "markVisited",
- headerName: "Visited?",
- width: 190,
- renderCell: (params) => {
- const { id, status } = params.row;
-
- return (
-
- );
- },
- },
- {
- field: "status",
- headerName: "Status",
- width: 190,
- renderCell: (status) => ,
- },
- {
- valueFormatter: formatName,
- field: "name",
- headerName: "Name",
- width: 190,
- },
- {
- valueFormatter: formatDateCreated,
- field: "dateCreated",
- headerName: "Requested on",
- width: 190,
- },
- {
- valueFormatter: formatTimeRange,
- field: "timeRange",
- headerName: "Timeslot",
- width: 190,
- },
- {
- valueFormatter: formatPhone,
- field: "phone",
- headerName: "Phone",
- width: 190,
- },
- { field: "email", headerName: "Email", width: 190 },
- {
- field: "address",
- headerName: "Address",
- width: 190,
- },
- ];
-
- const toggleVisited = async (id) => {
- await updateVisitedOnServer(id);
- const updatedRows = await fetchAppointments();
- setCustomRows(updatedRows);
+ const toggleVisited = async (id, status) => {
+ await updateStatusOnServer(id, status);
+ await refreshData();
};
+ const columns = useMemo(
+ () => getColumns(refreshData, toggleVisited),
+ [refreshData]
+ );
+
const filteredRows = useMemo(() => {
- if (!searchText) return customRows;
+ if (!searchText) return rows;
const needToFormat = {
dateCreated: (value) => formatDateCreated(new Date(value)),
@@ -135,7 +42,7 @@ export default function Grid({ rows }) {
row.markVisited ? "Visited" : "Need to Visit",
};
- return customRows.filter((row) =>
+ return rows.filter((row) =>
columns.some((col) => {
const value = row[col.field];
const formattedValue = needToFormat[col.field]
@@ -147,7 +54,7 @@ export default function Grid({ rows }) {
.includes(searchText.toLowerCase());
})
);
- }, [searchText, customRows]);
+ }, [searchText, rows]);
const onSearchChange = (value) => {
setSearchText(value);
diff --git a/client/components/admin_dashboard/Map.jsx b/client/components/admin_dashboard/Map.jsx
index c120e03..9672866 100644
--- a/client/components/admin_dashboard/Map.jsx
+++ b/client/components/admin_dashboard/Map.jsx
@@ -3,8 +3,8 @@ import { GoogleMap, LoadScript } from "@react-google-maps/api";
import { googleApiKey, appointmentsMapId } from "@/constants";
import React, { useRef } from "react";
-export default function Map({ appointments }) {
- if (!appointments.length) {
+export default function Map({ initAppointments }) {
+ if (!initAppointments.length) {
return
No map data available
;
}
@@ -15,7 +15,7 @@ export default function Map({ appointments }) {
height: "400px",
};
- const startCoords = appointments.map((item) => ({
+ const startCoords = initAppointments.map((item) => ({
location: item.location,
visitOrder: item.schedule.order,
customerName: item.name,
@@ -40,7 +40,6 @@ export default function Map({ appointments }) {
});
});
};
- console.log(appointments[0]);
return (
{
+ const updatedAppts = await fetchAppointments();
+
+ setAppointments(updatedAppts);
+ };
+
+ console.log("reso table - grid", appointments);
-export default function ReservationTable({ appointments }) {
return (
-
+
);
}
diff --git a/client/components/admin_dashboard/StatusChange.jsx b/client/components/admin_dashboard/grid-components/StatusChange.jsx
similarity index 59%
rename from client/components/admin_dashboard/StatusChange.jsx
rename to client/components/admin_dashboard/grid-components/StatusChange.jsx
index 3a05e66..2830e93 100644
--- a/client/components/admin_dashboard/StatusChange.jsx
+++ b/client/components/admin_dashboard/grid-components/StatusChange.jsx
@@ -1,11 +1,13 @@
+"use client";
import React from "react";
import { Box, InputLabel, MenuItem, FormControl, Select } from "@mui/material";
+import { updateStatusOnServer } from "@/actions/form";
-export default function StatusChange() {
- const [status, setStatus] = React.useState("Requested");
-
- const handleChange = (e) => {
- setStatus(e.target.value);
+export default function StatusChange({ id, refreshData, currentStatus }) {
+ const handleChange = async (e, id) => {
+ const newStatus = e.target.value;
+ await updateStatusOnServer(id, newStatus);
+ await refreshData();
};
return (
@@ -14,15 +16,15 @@ export default function StatusChange() {
diff --git a/client/components/admin_dashboard/grid-components/VisitedChip.jsx b/client/components/admin_dashboard/grid-components/VisitedChip.jsx
new file mode 100644
index 0000000..cc71689
--- /dev/null
+++ b/client/components/admin_dashboard/grid-components/VisitedChip.jsx
@@ -0,0 +1,17 @@
+import { Button } from "@mui/material";
+
+const VisitedChip = ({ id, status, toggleVisited }) => {
+ const visited = status === "Visited" || status === "Completed";
+
+ return (
+
+ );
+};
+
+export default VisitedChip;
diff --git a/client/components/admin_dashboard/grid-components/columns.jsx b/client/components/admin_dashboard/grid-components/columns.jsx
new file mode 100644
index 0000000..c98079d
--- /dev/null
+++ b/client/components/admin_dashboard/grid-components/columns.jsx
@@ -0,0 +1,74 @@
+import VisitedChip from "./VisitedChip";
+import StatusChange from "./StatusChange";
+import {
+ formatName,
+ formatDateCreated,
+ formatTimeRange,
+ formatPhone,
+} from "./formatters";
+
+export const getColumns = (refreshData, toggleVisited) => [
+ {
+ field: "visitOrder",
+ headerName: "Visit Order",
+ width: 190,
+ valueGetter: (_, row) => row.schedule.order,
+ },
+ {
+ field: "markVisited",
+ headerName: "Visited?",
+ width: 190,
+ renderCell: (params) => (
+
+ ),
+ },
+ {
+ field: "status",
+ headerName: "Status",
+ width: 190,
+ renderCell: (params) => {
+ const { id, status } = params.row;
+ return (
+
+ );
+ },
+ },
+ {
+ valueFormatter: formatName,
+ field: "name",
+ headerName: "Name",
+ width: 190,
+ },
+ {
+ valueFormatter: formatDateCreated,
+ field: "dateCreated",
+ headerName: "Requested on",
+ width: 190,
+ },
+ {
+ valueFormatter: formatTimeRange,
+ field: "timeRange",
+ headerName: "Timeslot",
+ width: 190,
+ },
+ {
+ valueFormatter: formatPhone,
+ field: "phone",
+ headerName: "Phone",
+ width: 190,
+ },
+ { field: "email", headerName: "Email", width: 190 },
+ {
+ field: "address",
+ headerName: "Address",
+ width: 190,
+ },
+];
diff --git a/client/components/admin_dashboard/grid-components/formatters.jsx b/client/components/admin_dashboard/grid-components/formatters.jsx
new file mode 100644
index 0000000..67293e6
--- /dev/null
+++ b/client/components/admin_dashboard/grid-components/formatters.jsx
@@ -0,0 +1,31 @@
+export const formatName = (name) => {
+ const [firstName, ...rest] = name.split(" ");
+ const lastName = rest.join(" ");
+ if (!lastName) {
+ return firstName;
+ }
+ return `${lastName}, ${firstName}`;
+};
+
+export const formatTime = (hour) => {
+ return hour <= 12 ? `${hour}a` : `${hour - 12}p`;
+};
+
+export const formatTimeRange = (timeRange) => {
+ return `${formatTime(timeRange?.preferredEarlyTime)} - ${formatTime(
+ timeRange?.preferredLateTime
+ )}`;
+};
+
+export const formatDateCreated = (date) => {
+ return `${date.toLocaleDateString()} ${date.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}`;
+};
+
+export const formatPhone = (phone) => {
+ const cleaned = ("" + phone).replace(/\D/g, "");
+ const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
+ return match ? `(${match[1]}) ${match[2]}-${match[3]}` : phone;
+};
diff --git a/server/src/controllers/appointments.controller.js b/server/src/controllers/appointments.controller.js
index 7abe511..b50f81d 100644
--- a/server/src/controllers/appointments.controller.js
+++ b/server/src/controllers/appointments.controller.js
@@ -120,7 +120,10 @@ export async function getAllAppointments(req, res, next) {
try {
const appointments = await Appointment.find();
const withScheduling = appendSchedule(
- appointments.map((a) => a.toObject())
+ appointments.map((a) => ({
+ ...a.toObject(),
+ id: a.id,
+ }))
);
res.status(200).json(withScheduling);
} catch (error) {
@@ -188,22 +191,3 @@ export async function cancelAppointment(req, res, next) {
return next({ message: "An internal server error occurred" });
}
}
-
-export async function updateVisited(req, res) {
- const { id } = req.params;
-
- try {
- const data = await Appointment.findById(id);
- console.log("data", data);
- if (!data) {
- return res.status(404).json({ message: "Could not grab data" });
- }
- const newStatus = data.status === "Confirmed" ? "Visited" : "Confirmed";
- data.status = newStatus;
- const visited = await data.save();
- console.log("server: visited", visited);
- return res.status(200).json(visited);
- } catch (error) {
- return res.status(500).json({ message: "Server error: updating status" });
- }
-}
diff --git a/server/src/models/appointments.model.js b/server/src/models/appointments.model.js
index 0691c2a..d3916b4 100644
--- a/server/src/models/appointments.model.js
+++ b/server/src/models/appointments.model.js
@@ -16,7 +16,7 @@ const appointmentSchema = new mongoose.Schema({
},
status: {
type: String,
- enum: ["Pending", "Confirmed", "Cancelled", "Visited"],
+ enum: ["Requested", "Confirmed", "Scheduled", "Completed", "Cancelled"],
default: "Pending",
},
notifications: {
diff --git a/server/src/routes/appointments.routes.js b/server/src/routes/appointments.routes.js
index 9743c89..57c5d99 100644
--- a/server/src/routes/appointments.routes.js
+++ b/server/src/routes/appointments.routes.js
@@ -4,9 +4,9 @@ import {
getSingleAppointment,
getUsersAppointments,
newAppointment,
- updateVisited,
cancelAppointment,
} from "../controllers/appointments.controller.js";
+import { updateStatus } from "../scheduling/scheduler.js";
const router = Router();
@@ -25,7 +25,7 @@ router.get("/:email/all", getUsersAppointments);
// PATCH "/appointments/cancel"
router.patch("/cancel", cancelAppointment);
-// PATCH "/appointments/:id"
-router.patch("/:id", updateVisited);
+// PATCH "/appointments/:id/status-change"
+router.patch("/:id/status-change", updateStatus);
export default router;
diff --git a/server/src/scheduling/scheduler.js b/server/src/scheduling/scheduler.js
index 8ba9506..ba53071 100644
--- a/server/src/scheduling/scheduler.js
+++ b/server/src/scheduling/scheduler.js
@@ -1,3 +1,5 @@
+import Appointment from "../models/appointments.model.js";
+
/**
* Comparator function for 2 appointments. See the definition of array.sort()
*/
@@ -8,21 +10,47 @@ const appointmentComparator = (a, b) => a.dateCreated - b.dateCreated;
* Doesn't change the order of the array.
*/
const appendSchedule = (appointments) => {
- const comparator = (a, b) => appointmentComparator(a.appointment, b.appointment)
- const sortedArray = appointments.map((appointment, index) => ({ appointment, index })).sort(comparator);
-
- const orderMap = new Map();
- sortedArray.forEach((item, index) => {
- const visitOrder = index + 1;
- orderMap.set(item.index, visitOrder);
- });
-
- return appointments.map((appointment, index) => ({
- ...appointment,
- schedule: {
- order: orderMap.get(index),
- },
- }))
+ const comparator = (a, b) =>
+ appointmentComparator(a.appointment, b.appointment);
+ const sortedArray = appointments
+ .map((appointment, index) => ({ appointment, index }))
+ .sort(comparator);
+
+ const orderMap = new Map();
+ sortedArray.forEach((item, index) => {
+ const visitOrder = index + 1;
+ orderMap.set(item.index, visitOrder);
+ });
+
+ return appointments.map((appointment, index) => ({
+ ...appointment,
+ schedule: {
+ order: orderMap.get(index),
+ },
+ }));
+};
+
+export async function updateStatus(req, res) {
+ const { id } = req.params;
+ const { newStatus } = req.body;
+
+ try {
+ const data = await Appointment.findByIdAndUpdate(
+ { _id: id },
+ { status: newStatus },
+ { new: true }
+ );
+
+ if (!data) {
+ return res.status(404).json({ message: "Could not get data" });
+ }
+
+ console.log("data.status", data.status);
+ return res.status(200).json(data.status);
+ } catch (error) {
+ console.log(error);
+ return res.status(500).json({ message: "Server error: saving new status" });
+ }
}
-export { appendSchedule }
\ No newline at end of file
+export { appendSchedule };