From 3c1230c6c1ce7a147df14149857ebe35c7005581 Mon Sep 17 00:00:00 2001 From: Fred Shema Date: Wed, 24 Jul 2024 02:06:12 +0200 Subject: [PATCH] ft: add appointment date filters --- .eslintrc.json | 3 +- Dockerfile | 4 + app/dashboard/appointments/page.tsx | 154 +++++++++--------- components/MainSection.tsx | 42 ++--- .../appointments/appointmentRequests.tsx | 68 ++------ .../appointments/cancelledAppointments.tsx | 20 +-- components/dashboard/appointments/helpers.ts | 102 ++++++++++++ .../appointments/upcomingAppointments.tsx | 77 ++------- components/navbar.tsx | 8 - 9 files changed, 242 insertions(+), 236 deletions(-) create mode 100644 components/dashboard/appointments/helpers.ts diff --git a/.eslintrc.json b/.eslintrc.json index cd156fc..ff17f87 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,7 @@ "rules": { "@next/next/no-img-element": "off", "jsx-a11y/alt-text": "off", - "react/no-unescaped-entities": "off" + "react/no-unescaped-entities": "off", + " react-hooks/exhaustive-deps": "off" } } diff --git a/Dockerfile b/Dockerfile index abf155c..55e0202 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ # Adjust NODE_VERSION as desired ARG NODE_VERSION=20.15.0 +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY FROM node:${NODE_VERSION}-slim as base LABEL fly_launch_runtime="Next.js" @@ -27,6 +29,8 @@ RUN npm ci --include=dev # Copy application code COPY --link . . +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY # Build application RUN npm run build diff --git a/app/dashboard/appointments/page.tsx b/app/dashboard/appointments/page.tsx index c272e52..949fc92 100644 --- a/app/dashboard/appointments/page.tsx +++ b/app/dashboard/appointments/page.tsx @@ -1,100 +1,108 @@ "use client"; import AppointmentRequests from "@/components/dashboard/appointments/appointmentRequests"; import CancelledAppointments from "@/components/dashboard/appointments/cancelledAppointments"; +import { DateValue } from "@/components/dashboard/appointments/helpers"; import UpcomingAppointments from "@/components/dashboard/appointments/upcomingAppointments"; import Navbar from "@/components/navbar"; import TopBar from "@/components/topbar"; import Link from "next/link"; -import { useContext, useState } from "react"; +import { createContext, useContext, useState } from "react"; import Calendar from "react-calendar"; import "react-calendar/dist/Calendar.css"; import { AuthContext } from "../layout"; -type ValuePiece = Date | null; -type DateValue = ValuePiece | [ValuePiece, ValuePiece]; +export const AppointmentCalendarContext = createContext<{ + date: DateValue; + setDate: (date: DateValue) => void; +}>({ + date: new Date(), + setDate: () => {}, +}); export default function AppointmentsPage() { const currentUser = useContext(AuthContext); const [date, setDate] = useState(new Date()); return ( -
-
- -
-
- -
-
-

- Good morning   - - Dr. {currentUser?.firstName}! - -

- -
-
-
-
-
Calendar
- - - -
-
- -
-
-

Upcoming

- - View all - -
-
-
-

M

+ +
+
+ +
+
+ +
+
+

+ Good morning   + + Dr. {currentUser?.firstName}! + +

+ +
+
+
+
+
Calendar
+ + +
-

- Monthly doctor's meet -

-

- 8 April, 2021 | 04:00 PM -

+ +
+
+

Upcoming

+ + View all + +
+
+
+

M

+
+
+

+ Monthly doctor's meet +

+

+ 8 April, 2021 | 04:00 PM +

+
-
-
-
- -
-
- +
+
+ +
+
+ +
-
-
+
+ ); } diff --git a/components/MainSection.tsx b/components/MainSection.tsx index b0cdbf8..78c597f 100644 --- a/components/MainSection.tsx +++ b/components/MainSection.tsx @@ -9,9 +9,11 @@ import "react-calendar/dist/Calendar.css"; import { Appointment, getColorByPackage, - Patient, -} from "./dashboard/appointments/upcomingAppointments"; + getDate, + getPatientName, +} from "./dashboard/appointments/helpers"; import ListItem from "./listItem"; +import Loading from "./Loading"; import TopBar from "./topbar"; type ValuePiece = Date | null; @@ -20,12 +22,12 @@ type Value = ValuePiece | [ValuePiece, ValuePiece]; export default function MainSection() { const currentUser = useContext(AuthContext); const [appointments, setAppointments] = useState([]); - - const getPatientName = (patient: Patient) => - patient.full_name + " " + patient.nickname; + const [date, setDate] = useState(new Date()); + const [loading, setLoading] = useState(true); useEffect(() => { if (!currentUser) return; + setLoading(true); const supabase = createClient(); const fetchAppointments = async () => { const { data, error } = await supabase @@ -35,10 +37,12 @@ export default function MainSection() { ) .eq("doctor_id", currentUser.id) .eq("status", "Approved") - .gt("appointment_date", new Date().toISOString()) + .gt("appointment_date", getDate(date).toISOString()) .order("appointment_date", { ascending: true }) .limit(5); + setLoading(false); + if (error) { console.log(error); } @@ -48,9 +52,7 @@ export default function MainSection() { } }; fetchAppointments(); - }, [currentUser]); - - const [dateValue, setDateValue] = useState(new Date()); + }, [currentUser, date]); return (
@@ -121,22 +123,24 @@ export default function MainSection() {

Upcoming appointments

-
-

Today

- accordion -
- {appointments.length === 0 && ( + {loading && appointments.length == 0 && ( +
+ +
+ )} + {!loading && appointments.length === 0 && (

No appointments found

)} - {appointments.length > 0 && + {!loading && + appointments.length > 0 && appointments.map((appointment) => (
diff --git a/components/dashboard/appointments/appointmentRequests.tsx b/components/dashboard/appointments/appointmentRequests.tsx index dc139df..2be397c 100644 --- a/components/dashboard/appointments/appointmentRequests.tsx +++ b/components/dashboard/appointments/appointmentRequests.tsx @@ -1,67 +1,32 @@ +import { AppointmentCalendarContext } from "@/app/dashboard/appointments/page"; import { AuthContext } from "@/app/dashboard/layout"; import Loading from "@/components/Loading"; import { createClient } from "@/utils/supabase/client"; import { useContext, useEffect, useState } from "react"; import { Appointment, + getAppointmentDate, + getAppointmentDuration, + getAppointmentTime, getColorByPackage, - Patient, -} from "./upcomingAppointments"; + getDate, + getPatientAge, + getPatientName, +} from "./helpers"; export default function AppointmentRequests() { const currentUser = useContext(AuthContext); + const { date } = useContext(AppointmentCalendarContext); const [appointments, setAppointments] = useState([]); const [selectedAppointment, setSelectedAppointment] = useState(null); const [loading, setLoading] = useState(true); const [updating, setUpdating] = useState(false); - const getPatientName = (patient: Patient) => - patient.full_name + " " + patient.nickname; - - const getAppointmentTime = (time: string) => { - const [hours, minutes] = time.split(":"); - return `${hours}:${minutes} ${parseInt(hours) >= 12 ? "PM" : "AM"}`; - }; - - const getAppointmentDate = (date: string) => - new Date(date).toLocaleDateString("en-US", { - weekday: "long", - year: "numeric", - month: "long", - day: "numeric", - }); - - const getAppointmentDuration = (time: string, duration: string) => { - const [hours, minutes] = time.split(":"); - const [count, identifier] = duration.split(" "); - const totalMinutes = parseInt(hours) * 60 + parseInt(minutes); - let endMinutes = totalMinutes; - - if (identifier === "minutes") { - endMinutes += parseInt(count); - } else { - endMinutes += parseInt(count) * 60; - } - - const endHours = Math.floor(endMinutes / 60); - const endMinutesRemainder = endMinutes % 60; - - return `${hours}:${minutes} - ${endHours}:${endMinutesRemainder} ${ - endHours >= 12 ? "PM" : "AM" - } (${duration})`; - }; - - const getPatientAge = (dateOfBirth: string) => { - if (!dateOfBirth) return "N/A"; - const dob = new Date(dateOfBirth); - const diffMs = Date.now() - dob.getTime(); - const ageDt = new Date(diffMs); - return Math.abs(ageDt.getUTCFullYear() - 1970); - }; - useEffect(() => { if (!currentUser) return; + if (!date) return; + const supabase = createClient(); const fetchAppointments = async () => { setLoading(true); @@ -72,6 +37,7 @@ export default function AppointmentRequests() { ) .eq("doctor_id", currentUser.id) .eq("status", "Booked") + .gt("appointment_date", getDate(date).toISOString()) .order("appointment_date", { ascending: true }) .limit(5); @@ -86,7 +52,7 @@ export default function AppointmentRequests() { } }; fetchAppointments(); - }, [currentUser]); + }, [currentUser, date]); const approveAppointment = async (appointment: Appointment) => { const supabase = createClient(); @@ -214,14 +180,6 @@ export default function AppointmentRequests() {

Appointment Requests

-
- -
{loading && appointments.length == 0 && ( diff --git a/components/dashboard/appointments/cancelledAppointments.tsx b/components/dashboard/appointments/cancelledAppointments.tsx index 2998783..cbcc641 100644 --- a/components/dashboard/appointments/cancelledAppointments.tsx +++ b/components/dashboard/appointments/cancelledAppointments.tsx @@ -1,15 +1,13 @@ +import { AppointmentCalendarContext } from "@/app/dashboard/appointments/page"; import { AuthContext } from "@/app/dashboard/layout"; import Loading from "@/components/Loading"; import { createClient } from "@/utils/supabase/client"; import { useContext, useEffect, useState } from "react"; -import { - Appointment, - getColorByPackage, - Patient, -} from "./upcomingAppointments"; +import { Appointment, getColorByPackage, getDate, Patient } from "./helpers"; export default function CancelledAppointments() { const currentUser = useContext(AuthContext); + const { date } = useContext(AppointmentCalendarContext); const [appointments, setAppointments] = useState([]); const [loading, setLoading] = useState(true); @@ -33,6 +31,7 @@ export default function CancelledAppointments() { ) .eq("doctor_id", currentUser.id) .eq("status", "Cancelled") + .gt("appointment_date", getDate(date).toISOString()) .order("appointment_date", { ascending: true }) .limit(5); @@ -47,20 +46,13 @@ export default function CancelledAppointments() { } }; fetchAppointments(); - }, [currentUser]); + }, [currentUser, date]); return (

Cancelled Appointments

-
- -
+
{loading && appointments.length == 0 && ( diff --git a/components/dashboard/appointments/helpers.ts b/components/dashboard/appointments/helpers.ts new file mode 100644 index 0000000..ea63c29 --- /dev/null +++ b/components/dashboard/appointments/helpers.ts @@ -0,0 +1,102 @@ +import { UUID } from "crypto"; + +type ValuePiece = Date | null; +export type DateValue = ValuePiece | [ValuePiece, ValuePiece]; + +export type Patient = { + id: UUID; + full_name: string; + nickname: string; + email: string; + phone: string; + gender: string; + date_of_birth: string; + problem: string; +}; + +export type Appointment = { + id: UUID; + patient_id: UUID; + doctor_id: UUID; + date: string; + time: string; + status: "Booked" | "Approved" | "Completed" | "Cancelled" | "Rescheduled"; + package: "Messaging" | "Video Call" | "Voice Call"; + duration: string; + amount: string; + cancellation_reason: string; + patient: Patient; +}; + +const colors = [ + { + type: "Messaging", + light: "#fdd2e7", + dark: "#F62088", + }, + { + type: "Video Call", + light: "#ccccff", + dark: "#6666ff", + }, + { + type: "Voice Call", + light: "#cfe7e6", + dark: "#128983", + }, +]; + +export const getColorByPackage = (type: string) => { + return colors.find((color) => color.type === type); +}; + +export const getPatientName = (patient: Patient) => + patient.full_name + " " + patient.nickname; + +export const getAppointmentTime = (time: string) => { + const [hours, minutes] = time.split(":"); + return `${hours}:${minutes} ${parseInt(hours) >= 12 ? "PM" : "AM"}`; +}; + +export const getAppointmentDate = (date: string) => + new Date(date).toLocaleDateString("en-US", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }); + +export const getPatientAge = (dateOfBirth: string) => { + if (!dateOfBirth) return "N/A"; + const dob = new Date(dateOfBirth); + const diffMs = Date.now() - dob.getTime(); + const ageDt = new Date(diffMs); + return Math.abs(ageDt.getUTCFullYear() - 1970); +}; + +export const getAppointmentDuration = (time: string, duration: string) => { + const [hours, minutes] = time.split(":"); + const [count, identifier] = duration.split(" "); + const totalMinutes = parseInt(hours) * 60 + parseInt(minutes); + let endMinutes = totalMinutes; + + if (identifier === "minutes") { + endMinutes += parseInt(count); + } else { + endMinutes += parseInt(count) * 60; + } + + const endHours = Math.floor(endMinutes / 60); + const endMinutesRemainder = endMinutes % 60; + + return `${hours}:${minutes} - ${endHours}:${endMinutesRemainder} ${ + endHours >= 12 ? "PM" : "AM" + } (${duration})`; +}; + +export const getDate = (date: DateValue) => { + const d = new Date(date?.toString() ?? ""); + return new Date( + Date.parse(`${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()} 00:00:00`) + ); +}; diff --git a/components/dashboard/appointments/upcomingAppointments.tsx b/components/dashboard/appointments/upcomingAppointments.tsx index ee4e210..a0a12c8 100644 --- a/components/dashboard/appointments/upcomingAppointments.tsx +++ b/components/dashboard/appointments/upcomingAppointments.tsx @@ -1,72 +1,25 @@ +import { AppointmentCalendarContext } from "@/app/dashboard/appointments/page"; import { AuthContext } from "@/app/dashboard/layout"; import Loading from "@/components/Loading"; import { createClient } from "@/utils/supabase/client"; -import { UUID } from "crypto"; import { useContext, useEffect, useState } from "react"; - -export type Patient = { - id: UUID; - full_name: string; - nickname: string; - email: string; - phone: string; - gender: string; - date_of_birth: string; - problem: string; -}; - -export type Appointment = { - id: UUID; - patient_id: UUID; - doctor_id: UUID; - date: string; - time: string; - status: "Booked" | "Approved" | "Completed" | "Cancelled" | "Rescheduled"; - package: "Messaging" | "Video Call" | "Voice Call"; - duration: string; - amount: string; - cancellation_reason: string; - patient: Patient; -}; - -const colors = [ - { - type: "Messaging", - light: "#fdd2e7", - dark: "#F62088", - }, - { - type: "Video Call", - light: "#ccccff", - dark: "#6666ff", - }, - { - type: "Voice Call", - light: "#cfe7e6", - dark: "#128983", - }, -]; - -export const getColorByPackage = (type: string) => { - return colors.find((color) => color.type === type); -}; +import { + Appointment, + getAppointmentTime, + getColorByPackage, + getDate, + getPatientName, +} from "./helpers"; export default function UpcomingAppointments() { const currentUser = useContext(AuthContext); + const { date } = useContext(AppointmentCalendarContext); const [appointments, setAppointments] = useState([]); const [selectedAppointment, setSelectedAppointment] = useState(null); const [updating, setUpdating] = useState(false); const [loading, setLoading] = useState(true); - const getPatientName = (patient: Patient) => - patient.full_name + " " + patient.nickname; - - const getAppointmentTime = (time: string) => { - const [hours, minutes] = time.split(":"); - return `${hours}:${minutes} ${parseInt(hours) >= 12 ? "PM" : "AM"}`; - }; - useEffect(() => { if (!currentUser) return; const supabase = createClient(); @@ -79,7 +32,7 @@ export default function UpcomingAppointments() { ) .eq("doctor_id", currentUser.id) .eq("status", "Approved") - .gt("appointment_date", new Date().toISOString()) + .gt("appointment_date", getDate(date).toISOString()) .order("appointment_date", { ascending: true }) .limit(5); @@ -94,7 +47,7 @@ export default function UpcomingAppointments() { } }; fetchAppointments(); - }, [currentUser]); + }, [currentUser, date]); const cancelAppointment = async (appointment: Appointment) => { const supabase = createClient(); @@ -179,14 +132,6 @@ export default function UpcomingAppointments() {

Upcoming appointments

-
- -
{loading && appointments.length == 0 && ( diff --git a/components/navbar.tsx b/components/navbar.tsx index 6f0b95c..e455632 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -64,14 +64,6 @@ export default function Navbar() {
Messages -
-
- - home{" "} - -
- Reports -