UI/UX: Improve booking flow, fix date bug, add responsiveness & stepper#72
Conversation
- 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughReplaces 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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Areas needing extra attention:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Comment |
There was a problem hiding this comment.
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
allFeaturesconstant duplicates thefeaturesstate 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 usersOptional: add aria-live for the toast so screen readers get notified.
- <motion.div + <motion.div + role="status" + aria-live="polite"
130-132: Loading state namingOptional: 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 textInclude 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 ATMark 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 keysUsing 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 techAdd 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 attributestyle 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 importuseEffect 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 shortOnly 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
📒 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
altattributes 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 fieldVerification confirms
schedule.appointmentDurationis 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 appliesschedule?.appointmentDuration.
| const formatDate = (dateString) => { | ||
| const date = new Date(dateString); | ||
| return date.toLocaleDateString('en-US', { | ||
| weekday: 'short', | ||
| month: 'short', | ||
| day: 'numeric', | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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.
| const formatDate = (dateString) => { | ||
| const date = new Date(dateString); | ||
| return date.toLocaleDateString('en-US', { | ||
| weekday: 'long', | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
the project is specifically for the india so i dont think so the time zone issue will be . am i right?
There was a problem hiding this comment.
@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:
-
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.
-
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.
-
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! 👍
| 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" | ||
| /> |
There was a problem hiding this comment.
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.
| <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> | ||
| ))} |
There was a problem hiding this comment.
🛠️ 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 -B2Length 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=cssLength 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 -50Length of output: 626
🏁 Script executed:
#!/bin/bash
# Check if focus-visible is used anywhere (without type flag)
rg -n "focus-visible" client/srcLength 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=20Length 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.
| <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.
| <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> |
There was a problem hiding this comment.
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.
| <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> |
| icon: MapPin, | ||
| title: 'Find Clinics Near you', | ||
| subtitle: 'Locate trusted clinics in your area', |
There was a problem hiding this comment.
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.
| 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.
| 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; | ||
|
|
There was a problem hiding this comment.
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.
| 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, |
There was a problem hiding this comment.
🧩 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 fLength 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 -70Length of output: 2071
🏁 Script executed:
#!/bin/bash
# Find DoctorInfoCard to check how it references schedule fields
fd -e jsx -e js 'DoctorInfoCard' --type fLength 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 -100Length 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 -100Length 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 5Length 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 -20Length 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 -80Length 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 -10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get the full BookAppointment.jsx file to understand context
cat client/src/pages/patient/BookAppointment.jsxLength 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/nullLength 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 -80Length 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 -100Length 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 -10Length 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 -20Length 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 -10Length 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 5Length 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 -50Length 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 -80Length 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 -60Length 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 -100Length 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 -60Length 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 -50Length 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 -10Length 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 -100Length 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/nullLength 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 -50Length 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 -80Length 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 -20Length 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 -60Length 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 -40Length 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 2Length 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 -80Length 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.jsxLength of output: 48
🏁 Script executed:
#!/bin/bash
# Show structure around lines 90-120 with context
sed -n '80,125p' client/src/pages/patient/BookAppointment.jsxLength 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 -uLength 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 -uLength 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=jsLength 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 -200Length 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 -150Length 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 -30Length 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 -100Length 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 -30Length 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 -80Length 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 -100Length 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 -20Length of output: 454
🏁 Script executed:
#!/bin/bash
# Check the appointment controller for booking logic
cat ./server/Controllers/appointmentController.js | head -300Length of output: 9165
🏁 Script executed:
#!/bin/bash
# Check doctor controller for schedule endpoint
cat ./server/Controllers/doctorController.js | grep -A 20 'schedule\|duration' | head -100Length 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 -80Length 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 -10Length of output: 94
🏁 Script executed:
#!/bin/bash
# Look at the complete publicapi.js file
cat ./client/src/service/publicapi.jsLength of output: 1037
🏁 Script executed:
#!/bin/bash
# Search for duration field references in backend
grep -rn 'duration\|appointmentDuration' ./server --include='*.js' | head -50Length 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 -5Length 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 -100Length of output: 6955
🏁 Script executed:
#!/bin/bash
# Check appointmentApiService to see how booking payload is sent
cat ./client/src/service/appointmentApiService.js | head -150Length 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 -100Length 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 -80Length 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 2Length of output: 3302
🏁 Script executed:
#!/bin/bash
# Verify Schedule model completely
cat ./server/models/Clinic/Schedule.jsLength 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 -40Length of output: 407
🏁 Script executed:
#!/bin/bash
# Find BookingForm to see how selectedDate is set
cat ./client/src/components/Patient/BookAppointment/BookingForm.jsx | head -200Length 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 -60Length 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 2Length 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 prescriptionLength of output: 96
Fix schedule field mismatch and time calculation logic
Two issues need fixing:
-
DoctorInfoCard.jsx line 61-65 uses
schedule?.durationwhich doesn't exist—the schedule model only hasappointmentDuration. Change toschedule?.appointmentDuration. -
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.
| 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.
Description
Closes #69. Related: #60.
New Screenshots
Desktop :

Mobile:
Summary by CodeRabbit
New Features
Improvements
UI