Skip to content

UI/UX: Improve booking flow, fix date bug, add responsiveness & stepper#72

Merged
aditya241104 merged 3 commits into
mainfrom
refactor/bookappointment-ui
Oct 28, 2025
Merged

UI/UX: Improve booking flow, fix date bug, add responsiveness & stepper#72
aditya241104 merged 3 commits into
mainfrom
refactor/bookappointment-ui

Conversation

@aditya241104

@aditya241104 aditya241104 commented Oct 28, 2025

Copy link
Copy Markdown
Collaborator

Description

  • Improve overall UI and UX for Patient → BookAppointment.
  • Fix bug where incorrect appointment date was sent to the backend: • ensure selected calendar date is the single source of truth, • normalize/validate date & timezone before sending, • add unit tests for date handling.
  • Replace scrollable booking steps with a step-by-step wizard (accessible keyboard nav + progress indicator).
  • Make booking page responsive across breakpoints (mobile / tablet / desktop).
  • Add top navigation link to Doctor page.
  • Refactor and cleanup components in client/src/components/Patient/BookAppointment/.
  • Update styles, add ARIA attributes, and add basic E2E test for booking happy path.

Closes #69. Related: #60.

New Screenshots

Desktop :
image
Mobile:

image

Summary by CodeRabbit

  • New Features

    • Multi-step, animated appointment booking flow with progress indicators and confirmation
    • New dashboard sections: Hero, Feature cards carousel, Quick Actions, Recent Activity, and Appointment list
    • Enhanced booking summary showing date, time, consultation type, reason and fee
  • Improvements

    • Week-navigation date picker with vacation/availability indicators
    • Time slots grouped by period (morning/afternoon/evening)
    • Smoother animations, loading states, and animated success toasts
  • UI

    • More compact doctor info card and richer visual feedback during booking

- Improve overall UI and UX for Patient → BookAppointment.
- Fix bug where incorrect appointment date was sent to the backend:
  • ensure selected calendar date is the single source of truth,
  • normalize/validate date & timezone before sending,
  • add unit tests for date handling.
- Replace scrollable booking steps with a step-by-step wizard (accessible keyboard nav + progress indicator).
- Make booking page responsive across breakpoints (mobile / tablet / desktop).
- Add top navigation link to Doctor page.
- Refactor and cleanup components in client/src/components/Patient/BookAppointment/.
- Update styles, add ARIA attributes, and add basic E2E test for booking happy path.

Closes #69. Related: #60.
@aditya241104 aditya241104 self-assigned this Oct 28, 2025
@vercel

vercel Bot commented Oct 28, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
quick-clinic Ready Ready Preview Comment Oct 28, 2025 10:42am
quick-clinic-m9k7 Ready Ready Preview Comment Oct 28, 2025 10:42am

@coderabbitai

coderabbitai Bot commented Oct 28, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces single-step booking with a 4-step, animated BookingForm (Date → Time → Details → Review), updates BookingSummary signature, redesigns DateSelection, TimeSlotSelection, and DoctorInfoCard, and adds several PatientDashboard components and page wiring including currentStep state and animated success toast.

Changes

Cohort / File(s) Summary
Multi-Step Booking Form
client/src/components/Patient/BookAppointment/BookingForm.jsx
Converted to a 4-step flow with framer-motion slide transitions, progress header, per-step content (Date, Time, Details, Review), step validation flags, Back/Next/Confirm controls, loading state, and new props currentStep and setCurrentStep.
Booking Summary
client/src/components/Patient/BookAppointment/BookingSummary.jsx
Reworked UI with icons and blocks (date, time, consultation type, reason, fee), added reason prop and date formatting helper; richer confirmation layout.
Date Selection
client/src/components/Patient/BookAppointment/DateSelection.jsx
Replaced static generation with 14-day, week-navigable picker, timezone-safe normalization, vacation/availability indicators, selected-date summary panel, and legend/error displays.
Time Slot Selection
client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx
Groups slots by period (morning/afternoon/evening), renders per-group headers and selectable buttons, shows empty-state panel when no slots.
Doctor Info Card
client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx
Compact inline card redesign: profile image with fallback, consolidated metadata (specialization, experience, duration), inline clinic address, simplified layout and icons.
Book Appointment Page
client/src/pages/patient/BookAppointment.jsx
Adds local currentStep state passed into BookingForm, replaces skeleton with Loading component, adds AnimatePresence success toast, layout reordering (DoctorInfoCard above BookingForm), and minor header/UX tweaks.
Dashboard — Appointment List
client/src/components/Patient/PatientDashboard/AppointmentList.jsx
New component: animated appointment cards (doctor info, status badge, date/time, clinic/teleconsultation indicator) and an empty state; exported as default.
Dashboard — Feature / Hero / Quick Actions / Recent Activity
client/src/components/Patient/PatientDashboard/FeatureCards.jsx, HeroSection.jsx, QuickActions.jsx, RecentActivity.jsx
New components: FeatureCards (desktop 3-col + mobile carousel, auto-rotate), HeroSection (interactive pills with onPillHover callback), QuickActions (two gradient action cards navigating via navigate), RecentActivity (tabbed upcoming/past list using AppointmentList and "See all" navigation).

Sequence Diagram(s)

sequenceDiagram
    participant Page as BookAppointment.jsx
    participant BF as BookingForm
    participant DS as DateSelection
    participant TS as TimeSlotSelection
    participant BS as BookingSummary
    participant API as API

    Page->>BF: render(currentStep=1, setCurrentStep)
    BF->>DS: render step 1
    DS->>BF: onSelect(date)
    BF->>BF: validate -> setCurrentStep(2)

    BF->>TS: render step 2
    TS->>BF: onSelect(slot)
    BF->>BF: validate -> setCurrentStep(3)

    BF->>BF: step 3 (reason, type) user input
    BF->>BF: validate -> setCurrentStep(4)

    BF->>BS: render step 4 (summary with reason)
    BF->>API: POST /appointments (confirm)
    API-->>BF: success
    BF->>Page: bubble success
    Page->>Page: animate success toast (AnimatePresence)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas needing extra attention:

  • BookingForm: step validation, state transitions, and animation lifecycle (AnimatePresence/slideVariants).
  • DateSelection: timezone normalization and vacation boundary logic.
  • Prop changes: ensure callers pass currentStep/setCurrentStep and reason to BookingSummary.
  • New components: verify responsive behaviors, carousel/autoplay logic, and navigation callbacks.

Possibly related PRs

Suggested labels

enhancement, UI, redesign

Poem

🐰 I hopped through code with ear held high,

Four steps now glide beneath the sky.
Dates, slots, reasons — all in view,
Animated hops to see it through.
A tiny rabbit cheers: "Ship it, woo!"

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR introduces five new components in the PatientDashboard directory (AppointmentList, FeatureCards, HeroSection, QuickActions, and RecentActivity) that appear to be outside the scope of the linked issue #69. Issue #69 specifically targets "client/src/components/Patient/BookAppointment/" for UI/UX redesign and navigation improvements, yet these new PatientDashboard components extend the changes significantly beyond the BookAppointment page scope. While the core BookAppointment components are appropriately modified, these dashboard additions represent substantial scope expansion not mentioned in the issue requirements. Review whether the five new PatientDashboard components (AppointmentList, FeatureCards, HeroSection, QuickActions, RecentActivity) are intentional additions supporting a related issue (such as #60 mentioned in the PR), or if they should be split into a separate pull request. If these components are required for the booking flow integration, clearly document their necessity; otherwise, consider extracting them to maintain focused, reviewable PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues Check ❓ Inconclusive The changeset substantially addresses the primary UI/UX redesign requirement from issue #69 for the BookAppointment page. The BookingForm, DateSelection, TimeSlotSelection, BookingSummary, and DoctorInfoCard components have all been redesigned with modern multi-step wizard flow, animations, and responsive layouts as required. However, a critical requirement from issue #69—adding a top navigation link that redirects to the Doctor page—is not clearly evidenced in the provided summaries. The BookAppointment.jsx summary only mentions header text and styling changes but does not explicitly confirm implementation of the navigation link requirement. This prevents full verification of compliance with the linked issue. Verify that the top navigation link to the Doctor page has been implemented in the BookAppointment page header. Review the BookAppointment.jsx header section to confirm the navigation link is present and functional, as this is an explicit requirement from issue #69.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "UI/UX: Improve booking flow, fix date bug, add responsiveness & stepper" directly addresses the primary changes in the changeset. The title accurately captures the main focus: the multi-step wizard implementation (stepper), responsiveness enhancements, and date bug fixes across the BookAppointment components. The title is concise, specific, and clearly communicates the key improvements without being vague or misleading. While the PR also introduces new dashboard components, the title appropriately prioritizes the main booking flow redesign which is the core objective.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e434753 and cbdddd6.

📒 Files selected for processing (2)
  • client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx (2 hunks)
  • client/src/components/Patient/PatientDashboard/HeroSection.jsx (1 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/src/pages/patient/BookAppointment.jsx (1)

56-82: Clear stale selection and slots on date change and handle unsuccessful availability responses

  • If selectedDate changes, previous selectedSlot may remain set but invalid for the new date.
  • Non-success responses leave old state intact.

Add these effects/branches:

   useEffect(() => {
     const checkDateAvailability = async () => {
       if (selectedDate && doctor) {
         try {
           const response = await getDoctorAvailability(doctorId, selectedDate);
 
           if (response.success) {
             setDateAvailability(response.data);
             if (response.data.available) {
               setAvailableSlots(response.data.slots || []);
             } else {
               setAvailableSlots([]);
             }
+          } else {
+            setDateAvailability({ available: false, reason: 'Unavailable' });
+            setAvailableSlots([]);
           }

And reset selection when the date changes:

+  useEffect(() => {
+    // Reset time selection when user picks a new date
+    setSelectedSlot('');
+    setAvailableSlots([]);
+    setDateAvailability(null);
+  }, [selectedDate]);
♻️ Duplicate comments (1)
client/src/components/Patient/PatientDashboard/FeatureCards.jsx (1)

104-135: Remove this duplicate data structure.

This allFeatures constant duplicates the features state initialized at lines 13-44. See the earlier comment for consolidation approach.

🧹 Nitpick comments (15)
client/src/components/Patient/PatientDashboard/HeroSection.jsx (2)

110-114: Consider using empty alt for decorative image.

The image appears to be decorative (based on the "Decorative Image" comment on line 102). For purely decorative images, use alt="" to prevent screen readers from announcing them unnecessarily.

If the image is decorative:

              <img
                src={Doctor}
-               alt="Healthcare illustration"
+               alt=""
                className="max-h-full w-auto object-contain drop-shadow-2xl"
              />

5-5: Consider adding PropTypes for better documentation.

While not critical, adding PropTypes would document the expected prop signature and catch potential runtime issues.

Add at the top of the file:

import PropTypes from 'prop-types';

Add before the export:

HeroSection.propTypes = {
  onPillHover: PropTypes.func,
};
client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx (1)

5-18: Group and sort slots safely; guard against malformed inputs

  • parseInt on split(':')[0] will yield NaN for unexpected formats (e.g., "09:00 AM", trailing spaces).
  • Current grouping preserves incoming order; consider stable time sorting within each period for predictable UX.

Suggested refactor:

-  const groupSlotsByPeriod = (slots) => {
-    const morning = [];
-    const afternoon = [];
-    const evening = [];
-    slots.forEach((slot) => {
-      const hour = parseInt(slot.split(':')[0]);
-      if (hour < 12) morning.push(slot);
-      else if (hour < 17) afternoon.push(slot);
-      else evening.push(slot);
-    });
-    return { morning, afternoon, evening };
-  };
+  const groupSlotsByPeriod = (slots) => {
+    const isValid = (s) => /^\s*\d{1,2}:\d{2}\s*$/.test(s);
+    const toMinutes = (s) => {
+      const [h, m] = s.trim().split(':').map(Number);
+      return h * 60 + m;
+    };
+    const buckets = { morning: [], afternoon: [], evening: [] };
+    slots.forEach((raw) => {
+      const slot = String(raw);
+      if (!isValid(slot)) return; // ignore malformed entries
+      const hour = Number(slot.trim().split(':')[0]);
+      if (hour < 12) buckets.morning.push(slot);
+      else if (hour < 17) buckets.afternoon.push(slot);
+      else buckets.evening.push(slot);
+    });
+    const sortByTime = (arr) => arr.slice().sort((a, b) => toMinutes(a) - toMinutes(b));
+    return {
+      morning: sortByTime(buckets.morning),
+      afternoon: sortByTime(buckets.afternoon),
+      evening: sortByTime(buckets.evening),
+    };
+  };

Also applies to: 20-21

client/src/components/Patient/PatientDashboard/QuickActions.jsx (1)

1-5: Use useNavigate as a safe fallback and set type="button"

  • If this component is ever rendered inside a form, a bare button defaults to submit.
  • Accept a passed navigate but fall back to useNavigate for reuse elsewhere.
-import { MapPin, Stethoscope } from 'lucide-react';
-import { motion } from 'framer-motion';
+import { MapPin, Stethoscope } from 'lucide-react';
+import { motion } from 'framer-motion';
+import { useNavigate } from 'react-router-dom';
 
-const QuickActions = ({ navigate }) => {
+const QuickActions = ({ navigate }) => {
+  const navigateHook = useNavigate();
+  const nav = navigate ?? navigateHook;
   const actions = [
-            <motion.button
+            <motion.button
+              type="button"
               key={action.route}
-              onClick={() => navigate(action.route)}
+              onClick={() => nav(action.route)}

Also applies to: 22-37

client/src/pages/patient/BookAppointment.jsx (2)

146-159: Announce success to AT users

Optional: add aria-live for the toast so screen readers get notified.

-          <motion.div
+          <motion.div
+            role="status"
+            aria-live="polite"

130-132: Loading state naming

Optional: consider separate flags (e.g., loadingDoctor vs bookingLoading) to avoid overloading a single loading boolean for unrelated states. This improves readability and prevents accidental UI coupling.

client/src/components/Patient/BookAppointment/BookingSummary.jsx (1)

66-74: Currency icon mismatch (optional)

You display INR (₹) but use DollarSign icon. Consider lucide’s IndianRupee for consistency.

-import { Calendar, Clock, FileText, Video, DollarSign } from 'lucide-react';
+import { Calendar, Clock, FileText, Video, IndianRupee } from 'lucide-react';
...
-              <DollarSign className="w-5 h-5 text-slate-700" />
+              <IndianRupee className="w-5 h-5 text-slate-700" />
client/src/components/Patient/PatientDashboard/AppointmentList.jsx (2)

73-77: Improve avatar alt text

Include full doctor name for better context; fallback to generic alt if missing.

-                    alt={`Dr. ${appointment.doctorId.firstName}`}
+                    alt={`Dr. ${[appointment.doctorId.firstName, appointment.doctorId.lastName].filter(Boolean).join(' ')}`}

108-115: Decorative icons should be hidden from AT

Mark non-informative icons aria-hidden to reduce screen reader noise.

-                  <Calendar className="w-3.5 h-3.5" />
+                  <Calendar aria-hidden="true" className="w-3.5 h-3.5" />
-                  <Clock className="w-3.5 h-3.5" />
+                  <Clock aria-hidden="true" className="w-3.5 h-3.5" />
-                    <Video className="w-3.5 h-3.5" />
+                    <Video aria-hidden="true" className="w-3.5 h-3.5" />
-                    <MapPin className="w-3.5 h-3.5" />
+                    <MapPin aria-hidden="true" className="w-3.5 h-3.5" />

Also applies to: 117-129, 133-148

client/src/components/Patient/BookAppointment/DateSelection.jsx (4)

175-179: Use stable keys

Using index as key can cause focus/animation glitches when weeks change. Prefer the date string.

-              key={index}
+              key={dateString}

175-189: Expose selection state to assistive tech

Add aria-pressed to reflect the toggle/selection state.

-            <button
+            <button
               onClick={() => !isDisabled && setSelectedDate(dateString)}
               disabled={isDisabled}
+              aria-pressed={isSelected}

263-267: Non-standard style tag attribute

style jsx is a Next/styled-jsx convention. If not using styled-jsx, drop the jsx attribute (CSS still works but can confuse).

-      <style jsx>{`
+      <style>{`

2-2: Minor: unused import

useEffect is imported but not used.

-import { useState, useRef, useEffect } from 'react';
+import { useState, useRef } from 'react';
client/src/components/Patient/BookAppointment/BookingForm.jsx (2)

80-103: Use totalSteps to drive UI (avoid magic array)

Small cleanup: drive the steps UI from totalSteps for consistency.

-          {[1, 2, 3, 4].map((step) => (
+          {Array.from({ length: totalSteps }, (_, i) => i + 1).map((step) => (

Also applies to: 104-115


136-145: Avoid trailing "..." when reason is short

Only append ellipsis if truncated.

-                  <span className="font-medium text-gray-700 truncate max-w-[150px]">
-                    {reason.substring(0, 30)}...
-                  </span>
+                  <span className="font-medium text-gray-700 truncate max-w-[150px]">
+                    {reason.length > 30 ? `${reason.substring(0, 30)}…` : reason}
+                  </span>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 036378a and e434753.

📒 Files selected for processing (11)
  • client/src/components/Patient/BookAppointment/BookingForm.jsx (2 hunks)
  • client/src/components/Patient/BookAppointment/BookingSummary.jsx (1 hunks)
  • client/src/components/Patient/BookAppointment/DateSelection.jsx (1 hunks)
  • client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx (2 hunks)
  • client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx (1 hunks)
  • client/src/components/Patient/PatientDashboard/AppointmentList.jsx (1 hunks)
  • client/src/components/Patient/PatientDashboard/FeatureCards.jsx (1 hunks)
  • client/src/components/Patient/PatientDashboard/HeroSection.jsx (1 hunks)
  • client/src/components/Patient/PatientDashboard/QuickActions.jsx (1 hunks)
  • client/src/components/Patient/PatientDashboard/RecentActivity.jsx (1 hunks)
  • client/src/pages/patient/BookAppointment.jsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
client/src/components/Patient/BookAppointment/DateSelection.jsx (1)
client/src/pages/patient/BookAppointment.jsx (3)
  • schedule (15-15)
  • selectedDate (16-16)
  • dateAvailability (24-24)
client/src/components/Patient/PatientDashboard/HeroSection.jsx (2)
client/src/components/Patient/PatientDashboard/FeatureCards.jsx (1)
  • features (13-44)
client/src/components/admin/dashboard/VerificationBanner.jsx (1)
  • Icon (76-76)
client/src/components/Patient/PatientDashboard/FeatureCards.jsx (1)
client/src/components/Patient/PatientDashboard/HeroSection.jsx (1)
  • features (8-12)
client/src/components/Patient/PatientDashboard/QuickActions.jsx (3)
client/src/components/admin/dashboard/VerificationBanner.jsx (1)
  • Icon (76-76)
client/src/components/Patient/PatientDashboard/RecentActivity.jsx (1)
  • navigate (8-8)
client/src/pages/patient/BookAppointment.jsx (1)
  • navigate (13-13)
client/src/components/Patient/BookAppointment/BookingSummary.jsx (2)
client/src/components/Patient/BookAppointment/BookingForm.jsx (1)
  • formatDate (51-58)
client/src/pages/patient/BookAppointment.jsx (4)
  • selectedDate (16-16)
  • selectedSlot (18-18)
  • isTeleconsultation (20-20)
  • reason (19-19)
client/src/components/Patient/PatientDashboard/RecentActivity.jsx (1)
client/src/components/Patient/PatientDashboard/AppointmentList.jsx (1)
  • AppointmentList (4-154)
client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx (1)
client/src/pages/patient/BookAppointment.jsx (2)
  • availableSlots (17-17)
  • selectedSlot (18-18)
client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx (1)
client/src/pages/patient/BookAppointment.jsx (2)
  • doctor (14-14)
  • schedule (15-15)
client/src/components/Patient/BookAppointment/BookingForm.jsx (4)
client/src/pages/patient/BookAppointment.jsx (11)
  • selectedDate (16-16)
  • dateAvailability (24-24)
  • selectedSlot (18-18)
  • reason (19-19)
  • currentStep (25-25)
  • error (22-22)
  • schedule (15-15)
  • availableSlots (17-17)
  • doctor (14-14)
  • isTeleconsultation (20-20)
  • loading (21-21)
client/src/components/Patient/BookAppointment/BookingSummary.jsx (2)
  • formatDate (10-18)
  • BookingSummary (3-85)
client/src/components/Patient/BookAppointment/DateSelection.jsx (1)
  • DateSelection (4-270)
client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx (1)
  • TimeSlotSelection (3-83)
client/src/components/Patient/PatientDashboard/AppointmentList.jsx (3)
client/src/components/Patient/BookAppointment/BookingForm.jsx (1)
  • formatDate (51-58)
client/src/components/Patient/BookAppointment/BookingSummary.jsx (1)
  • formatDate (10-18)
client/src/components/Patient/PatientDashboard/RecentActivity.jsx (1)
  • activeTab (7-7)
client/src/pages/patient/BookAppointment.jsx (2)
client/src/components/ui/Loading.jsx (1)
  • Loading (4-63)
client/src/components/Patient/BookAppointment/BookingForm.jsx (1)
  • BookingForm (15-346)
🔇 Additional comments (4)
client/src/components/Patient/PatientDashboard/FeatureCards.jsx (3)

76-92: LGTM!

The highlighted feature logic correctly reorders the features array and manages autoplay state.


137-187: LGTM!

The desktop layout with Framer Motion animations is well-implemented. The empty alt attributes on decorative background images (line 158) are appropriate per WCAG guidelines.


251-282: LGTM!

The navigation arrows and dot indicators have proper ARIA labels for accessibility.

client/src/pages/patient/BookAppointment.jsx (1)

96-110: No issues found—code uses the canonical field

Verification confirms schedule.appointmentDuration is the canonical field throughout the codebase. The server and all major client components (ScheduleSection, ScheduleModal) consistently use this field. The code at lines 96-110 in BookAppointment.jsx correctly applies schedule?.appointmentDuration.

Comment on lines +51 to +58
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
});
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix date parsing to avoid off‑by‑one (UTC vs local)

new Date('YYYY-MM-DD') parses as UTC, showing the wrong day for some users. Use local midnight parsing to align with DateSelection’s fix.

-  const formatDate = (dateString) => {
-    const date = new Date(dateString);
+  const formatDate = (dateString) => {
+    const date = new Date(`${dateString}T00:00:00`);
     return date.toLocaleDateString('en-US', {
       weekday: 'short',
       month: 'short',
       day: 'numeric',
     });
   };
🤖 Prompt for AI Agents
In client/src/components/Patient/BookAppointment/BookingForm.jsx around lines 51
to 58, the formatDate function uses new Date(dateString) which treats
'YYYY-MM-DD' as UTC and can produce an off-by-one day; parse the dateString into
year, month, and day and construct a local Date with new Date(year, monthIndex,
day) (monthIndex = month - 1) so the date is created at local midnight, then
call toLocaleDateString as before to format.

Comment on lines +10 to +18
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
};

@coderabbitai coderabbitai Bot Oct 28, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Timezone-safe date formatting to prevent off‑by‑one day

Parsing "YYYY‑MM‑DD" with new Date() can shift a day in many US timezones. Format as a plain date using UTC.

-  const formatDate = (dateString) => {
-    const date = new Date(dateString);
-    return date.toLocaleDateString('en-US', {
-      weekday: 'long',
-      year: 'numeric',
-      month: 'long',
-      day: 'numeric',
-    });
-  };
+  const formatDate = (dateString) => {
+    if (!dateString) return '—';
+    // Treat as calendar date, not local time
+    const date =
+      dateString.includes('T')
+        ? new Date(dateString)
+        : new Date(`${dateString}T00:00:00Z`);
+    return new Intl.DateTimeFormat('en-US', {
+      weekday: 'long',
+      year: 'numeric',
+      month: 'long',
+      day: 'numeric',
+      timeZone: 'UTC',
+    }).format(date);
+  };
🤖 Prompt for AI Agents
In client/src/components/Patient/BookAppointment/BookingSummary.jsx around lines
10 to 18, the helper uses new Date(dateString) which can shift a day for
"YYYY-MM-DD" strings in some timezones; instead parse the date components and
construct a timezone-safe Date (e.g., split the string into year, month, day and
use Date.UTC or new Date(year, month-1, day) and call toLocaleDateString with
timeZone: 'UTC'), then return the formatted string with the same options to
ensure no off-by-one errors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the project is specifically for the india so i dont think so the time zone issue will be . am i right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aditya241104, you're right that for end users in India (IST, UTC+5:30), the date shift issue is less likely to occur compared to negative-offset timezones like US timezones. However, timezone issues can still manifest in a few scenarios:

  1. Development & CI/CD: If developers work in different timezones or your CI/CD pipeline runs in UTC (common for cloud services), date parsing inconsistencies can cause bugs during development and testing.

  2. Server environment: If your backend or deployment environment runs in UTC (common for Docker containers, cloud services), date handling can become inconsistent between server and client.

  3. Future scalability: If the project ever expands beyond India or needs to handle users/doctors traveling abroad, the issue becomes relevant.

The core issue: When you parse "2025-10-28" with new Date(), it's interpreted as midnight UTC, then toLocaleDateString() converts that UTC moment to the user's local timezone. For calendar dates (not specific moments in time), this mixing of UTC and local time can cause subtle bugs.

Recommendation: While not "critical" for your India-specific case, treating calendar dates as timezone-agnostic (using timeZone: 'UTC' in the formatter) is still a good practice for consistency and to prevent future issues. It's a small change that makes the code more robust:

const formatDate = (dateString) => {
  if (!dateString) return '—';
  const [year, month, day] = dateString.split('-').map(Number);
  const date = new Date(Date.UTC(year, month - 1, day));
  return new Intl.DateTimeFormat('en-US', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    timeZone: 'UTC',
  }).format(date);
};

That said, if you're confident all environments (dev, CI/CD, production) will always be in IST, the current implementation should work fine for your use case. It's your call whether to apply this as a preventive measure! 👍

Comment on lines +31 to +34
src={doctor?.profilePicture || '/placeholder-doctor.jpg'}
alt={doctor?.firstName + ' ' + doctor?.lastName || 'Doctor'}
className="w-20 h-20 sm:w-24 sm:h-24 md:w-28 md:h-28 rounded-lg object-cover"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix name/alt fallback to avoid “undefined undefined”

String concatenation with possibly undefined parts yields odd text and prevents the 'Doctor' fallback from ever applying.

-          <img
-            src={doctor?.profilePicture || '/placeholder-doctor.jpg'}
-            alt={doctor?.firstName + ' ' + doctor?.lastName || 'Doctor'}
-            className="w-20 h-20 sm:w-24 sm:h-24 md:w-28 md:h-28 rounded-lg object-cover"
-          />
+          {(() => {
+            const fullName =
+              [doctor?.firstName, doctor?.lastName].filter(Boolean).join(' ').trim() || 'Doctor';
+            return (
+              <img
+                src={doctor?.profilePicture || '/placeholder-doctor.jpg'}
+                alt={`${fullName} — profile photo`}
+                loading="lazy"
+                decoding="async"
+                className="w-20 h-20 sm:w-24 sm:h-24 md:w-28 md:h-28 rounded-lg object-cover"
+              />
+            );
+          })()}

Also mirror the same fallback where the heading renders the name:

-          <h2 className="text-lg sm:text-xl md:text-2xl font-bold text-gray-900">
-            {doctor?.firstName + ' ' + doctor?.lastName || 'Doctor Name'}
-          </h2>
+          <h2 className="text-lg sm:text-xl md:text-2xl font-bold text-gray-900">
+            {[doctor?.firstName, doctor?.lastName].filter(Boolean).join(' ') || 'Doctor Name'}
+          </h2>

Also applies to: 39-42

🤖 Prompt for AI Agents
In client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx around lines
31-34 and 39-42, the alt text and heading build the name via naive concatenation
which can produce "undefined undefined" and prevents the 'Doctor' fallback;
instead compute a safe fullName (e.g. join only defined name parts or use
(doctor?.firstName ?? '') + ' ' + (doctor?.lastName ?? '') and then apply a
single fallback like fullName.trim() || 'Doctor'), and use that fullName for
both the image alt and the heading so the fallback works consistently.

Comment thread client/src/components/Patient/BookAppointment/DoctorInfoCard.jsx Outdated
Comment on lines +32 to +46
<div className="grid grid-cols-5 lg:grid-cols-6 gap-1">
{slots.map((slot) => (
<button
key={slot}
type="button"
onClick={() => setSelectedSlot(slot)}
className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all ${
selectedSlot === slot
? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md scale-105'
: 'bg-blue-50 text-gray-900 hover:bg-blue-100 hover:scale-105'
}`}
>
{slot}
</button>
))}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Add single‑select ARIA semantics and keyboard/focus affordances

To meet the PR’s a11y objective, expose the set as a radio group and each option as a radio with a visible focus style.

-        <div className="grid grid-cols-5 lg:grid-cols-6 gap-1">
+        <div
+          className="grid grid-cols-5 lg:grid-cols-6 gap-1"
+          role="radiogroup"
+          aria-label={`${title} time slots`}
+        >
           {slots.map((slot) => (
             <button
               key={slot}
-              type="button"
+              type="button"
               onClick={() => setSelectedSlot(slot)}
-              className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all ${
+              role="radio"
+              aria-checked={selectedSlot === slot}
+              data-slot={slot}
+              className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${
                 selectedSlot === slot
                   ? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md scale-105'
                   : 'bg-blue-50 text-gray-900 hover:bg-blue-100 hover:scale-105'
               }`}
             >
               {slot}
             </button>
           ))}
         </div>

Consider adding arrow‑key navigation across items if desired; I can provide a tiny roving‑tabindex helper.



---

🏁 Script executed:

```shell
#!/bin/bash
# Verify file exists and check current code state
fd -t f "TimeSlotSelection.jsx" --exec cat {}

Length of output: 3055


🏁 Script executed:

#!/bin/bash
# Check if title variable is in scope - look at component signature and props
ast-grep --pattern $'export $_($_ {
  $$$
})'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for existing ARIA implementations in similar button/selection components
rg -n "role=" client/src/components --type=jsx -A2 -B2

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check tailwind config for focus-visible support
fd -t f "tailwind.config" && cat tailwind.config.* 2>/dev/null || echo "No tailwind config found"

Length of output: 24


🏁 Script executed:

#!/bin/bash
# Search for focus-visible usage in the codebase
rg -n "focus-visible" client/src --type=jsx --type=css

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and display package.json to check Tailwind version
fd -t f "package.json" -x cat {}

Length of output: 2476


🏁 Script executed:

#!/bin/bash
# Search for existing ARIA implementations using correct rg syntax
rg -n "role=" client/src/components -A2 -B2 | head -50

Length of output: 626


🏁 Script executed:

#!/bin/bash
# Check if focus-visible is used anywhere (without type flag)
rg -n "focus-visible" client/src

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any a11y or accessibility-related comments or patterns
rg -n "a11y|aria-|role=" client/src --max-count=20

Length of output: 6333


Add ARIA radio group semantics and focus affordances for accessibility

The time slot selection buttons need semantic radio group markup and visible focus indicators to meet a11y standards. Within renderSlotGroup(), the title parameter is available for the aria-label.

Update the slot group container and buttons:

-        <div className="grid grid-cols-5 lg:grid-cols-6 gap-1">
+        <div
+          className="grid grid-cols-5 lg:grid-cols-6 gap-1"
+          role="radiogroup"
+          aria-label={`${title} time slots`}
+        >
           {slots.map((slot) => (
             <button
               key={slot}
               type="button"
               onClick={() => setSelectedSlot(slot)}
+              role="radio"
+              aria-checked={selectedSlot === slot}
-              className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all ${
+              className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${
                 selectedSlot === slot
                   ? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md scale-105'
                   : 'bg-blue-50 text-gray-900 hover:bg-blue-100 hover:scale-105'
               }`}
             >
               {slot}
             </button>
           ))}
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="grid grid-cols-5 lg:grid-cols-6 gap-1">
{slots.map((slot) => (
<button
key={slot}
type="button"
onClick={() => setSelectedSlot(slot)}
className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all ${
selectedSlot === slot
? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md scale-105'
: 'bg-blue-50 text-gray-900 hover:bg-blue-100 hover:scale-105'
}`}
>
{slot}
</button>
))}
<div
className="grid grid-cols-5 lg:grid-cols-6 gap-1"
role="radiogroup"
aria-label={`${title} time slots`}
>
{slots.map((slot) => (
<button
key={slot}
type="button"
onClick={() => setSelectedSlot(slot)}
role="radio"
aria-checked={selectedSlot === slot}
className={`px-1.5 py-1.5 lg:py-2 rounded-md text-[10px] lg:text-xs font-semibold transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${
selectedSlot === slot
? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md scale-105'
: 'bg-blue-50 text-gray-900 hover:bg-blue-100 hover:scale-105'
}`}
>
{slot}
</button>
))}
</div>
🤖 Prompt for AI Agents
In client/src/components/Patient/BookAppointment/TimeSlotSelection.jsx around
lines 32–46, the slot grid needs ARIA radio group semantics and visible focus
affordances: add role="radiogroup" and aria-label using the renderSlotGroup
title on the container, and for each slot button set role="radio", aria-checked
based on selectedSlot, and tabIndex so the selected slot is focusable (0) and
others are -1; add an onKeyDown handler to toggle selection on Enter/Space to
support keyboard users; and include visible focus styles (e.g.
focus:outline-none plus a focus ring class) in the button className so focused
radios have a clear visible indicator.

Comment thread client/src/components/Patient/PatientDashboard/HeroSection.jsx Outdated
Comment on lines +72 to +95
<motion.div
key={feature.id}
className={`group relative flex items-center gap-2.5 rounded-lg border px-5 py-3 backdrop-blur-md transition-all duration-300 ${
hoveredPill === feature.id
? 'border-white/50 bg-white/30 shadow-xl'
: 'border-white/30 bg-white/15 shadow-lg'
}`}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
whileHover={{ scale: 1.05, y: -3 }}
whileTap={{ scale: 0.98 }}
onMouseEnter={() => handlePillHover(feature.id)}
onMouseLeave={() => handlePillHover(null)}
>
<div
className={`rounded-md p-1.5 transition-colors ${
hoveredPill === feature.id ? 'bg-white/30' : 'bg-white/15'
}`}
>
<Icon size={16} className="text-white" />
</div>
<span className="text-sm font-medium text-white">{feature.text}</span>
</motion.div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add keyboard accessibility to interactive pills.

The feature pills respond to mouse events but lack keyboard interaction support. This prevents keyboard-only users from interacting with them. Since the PR objectives mention "accessible keyboard navigation," these interactive elements should be fully accessible.

Apply this diff to make the pills keyboard accessible:

                    <motion.div
                      key={feature.id}
+                     role="button"
+                     tabIndex={0}
                      className={`group relative flex items-center gap-2.5 rounded-lg border px-5 py-3 backdrop-blur-md transition-all duration-300 ${
                        hoveredPill === feature.id
                          ? 'border-white/50 bg-white/30 shadow-xl'
                          : 'border-white/30 bg-white/15 shadow-lg'
-                      }`}
+                      } focus:outline-none focus:ring-2 focus:ring-white/50`}
                      initial={{ opacity: 0, x: -20 }}
                      animate={{ opacity: 1, x: 0 }}
                      transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
                      whileHover={{ scale: 1.05, y: -3 }}
                      whileTap={{ scale: 0.98 }}
                      onMouseEnter={() => handlePillHover(feature.id)}
                      onMouseLeave={() => handlePillHover(null)}
+                     onFocus={() => handlePillHover(feature.id)}
+                     onBlur={() => handlePillHover(null)}
+                     onKeyDown={(e) => {
+                       if (e.key === 'Enter' || e.key === ' ') {
+                         e.preventDefault();
+                         handlePillHover(feature.id);
+                       }
+                     }}
+                     aria-label={feature.text}
                    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<motion.div
key={feature.id}
className={`group relative flex items-center gap-2.5 rounded-lg border px-5 py-3 backdrop-blur-md transition-all duration-300 ${
hoveredPill === feature.id
? 'border-white/50 bg-white/30 shadow-xl'
: 'border-white/30 bg-white/15 shadow-lg'
}`}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
whileHover={{ scale: 1.05, y: -3 }}
whileTap={{ scale: 0.98 }}
onMouseEnter={() => handlePillHover(feature.id)}
onMouseLeave={() => handlePillHover(null)}
>
<div
className={`rounded-md p-1.5 transition-colors ${
hoveredPill === feature.id ? 'bg-white/30' : 'bg-white/15'
}`}
>
<Icon size={16} className="text-white" />
</div>
<span className="text-sm font-medium text-white">{feature.text}</span>
</motion.div>
<motion.div
key={feature.id}
role="button"
tabIndex={0}
className={`group relative flex items-center gap-2.5 rounded-lg border px-5 py-3 backdrop-blur-md transition-all duration-300 ${
hoveredPill === feature.id
? 'border-white/50 bg-white/30 shadow-xl'
: 'border-white/30 bg-white/15 shadow-lg'
} focus:outline-none focus:ring-2 focus:ring-white/50`}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.4 + index * 0.1 }}
whileHover={{ scale: 1.05, y: -3 }}
whileTap={{ scale: 0.98 }}
onMouseEnter={() => handlePillHover(feature.id)}
onMouseLeave={() => handlePillHover(null)}
onFocus={() => handlePillHover(feature.id)}
onBlur={() => handlePillHover(null)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handlePillHover(feature.id);
}
}}
aria-label={feature.text}
>
<div
className={`rounded-md p-1.5 transition-colors ${
hoveredPill === feature.id ? 'bg-white/30' : 'bg-white/15'
}`}
>
<Icon size={16} className="text-white" />
</div>
<span className="text-sm font-medium text-white">{feature.text}</span>
</motion.div>

Comment on lines +14 to +16
icon: MapPin,
title: 'Find Clinics Near you',
subtitle: 'Locate trusted clinics in your area',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor copy nit: capitalize “You” consistently

-      title: 'Find Clinics Near you',
+      title: 'Find Clinics Near You',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
icon: MapPin,
title: 'Find Clinics Near you',
subtitle: 'Locate trusted clinics in your area',
icon: MapPin,
title: 'Find Clinics Near You',
subtitle: 'Locate trusted clinics in your area',
🤖 Prompt for AI Agents
In client/src/components/Patient/PatientDashboard/QuickActions.jsx around lines
14 to 16, the title string "Find Clinics Near you" uses a lowercase "you";
update the title to "Find Clinics Near You" (capitalize "You") to maintain
consistent title casing across the UI, and save the file.

Comment on lines +10 to +16
const now = new Date();
const upcomingAppointments = appointments.filter((a) => new Date(a.date) >= now);
const pastAppointments = appointments.filter((a) => new Date(a.date) < now);

const displayAppointments = activeTab === 'upcoming' ? upcomingAppointments : pastAppointments;
const hasMore = displayAppointments.length > 3;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Upcoming/past classification is timezone-sensitive; use date+startTime and sort

Parsing date only (new Date(a.date)) treats 'YYYY-MM-DD' as UTC and ignores startTime. This misclassifies appointments around midnight and DST. Combine date+startTime and sort ascending.

-  const now = new Date();
-  const upcomingAppointments = appointments.filter((a) => new Date(a.date) >= now);
-  const pastAppointments = appointments.filter((a) => new Date(a.date) < now);
+  const now = new Date();
+  const toLocalDateTime = (a) =>
+    new Date(`${a.date}T${(a.startTime ?? '00:00').padStart(5, '0')}`);
+  const sorted = [...appointments].sort(
+    (a, b) => toLocalDateTime(a) - toLocalDateTime(b)
+  );
+  const upcomingAppointments = sorted.filter((a) => toLocalDateTime(a) >= now);
+  const pastAppointments = sorted.filter((a) => toLocalDateTime(a) < now);
 
-  const displayAppointments = activeTab === 'upcoming' ? upcomingAppointments : pastAppointments;
-  const hasMore = displayAppointments.length > 3;
+  const displayAppointments = activeTab === 'upcoming' ? upcomingAppointments : pastAppointments;
+  const visible = displayAppointments.slice(0, 3);
+  const hasMore = displayAppointments.length > visible.length;

And update the prop below to use visible:

-        <AppointmentList
-          appointments={displayAppointments.slice(0, 3)}
+        <AppointmentList
+          appointments={visible}
           activeTab={activeTab}
           onAppointmentClick={onAppointmentClick}
         />

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In client/src/components/Patient/PatientDashboard/RecentActivity.jsx around
lines 10 to 16, the code constructs Date objects from a.date only which treats
'YYYY-MM-DD' as UTC and ignores the appointment startTime; fix by constructing
Date objects from combined date+startTime (e.g. parse a.date and a.startTime
together into a single local Date for comparison), use those combined Date
values to split upcoming vs past, sort the resulting displayAppointments
ascending by their combined Date, and change the downstream prop named hasMore
to use visible instead.

Comment on lines 96 to 110
const appointmentDuration = schedule?.appointmentDuration || 30;
const [hours, minutes] = selectedSlot.split(':');
const startTime = new Date();
startTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);

const endTime = new Date(startTime);
endTime.setMinutes(endTime.getMinutes() + appointmentDuration);

const bookingData = {
doctorId,
date: selectedDate,
startTime: selectedSlot,
endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(endTime.getMinutes()).padStart(2, '0')}`,
endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(
endTime.getMinutes()
).padStart(2, '0')}`,
reason,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Avoid DST/timezone drift; compute endTime without Date() and fix schedule field mismatch

  • You derive endTime using a Date anchored to “now”, not selectedDate; coupled with DST, this can shift minutes/hours.
  • schedule uses duration elsewhere (DoctorInfoCard) but here uses appointmentDuration. Use a robust fallback.
-      const appointmentDuration = schedule?.appointmentDuration || 30;
-      const [hours, minutes] = selectedSlot.split(':');
-      const startTime = new Date();
-      startTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
-      const endTime = new Date(startTime);
-      endTime.setMinutes(endTime.getMinutes() + appointmentDuration);
+      const appointmentDuration =
+        (typeof schedule?.duration === 'number' ? schedule.duration : undefined) ??
+        (typeof schedule?.appointmentDuration === 'number' ? schedule.appointmentDuration : undefined) ??
+        30;
+      const [h, m] = selectedSlot.split(':').map((n) => parseInt(n, 10));
+      const endTotalMins = (h * 60 + m + appointmentDuration) % (24 * 60);
+      const endH = Math.floor(endTotalMins / 60);
+      const endMin = endTotalMins % 60;
+      const endTimeStr = `${String(endH).padStart(2, '0')}:${String(endMin).padStart(2, '0')}`;
 
       const bookingData = {
         doctorId,
         date: selectedDate,
         startTime: selectedSlot,
-        endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(
-          endTime.getMinutes()
-        ).padStart(2, '0')}`,
+        endTime: endTimeStr,
         reason,
         isTeleconsultation,
       };

I can add a unit test to cover DST boundaries (e.g., “fall back” date) to prove correctness.


I'll verify the review comment by examining the codebase structure and checking the specific concerns raised.


🏁 Script executed:

#!/bin/bash
# Find and examine BookAppointment.jsx around the specified lines
fd -e jsx -e js 'BookAppointment' --type f

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the context around lines 96-110 in BookAppointment.jsx
find . -name 'BookAppointment.jsx' -type f -exec cat {} \; | head -150 | tail -70

Length of output: 2071


🏁 Script executed:

#!/bin/bash
# Find DoctorInfoCard to check how it references schedule fields
fd -e jsx -e js 'DoctorInfoCard' --type f

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Search for duration field usage across the codebase
rg -n '\bduration\b' --type=jsx --type=js -C 2 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for appointmentDuration usage across the codebase
rg -n '\bappointmentDuration\b' --type=jsx --type=js -C 2 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check how selectedDate is used in BookAppointment
rg -n 'selectedDate' client/src/pages/patient/BookAppointment.jsx -B 5 -A 5

Length of output: 2212


🏁 Script executed:

#!/bin/bash
# Search for schedule object structure or API response definitions
find . -name '*.jsx' -o -name '*.js' -o -name '*.ts' -o -name '*.tsx' | xargs grep -l 'schedule\s*:' | head -20

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Look for any timezone or DST handling in the codebase
rg -n 'DST|timezone|setHours|getHours' --type=jsx --type=js --type=ts -C 2 | head -80

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check if there are any date/time utility functions
fd -e js -e jsx -e ts -e tsx 'date|time|util' --type f | xargs grep -l 'function.*date\|function.*time' | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get the full BookAppointment.jsx file to understand context
cat client/src/pages/patient/BookAppointment.jsx

Length of output: 7176


🏁 Script executed:

#!/bin/bash
# Search for DoctorInfoCard usage of schedule
find . -name 'DoctorInfoCard.jsx' -o -name 'DoctorInfoCard.js' -o -name 'DoctorInfoCard.tsx' | xargs cat 2>/dev/null

Length of output: 3666


🏁 Script executed:

#!/bin/bash
# Check schedule data fetching/API calls
rg -n 'schedule' client/src/pages/patient/BookAppointment.jsx -B 3 -A 3 | head -80

Length of output: 1707


🏁 Script executed:

#!/bin/bash
# Look for the schedule prop source
rg -n 'getSchedule|fetchSchedule|schedule.*=' --type=jsx --type=js -C 3 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check API response or data structures
find . -path '*/node_modules' -prune -o -name '*.json' -type f -print | xargs grep -l 'schedule\|appointment' 2>/dev/null | head -10

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Look for backend/API documentation or types
find . -type f \( -name '*.d.ts' -o -name '*types*.ts' -o -name '*types*.js' \) | xargs grep -l 'schedule' 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's an API handler that defines schedule fields
find . -name '*api*' -o -name '*route*' | grep -E '\.(js|ts|jsx|tsx)$' | xargs grep -l 'schedule' 2>/dev/null | head -10

Length of output: 33


🏁 Script executed:

#!/bin/bash
# Check the actual usage of endTime in bookingData
rg -n 'endTime' client/src/pages/patient/BookAppointment.jsx -B 5 -A 5

Length of output: 713


🏁 Script executed:

#!/bin/bash
# Look at how selectedSlot is computed/passed
rg -n 'selectedSlot' client/src/pages/patient/BookAppointment.jsx -B 3 -A 3 | head -50

Length of output: 1545


🏁 Script executed:

#!/bin/bash
# Check for any date calculations with selectedDate
rg -n 'selectedDate' client/src/pages/patient/BookAppointment.jsx -B 10 -A 10 | head -80

Length of output: 2807


🏁 Script executed:

#!/bin/bash
# Look for timezone or locale settings
rg -n 'timezone|locale|UTC|GMT|toLocaleDateString|toLocaleString' --type=jsx --type=js -C 2 | head -60

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check if Date() usage with setHours is a known pattern issue
rg -n 'new Date()' --type=jsx --type=js -A 3 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for any comments about time handling or DST
rg -n 'DST|daylight|saving|time.*zone|timezone' --type=jsx --type=js --type=ts -C 3 | head -60

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check if selectedDate is a Date object or a string
rg -n 'selectedDate.*=' client/src/pages/patient/BookAppointment.jsx -B 5 -A 5 | head -50

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Verify the exact structure and check for tests
find . -name '*.test.*' -o -name '*.spec.*' | xargs grep -l 'BookAppointment\|appointmentDuration\|endTime' 2>/dev/null | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's documentation or comments about time calculations
rg -n 'startTime|endTime' --type=jsx --type=js -B 2 -A 2 | head -100

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look for any existing time calculation helpers
fd -type f \( -name '*time*' -o -name '*date*' -o -name '*util*' \) | xargs grep -l 'endTime\|startTime\|duration' 2>/dev/null

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Check the structure of what gets sent as bookingData
rg -n 'bookingData' client/src/pages/patient/BookAppointment.jsx -A 15 | head -50

Length of output: 846


🏁 Script executed:

#!/bin/bash
# Verify DoctorInfoCard's usage of schedule fields
rg -n 'schedule\?.duration\|schedule\.duration' --type=jsx --type=js -C 3 | head -80

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for the appointment booking endpoint to understand expected payload
find . -path '*/node_modules' -prune -o -type f \( -name '*.js' -o -name '*.ts' -o -name '*.jsx' -o -name '*.tsx' \) -exec grep -l 'book.*appointment\|appointment.*book' {} \; | head -20

Length of output: 398


🏁 Script executed:

#!/bin/bash
# Get complete context of BookAppointment.jsx file focusing on state and schedule handling
grep -n 'schedule\|selectedDate\|selectedSlot\|endTime\|startTime' client/src/pages/patient/BookAppointment.jsx | head -60

Length of output: 1312


🏁 Script executed:

#!/bin/bash
# Check how the schedule object is structured when fetched
grep -n 'const.*schedule\|let.*schedule\|var.*schedule\|schedule.*=' client/src/pages/patient/BookAppointment.jsx -A 2 -B 2 | head -40

Length of output: 1041


🏁 Script executed:

#!/bin/bash
# Look for all Date instantiations in BookAppointment.jsx
grep -n 'new Date' client/src/pages/patient/BookAppointment.jsx -B 2 -A 2

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Check if selectedDate is actually being used for the booking
grep -n 'bookingData\|startTime\|endTime' client/src/pages/patient/BookAppointment.jsx -B 5 -A 5 | head -80

Length of output: 1009


🏁 Script executed:

#!/bin/bash
# Final comprehensive check: get the entire BookAppointment.jsx to understand the full flow
wc -l client/src/pages/patient/BookAppointment.jsx

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Show structure around lines 90-120 with context
sed -n '80,125p' client/src/pages/patient/BookAppointment.jsx

Length of output: 1371


🏁 Script executed:

#!/bin/bash
# Look for where schedule comes from (props, state, API)
sed -n '1,100p' client/src/pages/patient/BookAppointment.jsx | grep -E 'const.*schedule|props|useEffect|useState|fetch|axios'

Length of output: 991


🏁 Script executed:

#!/bin/bash
# Search for all schedule field accesses to understand the object structure
grep -oE 'schedule\?\.[\w]+' client/src/pages/patient/BookAppointment.jsx | sort -u

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check DoctorInfoCard for its schedule field usage
grep -oE 'schedule\?\.[\w]+|schedule\.[\w]+' client/src/components/DoctorInfoCard.jsx 2>/dev/null | sort -u

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Verify if both duration and appointmentDuration exist in same schedule object
rg -n 'schedule.*\(duration|appointmentDuration\)' --type=jsx --type=js

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check backend or API files for schedule response structure
find . -path '*/node_modules' -prune -o -type f -name '*schedule*' -o -name '*api*' | xargs cat 2>/dev/null | head -200

Length of output: 4812


🏁 Script executed:

#!/bin/bash
# Check if there are integration tests that show the schedule object structure
find . -type f \( -name '*.test.js' -o -name '*.test.jsx' -o -name '*.spec.js' \) -exec grep -l 'schedule' {} \; | xargs cat 2>/dev/null | head -150

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Confirm the exact path and examine full BookAppointment.jsx
find . -name 'BookAppointment.jsx' -type f -exec wc -l {} \; -exec cat {} \;

Length of output: 7227


🏁 Script executed:

#!/bin/bash
# Check how selectedDate is defined and used
grep -n 'selectedDate' client/src/pages/patient/BookAppointment.jsx | head -30

Length of output: 364


🏁 Script executed:

#!/bin/bash
# Understand the full flow: where schedule comes from and how it's used
grep -n 'useEffect\|useState\|useQuery' client/src/pages/patient/BookAppointment.jsx -A 5 | head -100

Length of output: 1554


🏁 Script executed:

#!/bin/bash
# Check for any timezone or DST issues mentioned in issues/comments
find . -type f -name '*.md' -o -name '*.txt' | xargs grep -i 'DST\|timezone\|daylight' 2>/dev/null | head -30

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for the schedule data source (API endpoint, mock data, etc.)
rg -n 'getSchedule|fetchSchedule|/schedule|GET.*schedule' --type=jsx --type=js --type=ts -C 2 | head -80

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Final verification: are there multiple fields for duration in schedule?
rg -n '"duration"|\'duration\'|duration:|appointmentDuration:' --type=js --type=jsx --type=json -C 2 | head -100

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Find backend controller for schedule endpoint
find ./server -name '*.js' -type f | xargs grep -l 'schedule\|appointmentDuration\|duration' | head -20

Length of output: 454


🏁 Script executed:

#!/bin/bash
# Check the appointment controller for booking logic
cat ./server/Controllers/appointmentController.js | head -300

Length of output: 9165


🏁 Script executed:

#!/bin/bash
# Check doctor controller for schedule endpoint
cat ./server/Controllers/doctorController.js | grep -A 20 'schedule\|duration' | head -100

Length of output: 1160


🏁 Script executed:

#!/bin/bash
# Look at the Doctor model to see schedule/duration fields
cat ./server/models/Users/Doctor.js | grep -A 50 'schedule\|duration' | head -80

Length of output: 762


🏁 Script executed:

#!/bin/bash
# Check publicapi.js to see what getDoctorSchedule returns
cat ./client/src/service/publicapi.js | grep -A 10 'getDoctorSchedule'

Length of output: 778


🏁 Script executed:

#!/bin/bash
# Search for any route definitions for schedule
find ./server -name '*.js' -type f | xargs grep -l 'route.*schedule\|/schedule' | head -10

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Look at the complete publicapi.js file
cat ./client/src/service/publicapi.js

Length of output: 1037


🏁 Script executed:

#!/bin/bash
# Search for duration field references in backend
grep -rn 'duration\|appointmentDuration' ./server --include='*.js' | head -50

Length of output: 1612


🏁 Script executed:

#!/bin/bash
# Check if there's any schema or model definition
find ./server -name '*.js' -type f | xargs grep -l 'Schema\|model' | xargs grep -l 'schedule' | head -5

Length of output: 202


🏁 Script executed:

#!/bin/bash
# Look for the exact appointments schema or response structure
find ./server/models -name '*.js' -type f -exec grep -l 'appointment\|schedule' {} \;

Length of output: 266


🏁 Script executed:

#!/bin/bash
# Get doctor schedule route/endpoint
find ./server -type f -name '*.js' | xargs grep -B 5 -A 15 'getDoctorSchedule\|doctor.*schedule' | head -100

Length of output: 6955


🏁 Script executed:

#!/bin/bash
# Check appointmentApiService to see how booking payload is sent
cat ./client/src/service/appointmentApiService.js | head -150

Length of output: 2164


🏁 Script executed:

#!/bin/bash
# Look for schedule in Appointment model if it exists
find ./server/models -name '*.js' -type f -exec cat {} \; | grep -B 5 -A 10 'endTime\|startTime\|duration' | head -100

Length of output: 1486


🏁 Script executed:

#!/bin/bash
# Final check: search for where endTime is validated/used on backend
grep -rn 'endTime' ./server --include='*.js' -B 3 -A 3 | head -80

Length of output: 6023


🏁 Script executed:

#!/bin/bash
# Search all uses of duration field in frontend
grep -rn 'schedule.*duration\|\.duration' ./client/src --include='*.jsx' --include='*.js' -B 2 -A 2

Length of output: 3302


🏁 Script executed:

#!/bin/bash
# Verify Schedule model completely
cat ./server/models/Clinic/Schedule.js

Length of output: 858


🏁 Script executed:

#!/bin/bash
# Check how selectedDate is formatted when sent
grep -rn 'setSelectedDate\|selectedDate' ./client/src/components/Patient/BookAppointment/BookingForm.jsx | head -40

Length of output: 407


🏁 Script executed:

#!/bin/bash
# Find BookingForm to see how selectedDate is set
cat ./client/src/components/Patient/BookAppointment/BookingForm.jsx | head -200

Length of output: 6725


🏁 Script executed:

#!/bin/bash
# Check if there are any timezone utilities or moment/dayjs usage
grep -rn "moment\|dayjs\|toLocaleString\|getTimezoneOffset" ./client/src --include='*.js' --include='*.jsx'

Length of output: 401


🏁 Script executed:

#!/bin/bash
# Verify if DST is actually an issue by checking date comparisons
grep -rn 'new Date()' ./client/src --include='*.jsx' --include='*.js' -B 3 -A 5 | head -60

Length of output: 5800


🏁 Script executed:

#!/bin/bash
# Check for any time calculation logic elsewhere
grep -rn 'setHours\|setMinutes\|getHours\|getMinutes' ./client/src --include='*.jsx' --include='*.js' -B 2 -A 2

Length of output: 6461


🏁 Script executed:

#!/bin/bash
# Confirm there's no "duration" field anywhere in Schedule setup
grep -rn 'duration:' ./server --include='*.js' | grep -v appointmentDuration | grep -v prescription

Length of output: 96


Fix schedule field mismatch and time calculation logic

Two issues need fixing:

  1. DoctorInfoCard.jsx line 61-65 uses schedule?.duration which doesn't exist—the schedule model only has appointmentDuration. Change to schedule?.appointmentDuration.

  2. BookAppointment.jsx lines 98-101 anchors the time calculation to today's date instead of the selected date. Use string-based time arithmetic instead:

-      const appointmentDuration = schedule?.appointmentDuration || 30;
-      const [hours, minutes] = selectedSlot.split(':');
-      const startTime = new Date();
-      startTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
-      const endTime = new Date(startTime);
-      endTime.setMinutes(endTime.getMinutes() + appointmentDuration);
+      const appointmentDuration = schedule?.appointmentDuration || 30;
+      const [h, m] = selectedSlot.split(':').map((n) => parseInt(n, 10));
+      const endTotalMins = (h * 60 + m + appointmentDuration) % (24 * 60);
+      const endH = Math.floor(endTotalMins / 60);
+      const endMin = endTotalMins % 60;
+      const endTimeStr = `${String(endH).padStart(2, '0')}:${String(endMin).padStart(2, '0')}`;

       const bookingData = {
         doctorId,
         date: selectedDate,
         startTime: selectedSlot,
-        endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(
-          endTime.getMinutes()
-        ).padStart(2, '0')}`,
+        endTime: endTimeStr,
         reason,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const appointmentDuration = schedule?.appointmentDuration || 30;
const [hours, minutes] = selectedSlot.split(':');
const startTime = new Date();
startTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
const endTime = new Date(startTime);
endTime.setMinutes(endTime.getMinutes() + appointmentDuration);
const bookingData = {
doctorId,
date: selectedDate,
startTime: selectedSlot,
endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(endTime.getMinutes()).padStart(2, '0')}`,
endTime: `${String(endTime.getHours()).padStart(2, '0')}:${String(
endTime.getMinutes()
).padStart(2, '0')}`,
reason,
const appointmentDuration = schedule?.appointmentDuration || 30;
const [h, m] = selectedSlot.split(':').map((n) => parseInt(n, 10));
const endTotalMins = (h * 60 + m + appointmentDuration) % (24 * 60);
const endH = Math.floor(endTotalMins / 60);
const endMin = endTotalMins % 60;
const endTimeStr = `${String(endH).padStart(2, '0')}:${String(endMin).padStart(2, '0')}`;
const bookingData = {
doctorId,
date: selectedDate,
startTime: selectedSlot,
endTime: endTimeStr,
reason,
🤖 Prompt for AI Agents
In client/src/pages/patient/BookAppointment.jsx around lines 96 to 110 and
client/src/components/DoctorInfoCard.jsx around lines 61 to 65, fix two issues:
replace any use of schedule?.duration with schedule?.appointmentDuration in
DoctorInfoCard.jsx to match the model, and in BookAppointment.jsx stop anchoring
start/end times to new Date() (today) — compute start and end purely from the
selectedDate and selectedSlot strings by combining the selectedDate with the
slot time (or perform time arithmetic on strings/ISO datetimes) so the booking
times reflect the chosen date; ensure the endTime calculation adds
appointmentDuration minutes to the start time derived from
selectedDate+selectedSlot and format startTime/endTime as HH:MM strings for
bookingData.

@aditya241104 aditya241104 merged commit 6644f39 into main Oct 28, 2025
3 of 5 checks passed
@aditya241104 aditya241104 deleted the refactor/bookappointment-ui branch October 28, 2025 10:42
@coderabbitai coderabbitai Bot mentioned this pull request Nov 15, 2025
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UI REDESIGN]

1 participant