Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reschedule calendar modal on inbox #518

Merged
merged 5 commits into from
Nov 4, 2024

Conversation

joaorceschini
Copy link
Contributor

No description provided.

Copy link

coderabbitai bot commented Nov 1, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces several enhancements to the InboxItems component, adding new state variables and functions to support rescheduling functionality. It includes the creation of a new Calendar component for date selection, a DateSelectionButton for user interaction, and a RescheduleCalendar component to manage the overall rescheduling interface. Additionally, the dueDate type in the CycleItem interface is modified for improved flexibility. These changes collectively enhance the user experience in managing inbox items and their due dates.

Changes

File Change Summary
apps/frontend/src/components/Inbox/InboxItems.tsx Added state variables for rescheduling, implemented new useEffect hooks, and modified event handlers.
apps/frontend/src/components/Inbox/RescheduleCalendar/Calendar.tsx Introduced a new Calendar component utilizing DayPicker for date selection.
apps/frontend/src/components/Inbox/RescheduleCalendar/DateSelectionButton.tsx Added DateSelectionButton component for date selection with props for label, icon, and date.
apps/frontend/src/components/Inbox/RescheduleCalendar/RescheduleCalendar.tsx Created RescheduleCalendar component to manage date selection and integrate calendar functionality.
apps/frontend/src/lib/@types/Items/Cycle.ts Updated dueDate property type in CycleItem interface from `string
apps/frontend/src/lib/store/cycle.store.ts Enhanced item updating logic in the store, introducing updateItemsInViewInbox for inbox updates.
apps/frontend/src/components/DropDownItems.tsx Modified TooltipContent to handle dueDate safely by converting it to ISO string if necessary.
apps/frontend/src/components/ThisWeek/CustomKanban.tsx Updated date handling in onDragEnd and handleSubmit to use Date objects instead of ISO strings.

Possibly related PRs

Suggested labels

ready to be merged

Suggested reviewers

  • deewakar-k
  • sajdakabir

Poem

🐰 In the inbox, items wait,
Rescheduling now feels great!
With a calendar to see,
Dates are chosen joyfully.
Click the button, make it right,
Hop along, what a delight! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (3)
apps/frontend/src/lib/@types/Items/Cycle.ts (1)

11-11: Document the expected date string format.

To ensure consistent date handling, consider adding JSDoc documentation to specify the expected format for date strings (e.g., ISO 8601).

+  /** Due date for the item. When provided as string, should be in ISO 8601 format */
   dueDate: Date | string | null
apps/frontend/src/components/Inbox/RescheduleCalendar/Calendar.tsx (1)

12-17: Consider adding prop validation for critical props.

While the props are well-structured, consider adding runtime validation for critical props using PropTypes or a schema validation library like Zod, especially if this component will be used across different parts of the application.

Example with PropTypes:

import PropTypes from 'prop-types';

Calendar.propTypes = {
  className: PropTypes.string,
  classNames: PropTypes.object,
  showOutsideDays: PropTypes.bool,
  selected: PropTypes.instanceOf(Date),
  onSelect: PropTypes.func
};
apps/frontend/src/components/Inbox/InboxItems.tsx (1)

Line range hint 1-238: Consider splitting component responsibilities

The InboxItems component is handling multiple responsibilities:

  • Inbox item rendering
  • Item status management
  • Rescheduling functionality
  • Modal management

This could make the component harder to maintain as it grows.

Consider:

  1. Extracting the rescheduling modal into a separate component
  2. Creating a custom hook for inbox item management
  3. Moving the date handling logic into a separate service

Example structure:

// useInboxItems.ts
export const useInboxItems = () => {
  // Item management logic
}

// RescheduleModal.tsx
export const RescheduleModal = ({ isOpen, onClose, itemId, onReschedule }) => {
  // Modal and calendar logic
}

// InboxItems.tsx
export const InboxItems = () => {
  const { items, handleDone } = useInboxItems()
  // Rendering logic only
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0c42e3c and de6281f.

📒 Files selected for processing (5)
  • apps/frontend/src/components/Inbox/InboxItems.tsx (6 hunks)
  • apps/frontend/src/components/Inbox/RescheduleCalendar/Calendar.tsx (1 hunks)
  • apps/frontend/src/components/Inbox/RescheduleCalendar/DateSelectionButton.tsx (1 hunks)
  • apps/frontend/src/components/Inbox/RescheduleCalendar/RescheduleCalendar.tsx (1 hunks)
  • apps/frontend/src/lib/@types/Items/Cycle.ts (1 hunks)
🔇 Additional comments (6)
apps/frontend/src/components/Inbox/RescheduleCalendar/DateSelectionButton.tsx (1)

1-11: LGTM! Well-structured props interface.

The component props are well-typed and have clear, descriptive names. The use of React.ElementType for the icon prop is the correct approach for component props.

apps/frontend/src/lib/@types/Items/Cycle.ts (1)

11-11: LGTM! Verify date handling across the application.

The type change to support Date | string | null is appropriate for the calendar rescheduling feature. However, ensure consistent date handling throughout the application.

Let's verify the date handling implementation:

✅ Verification successful

Date handling is consistent and well-managed across the codebase

The verification shows a robust and consistent date handling implementation:

  • Frontend uses TypeScript's Date objects with proper formatting via date-fns library functions
  • Backend consistently handles dates with proper UTC considerations and ISO string conversions
  • Date serialization/deserialization is handled properly:
    • Frontend converts to ISO strings when sending to backend (toISOString())
    • Backend properly parses string dates using new Date()
    • Clear date range handling with start/end of day/week/month utilities

The type change to Date | string | null aligns with the existing date handling patterns, where dates are manipulated as Date objects internally and serialized to ISO strings for storage/transmission.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for date parsing/formatting consistency
# Look for date handling patterns across the codebase

# Search for date parsing/formatting
rg -A 2 "new Date\(|Date\.|toISOString|toLocaleDateString" 

# Look for potential date-related utility functions
ast-grep --pattern 'function $_(date: Date) { $$$ }'

Length of output: 20141

apps/frontend/src/components/Inbox/RescheduleCalendar/Calendar.tsx (2)

1-10: LGTM! Clean imports and type definition.

The imports are well-organized, and the type definition properly extends DayPicker props for type safety.


69-71: LGTM! Clean component export.

The component export follows React best practices by setting the displayName property.

apps/frontend/src/components/Inbox/RescheduleCalendar/RescheduleCalendar.tsx (1)

30-49: 🛠️ Refactor suggestion

Reduce code duplication in date setter functions.

The date setter functions follow a similar pattern and could be optimized using a single handler.

Consider consolidating the functions:

+ const handleDateChange = (newDate: Date | null) => {
+   setDate(newDate)
+   setDateChanged(true)
+ }
+
-  const setToday = () => {
-    setNoDateFlag(false)
-    setDate(new Date())
-    setDateChanged(true)
-  }
+  const setToday = () => handleDateChange(new Date())
-  const setTomorrow = () => {
-    setNoDateFlag(false)
-    setDate(addDays(new Date(), 1))
-    setDateChanged(true)
-  }
+  const setTomorrow = () => handleDateChange(addDays(new Date(), 1))
-  const setNextWeek = () => {
-    setNoDateFlag(false)
-    setDate(addDays(new Date(), 7))
-    setDateChanged(true)
-  }
+  const setNextWeek = () => handleDateChange(addDays(new Date(), 7))
-  const setNoDate = () => {
-    setNoDateFlag(true)
-    setDate(null)
-    setDateChanged(true)
-  }
+  const setNoDate = () => handleDateChange(null)

Likely invalid or redundant comment.

apps/frontend/src/components/Inbox/InboxItems.tsx (1)

5-5: LGTM: Clean state management setup

The new imports and state variables are well-structured and properly typed for the rescheduling functionality.

Also applies to: 8-8, 21-26

Comment on lines +13 to +21
<button
className="hover-bg hover-text flex w-full cursor-pointer items-center justify-between gap-4 rounded-lg p-2"
onClick={onClick}
>
<span className="flex items-center gap-2">
<Icon size={24} /> {label}
</span>
<span className="text-sm">{formattedDate}</span>
</button>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance accessibility and keyboard interaction.

While the basic implementation is solid, consider these improvements for better accessibility and user experience:

 <button
   className="hover-bg hover-text flex w-full cursor-pointer items-center justify-between gap-4 rounded-lg p-2"
   onClick={onClick}
+  type="button"
+  aria-label={`Schedule for ${formattedDate}`}
+  onKeyDown={(e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
+      onClick();
+    }
+  }}
 >

Also consider making the icon size configurable through props instead of hardcoding it to 24:

- <Icon size={24} /> {label}
+ <Icon size={iconSize ?? 24} /> {label}

And update the props interface:

 }: {
   label: string
   icon: React.ElementType
   formattedDate: string
   onClick: () => void
+  iconSize?: number
 })

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

Comment on lines +19 to +60
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4 text-primary-foreground",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
"h-7 w-7 text-center bg-transparent p-1 text-secondary-foreground hover-text"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-secondary-foreground rounded-md w-8 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: cn(
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
: "[&:has([aria-selected])]:rounded-md"
),
day: cn(
"h-8 w-8 p-0 font-normal aria-selected:opacity-100 text-secondary-foreground hover-bg hover-text rounded-lg"
),
day_range_start: "day-range-start",
day_range_end: "day-range-end",
day_selected:
"bg-background-hover border border-border !text-foreground",
day_today: "bg-background-hover text-foreground",
day_outside:
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",

...classNames,
}}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting class names and enhancing accessibility.

While the styling implementation is comprehensive, consider these improvements:

  1. Extract commonly used class names into constants
  2. Add ARIA labels for better screen reader support
  3. Consider adding keyboard navigation instructions

Example refactor:

const CALENDAR_CLASSES = {
  BASE: "p-3",
  MONTHS: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
  // ... other class definitions
} as const;

// In the component
<DayPicker
  showOutsideDays={showOutsideDays}
  className={cn(CALENDAR_CLASSES.BASE, className)}
  aria-label="Date picker calendar"
  // ... other props
/>

Comment on lines +61 to +64
components={{
IconLeft: ({ ...props }) => <ChevronLeftIcon className="size-4" />,
IconRight: ({ ...props }) => <ChevronRightIcon className="size-4" />,
}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance navigation accessibility and remove unused props.

The navigation implementation can be improved:

  1. Remove unused props spread
  2. Add aria-labels for better accessibility

Apply this diff:

components={{
-  IconLeft: ({ ...props }) => <ChevronLeftIcon className="size-4" />,
-  IconRight: ({ ...props }) => <ChevronRightIcon className="size-4" />,
+  IconLeft: () => (
+    <ChevronLeftIcon className="size-4" aria-label="Previous month" />
+  ),
+  IconRight: () => (
+    <ChevronRightIcon className="size-4" aria-label="Next month" />
+  ),
}}
📝 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
components={{
IconLeft: ({ ...props }) => <ChevronLeftIcon className="size-4" />,
IconRight: ({ ...props }) => <ChevronRightIcon className="size-4" />,
}}
components={{
IconLeft: () => (
<ChevronLeftIcon className="size-4" aria-label="Previous month" />
),
IconRight: () => (
<ChevronRightIcon className="size-4" aria-label="Next month" />
),
}}

Comment on lines +21 to +29
export function RescheduleCalendar({
date,
setDate,
dateChanged,
setDateChanged,
...props
}: RescheduleCalendarProps) {
const [noDate, setNoDateFlag] = React.useState<boolean>(false)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize state management by removing redundant state.

The noDate state can be derived from the date prop, making it unnecessary to maintain as separate state.

Replace the state with a computed value:

-  const [noDate, setNoDateFlag] = React.useState<boolean>(false)
+  const noDate = date === null

Then update the setter functions to remove setNoDateFlag calls since they're no longer needed.

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

Comment on lines +12 to +19
interface RescheduleCalendarProps {
date: Date | null
setDate: (date: Date | null) => void
dateChanged: boolean
setDateChanged: (state: boolean) => void
icon?: React.ReactNode
[key: string]: any
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve type safety of the RescheduleCalendarProps interface.

The interface has two potential issues:

  1. The catch-all index signature [key: string]: any reduces type safety by allowing any additional props
  2. The icon prop is defined but never used in the component

Consider this improved interface definition:

 interface RescheduleCalendarProps {
   date: Date | null
   setDate: (date: Date | null) => void
   dateChanged: boolean
   setDateChanged: (state: boolean) => void
-  icon?: React.ReactNode
-  [key: string]: any
+  className?: string  // Add specific props you need instead of [key: string]: any
 }
📝 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
interface RescheduleCalendarProps {
date: Date | null
setDate: (date: Date | null) => void
dateChanged: boolean
setDateChanged: (state: boolean) => void
icon?: React.ReactNode
[key: string]: any
}
interface RescheduleCalendarProps {
date: Date | null
setDate: (date: Date | null) => void
dateChanged: boolean
setDateChanged: (state: boolean) => void
className?: string // Add specific props you need instead of [key: string]: any
}

Comment on lines +51 to +59
const todayFormatted = format(new Date(), "eee")
const tomorrowFormatted = format(addDays(new Date(), 1), "eee")
const nextWeekFormatted = format(addDays(new Date(), 7), "eee MMM dd")

const dueDateFormatted = noDate
? "No Date Assigned"
: date
? format(date, "eee MMM dd")
: "No Date Assigned"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve date formatting maintainability.

The date format strings should be constants, and the due date formatting logic can be simplified.

Consider these improvements:

+ const DATE_FORMATS = {
+   dayOfWeek: "eee",
+   fullDate: "eee MMM dd"
+ } as const
+
-  const todayFormatted = format(new Date(), "eee")
-  const tomorrowFormatted = format(addDays(new Date(), 1), "eee")
-  const nextWeekFormatted = format(addDays(new Date(), 7), "eee MMM dd")
+  const todayFormatted = format(new Date(), DATE_FORMATS.dayOfWeek)
+  const tomorrowFormatted = format(addDays(new Date(), 1), DATE_FORMATS.dayOfWeek)
+  const nextWeekFormatted = format(addDays(new Date(), 7), DATE_FORMATS.fullDate)

-  const dueDateFormatted = noDate
-    ? "No Date Assigned"
-    : date
-      ? format(date, "eee MMM dd")
-      : "No Date Assigned"
+  const dueDateFormatted = date 
+    ? format(date, DATE_FORMATS.fullDate)
+    : "No Date Assigned"
📝 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 todayFormatted = format(new Date(), "eee")
const tomorrowFormatted = format(addDays(new Date(), 1), "eee")
const nextWeekFormatted = format(addDays(new Date(), 7), "eee MMM dd")
const dueDateFormatted = noDate
? "No Date Assigned"
: date
? format(date, "eee MMM dd")
: "No Date Assigned"
const DATE_FORMATS = {
dayOfWeek: "eee",
fullDate: "eee MMM dd"
} as const
const todayFormatted = format(new Date(), DATE_FORMATS.dayOfWeek)
const tomorrowFormatted = format(addDays(new Date(), 1), DATE_FORMATS.dayOfWeek)
const nextWeekFormatted = format(addDays(new Date(), 7), DATE_FORMATS.fullDate)
const dueDateFormatted = date
? format(date, DATE_FORMATS.fullDate)
: "No Date Assigned"

Comment on lines +61 to +108
return (
<div
className="flex flex-row-reverse overflow-hidden rounded-lg bg-background text-secondary-foreground"
{...props}
>
<div className="p-4 text-sm">
<div className="mb-2 text-primary-foreground">
Due Date:{" "}
<strong className="text-foreground">{dueDateFormatted}</strong>
</div>
<div>
<DateSelectionButton
label="Today"
icon={CalendarDot}
formattedDate={todayFormatted}
onClick={setToday}
/>
<DateSelectionButton
label="Tomorrow"
icon={SquareChevronRightIcon}
formattedDate={tomorrowFormatted}
onClick={setTomorrow}
/>
<DateSelectionButton
label="Next Week"
icon={CalendarDots}
formattedDate={nextWeekFormatted}
onClick={setNextWeek}
/>
<DateSelectionButton
label="No Date"
icon={CalendarX}
formattedDate={""}
onClick={setNoDate}
/>
</div>
</div>
<Calendar
mode="single"
selected={date || undefined}
onSelect={(selectedDate) => {
setNoDateFlag(false)
setDate(selectedDate || null)
setDateChanged(true)
}}
initialFocus
/>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance accessibility and prop handling.

The component needs accessibility improvements and better prop handling for the Calendar component.

Consider these improvements:

  1. Add proper ARIA labels:
 <div
   className="flex flex-row-reverse overflow-hidden rounded-lg bg-background text-secondary-foreground"
+  role="dialog"
+  aria-label="Reschedule calendar"
   {...props}
 >
  1. Avoid undefined fallback:
 <Calendar
   mode="single"
-  selected={date || undefined}
+  selected={date}
   onSelect={(selectedDate) => {
  1. Add error boundary and loading states:
+  if (error) {
+    return <div>Error loading calendar</div>
+  }
+
+  if (loading) {
+    return <div>Loading calendar...</div>
+  }

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

Comment on lines +216 to +238
{reschedulingItemId !== null && (
<div>
<div
className="fixed inset-0 z-50 cursor-default bg-black/80"
role="button"
onClick={() => setReschedulingItemId(null)}
onKeyDown={(e) => {
if (e.key === "Escape" || e.key === "Esc") {
setReschedulingItemId(null)
}
}}
tabIndex={0}
></div>
<div className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-md bg-background shadow-lg">
<RescheduleCalendar
date={date}
setDate={setDate}
dateChanged={dateChanged}
setDateChanged={setDateChanged}
/>
</div>
</div>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance modal accessibility

The modal implementation needs accessibility improvements for better keyboard navigation and screen reader support.

Consider these accessibility enhancements:

  {reschedulingItemId !== null && (
-   <div>
+   <div role="dialog" aria-modal="true" aria-label="Reschedule Item">
      <div
        className="fixed inset-0 z-50 cursor-default bg-black/80"
        role="button"
+       aria-label="Close modal"
        onClick={() => setReschedulingItemId(null)}
        onKeyDown={(e) => {
          if (e.key === "Escape" || e.key === "Esc") {
            setReschedulingItemId(null)
          }
        }}
        tabIndex={0}
      />
-     <div className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-md bg-background shadow-lg">
+     <div 
+       className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-md bg-background shadow-lg"
+       role="document"
+     >
        <RescheduleCalendar
          date={date}
          setDate={setDate}
          dateChanged={dateChanged}
          setDateChanged={setDateChanged}
        />
      </div>
    </div>
  )}

Consider also:

  1. Adding a focus trap to keep focus within the modal
  2. Restoring focus to the trigger element when the modal closes
  3. Adding aria-describedby for additional context

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

Comment on lines 58 to 69
useEffect(() => {
if (dateChanged && reschedulingItemId && date) {
updateItem(session, { dueDate: date }, reschedulingItemId)
setReschedulingItemId(null)
setDateChanged(false)
setUpdateInbox(!updateInbox)
}
}, [date, updateItem, session, reschedulingItemId, dateChanged, updateInbox])

useEffect(() => {
fetchInbox(session)
}, [updateInbox, fetchInbox, session])
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential race conditions and unnecessary refetches in useEffect hooks

Several issues need attention:

  1. The first useEffect combines multiple state updates which could lead to race conditions
  2. Using updateInbox toggle in dependencies could cause extra renders
  3. The second useEffect might trigger unnecessary refetches

Consider this refactor:

  useEffect(() => {
    if (dateChanged && reschedulingItemId && date) {
-     updateItem(session, { dueDate: date }, reschedulingItemId)
-     setReschedulingItemId(null)
-     setDateChanged(false)
-     setUpdateInbox(!updateInbox)
+     updateItem(session, { dueDate: date }, reschedulingItemId).then(() => {
+       setReschedulingItemId(null)
+       setDateChanged(false)
+       fetchInbox(session)
+     })
    }
-  }, [date, updateItem, session, reschedulingItemId, dateChanged, updateInbox])
+  }, [date, updateItem, session, reschedulingItemId, dateChanged, fetchInbox])

-  useEffect(() => {
-    fetchInbox(session)
-  }, [updateInbox, fetchInbox, session])

This change:

  • Ensures state updates happen after the API call completes
  • Removes the need for the updateInbox toggle
  • Eliminates the second useEffect to prevent duplicate fetches
📝 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
useEffect(() => {
if (dateChanged && reschedulingItemId && date) {
updateItem(session, { dueDate: date }, reschedulingItemId)
setReschedulingItemId(null)
setDateChanged(false)
setUpdateInbox(!updateInbox)
}
}, [date, updateItem, session, reschedulingItemId, dateChanged, updateInbox])
useEffect(() => {
fetchInbox(session)
}, [updateInbox, fetchInbox, session])
useEffect(() => {
if (dateChanged && reschedulingItemId && date) {
updateItem(session, { dueDate: date }, reschedulingItemId).then(() => {
setReschedulingItemId(null)
setDateChanged(false)
fetchInbox(session)
})
}
}, [date, updateItem, session, reschedulingItemId, dateChanged, fetchInbox])

Comment on lines 138 to 147
const handleRescheduleCalendar = (
e: React.MouseEvent,
id: string,
dueDate: Date | string | null
) => {
e.stopPropagation()

setReschedulingItemId(id)
setDate(dueDate)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance type safety and error handling in handleRescheduleCalendar

While the function works, it could benefit from improved type safety and validation.

Consider this enhancement:

  const handleRescheduleCalendar = (
    e: React.MouseEvent,
    id: string,
-   dueDate: Date | string | null
+   dueDate: Date | string | null,
  ) => {
    e.stopPropagation()
+   
+   // Ensure string dates are parsed correctly
+   const parsedDate = dueDate instanceof Date
+     ? dueDate
+     : dueDate
+       ? new Date(dueDate)
+       : new Date()
+   
+   // Validate the parsed date
+   if (isNaN(parsedDate.getTime())) {
+     console.error('Invalid date provided')
+     return
+   }

    setReschedulingItemId(id)
-   setDate(dueDate)
+   setDate(parsedDate)
  }
📝 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 handleRescheduleCalendar = (
e: React.MouseEvent,
id: string,
dueDate: Date | string | null
) => {
e.stopPropagation()
setReschedulingItemId(id)
setDate(dueDate)
}
const handleRescheduleCalendar = (
e: React.MouseEvent,
id: string,
dueDate: Date | string | null,
) => {
e.stopPropagation()
// Ensure string dates are parsed correctly
const parsedDate = dueDate instanceof Date
? dueDate
: dueDate
? new Date(dueDate)
: new Date()
// Validate the parsed date
if (isNaN(parsedDate.getTime())) {
console.error('Invalid date provided')
return
}
setReschedulingItemId(id)
setDate(parsedDate)
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between de6281f and 9ddd039.

📒 Files selected for processing (2)
  • apps/frontend/src/components/Inbox/InboxItems.tsx (6 hunks)
  • apps/frontend/src/lib/store/cycle.store.ts (2 hunks)
🔇 Additional comments (5)
apps/frontend/src/components/Inbox/InboxItems.tsx (4)

5-5: LGTM: State management and imports are well-structured.

The new imports and state variables are properly organized and typed for the rescheduling functionality.

Also applies to: 8-8, 21-25


57-63: Skipping comment as past review is still valid.

A previous review comment already addresses the race conditions and state management issues in this useEffect hook.


132-141: Skipping comment as past review is still valid.

A previous review comment already addresses the type safety improvements needed in this function.


210-232: Skipping comment as past review is still valid.

A previous review comment already addresses the accessibility improvements needed in the modal implementation.

apps/frontend/src/lib/store/cycle.store.ts (1)

422-425: Consider handling race conditions in optimistic updates.

While the implementation correctly updates the inbox view state, there's a potential race condition between the optimistic update and the server response. If multiple updates are made in quick succession, the final state might not reflect the correct order of operations.

Let's verify the potential for race conditions:

Consider these approaches to handle race conditions:

  1. Implement request queuing to serialize updates
  2. Track pending updates and merge them with server responses
  3. Add version numbers to items and validate them server-side

Comment on lines +194 to +196
onClick={(e) =>
handleRescheduleCalendar(e, item._id, item.dueDate)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider memoizing the click handler for better performance.

The inline arrow function creates a new function instance on each render. Consider memoizing it with useCallback.

+ const handleCalendarClick = useCallback(
+   (id: string, dueDate: Date | string | null) => (e: React.MouseEvent) => {
+     handleRescheduleCalendar(e, id, dueDate)
+   },
+   [handleRescheduleCalendar]
+ )

  // In JSX:
- onClick={(e) => handleRescheduleCalendar(e, item._id, item.dueDate)}
+ onClick={handleCalendarClick(item._id, item.dueDate)}

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

Comment on lines +398 to +407
const updateItemsInViewInbox = (items: CycleItem[]) => {
// remove item from inbox if dueDate is set (not null)
if (updates.dueDate) {
return items.filter((item) => item._id !== id)
}
// otherwise, just update the item in inbox
return items.map((item) =>
item._id === id ? { ...item, ...updates } : item
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing type safety and edge cases in updateItemsInViewInbox.

The function correctly implements the inbox view update logic, removing items when a due date is set and updating them otherwise. However, consider these improvements:

  1. Add explicit handling for when dueDate is set to null
  2. Use type narrowing for better type safety

Consider this enhanced implementation:

 const updateItemsInViewInbox = (items: CycleItem[]) => {
-  // remove item from inbox if dueDate is set (not null)
-  if (updates.dueDate) {
+  // Remove item from inbox if dueDate is set (not null)
+  if (updates.dueDate !== undefined && updates.dueDate !== null) {
     return items.filter((item) => item._id !== id)
   }
-  // otherwise, just update the item in inbox
+  // Otherwise, just update the item in inbox
   return items.map((item) =>
     item._id === id ? { ...item, ...updates } : item
   )
 }
📝 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 updateItemsInViewInbox = (items: CycleItem[]) => {
// remove item from inbox if dueDate is set (not null)
if (updates.dueDate) {
return items.filter((item) => item._id !== id)
}
// otherwise, just update the item in inbox
return items.map((item) =>
item._id === id ? { ...item, ...updates } : item
)
}
const updateItemsInViewInbox = (items: CycleItem[]) => {
// Remove item from inbox if dueDate is set (not null)
if (updates.dueDate !== undefined && updates.dueDate !== null) {
return items.filter((item) => item._id !== id)
}
// Otherwise, just update the item in inbox
return items.map((item) =>
item._id === id ? { ...item, ...updates } : item
)
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (7)
apps/frontend/src/components/DropDownItems.tsx (2)

96-96: LGTM! Consider simplifying the null check.

The date handling changes look good and properly handle the new Date object type. However, the null coalescing operator (??) is redundant since an empty string is already provided as a fallback.

-                {getOverdueText(item.dueDate?.toISOString() || "") ?? ""}
+                {getOverdueText(item.dueDate?.toISOString() || "")}

Line range hint 1-1: Remove unused and incorrect import.

The format import from the 'path' module appears to be unused and incorrect. The 'path' module is for handling file paths, not date formatting.

-import { format } from "path"

If date formatting is needed, consider using a date formatting library like date-fns:

import { format } from 'date-fns'
apps/frontend/src/components/Inbox/InboxItems.tsx (2)

132-141: Add date validation in handleRescheduleCalendar.

The function should validate the dueDate parameter before setting it as state to prevent invalid dates.

Consider this enhancement:

  const handleRescheduleCalendar = (
    e: React.MouseEvent,
    id: string,
    dueDate: Date | null
  ) => {
    e.stopPropagation()
+   
+   // Validate date if provided
+   if (dueDate && isNaN(dueDate.getTime())) {
+     console.error('Invalid date provided')
+     return
+   }

    setReschedulingItemId(id)
    setDate(dueDate)
  }

210-232: Add focus management to the modal.

The modal should manage focus properly to enhance user experience.

Consider using a focus trap library like focus-trap-react:

+ import { useFocusTrap } from '@focus-trap/react'

  // Inside component:
+ const focusTrap = useFocusTrap()
+ const [lastFocusedElement, setLastFocusedElement] = useState<HTMLElement | null>(null)

  const handleRescheduleCalendar = (e: React.MouseEvent, id: string, dueDate: Date | null) => {
    e.stopPropagation()
+   setLastFocusedElement(document.activeElement as HTMLElement)
    setReschedulingItemId(id)
    setDate(dueDate)
  }

+ const handleModalClose = () => {
+   setReschedulingItemId(null)
+   lastFocusedElement?.focus()
+ }

  // In JSX:
  {reschedulingItemId !== null && (
    <div>
      <div
        className="fixed inset-0 z-50 cursor-default bg-black/80"
        role="button"
-       onClick={() => setReschedulingItemId(null)}
+       onClick={handleModalClose}
        onKeyDown={(e) => {
          if (e.key === "Escape" || e.key === "Esc") {
-           setReschedulingItemId(null)
+           handleModalClose()
          }
        }}
        tabIndex={0}
      />
-     <div className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-md bg-background shadow-lg">
+     <div 
+       ref={focusTrap}
+       className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-md bg-background shadow-lg"
+     >
apps/frontend/src/components/ThisWeek/CustomKanban.tsx (3)

Line range hint 79-94: LGTM! Consider adding error handling for date operations.

The change from ISO string to Date object is correct and aligns with the interface updates. However, consider adding error handling for date operations to make the code more robust.

-    const today = new Date()
+    try {
+      const today = new Date()
+      if (isNaN(today.getTime())) {
+        throw new Error('Invalid date')
+      }
+    } catch (error) {
+      console.error('Error creating date:', error)
+      // Fallback to current timestamp
+      const today = new Date()
+    }

286-286: LGTM! Consider adding error handling for date creation.

The change from ISO string to Date object is correct. However, consider adding error handling for date creation to ensure robustness.

-        data.dueDate = new Date()
+        try {
+          const date = new Date()
+          if (isNaN(date.getTime())) {
+            throw new Error('Invalid date')
+          }
+          data.dueDate = date
+        } catch (error) {
+          console.error('Error creating date:', error)
+          // Fallback to current timestamp
+          data.dueDate = new Date()
+        }

Line range hint 1-388: Consider extracting reusable components and hooks.

The implementation is solid, but there are opportunities to improve code organization and reusability:

  1. The drag-and-drop logic could be extracted into a custom hook (e.g., useDragAndDrop)
  2. The Card and Column components could be moved to separate files
  3. Date handling logic could be centralized in a utility function

These changes would make the code more maintainable and easier to test.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9ddd039 and dd326f0.

📒 Files selected for processing (4)
  • apps/frontend/src/components/DropDownItems.tsx (1 hunks)
  • apps/frontend/src/components/Inbox/InboxItems.tsx (7 hunks)
  • apps/frontend/src/components/ThisWeek/CustomKanban.tsx (2 hunks)
  • apps/frontend/src/lib/@types/Items/Cycle.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/frontend/src/lib/@types/Items/Cycle.ts

Comment on lines +57 to +63
useEffect(() => {
if (dateChanged && reschedulingItemId && date) {
updateItem(session, { dueDate: date, status: "todo" }, reschedulingItemId)
setReschedulingItemId(null)
setDateChanged(false)
}
}, [date, updateItem, session, reschedulingItemId, dateChanged])
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to the date update effect.

The useEffect should handle potential failures in the updateItem call to prevent silent failures and provide feedback to users.

Consider this enhancement:

  useEffect(() => {
    if (dateChanged && reschedulingItemId && date) {
-     updateItem(session, { dueDate: date, status: "todo" }, reschedulingItemId)
-     setReschedulingItemId(null)
-     setDateChanged(false)
+     updateItem(session, { dueDate: date, status: "todo" }, reschedulingItemId)
+       .then(() => {
+         setReschedulingItemId(null)
+         setDateChanged(false)
+       })
+       .catch((error) => {
+         // Handle error appropriately (e.g., show error toast)
+         console.error('Failed to update date:', error)
+       })
    }
  }, [date, updateItem, session, reschedulingItemId, dateChanged])

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between dd326f0 and 3393e4a.

📒 Files selected for processing (2)
  • apps/frontend/src/components/DropDownItems.tsx (2 hunks)
  • apps/frontend/src/utils/datetime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/frontend/src/components/DropDownItems.tsx
🔇 Additional comments (1)
apps/frontend/src/utils/datetime.ts (1)

88-88: Verify the impact of the breaking change.

The parameter type change from string | null to Date | null is a breaking change that requires updates to all callers.

Let's verify all usages of this function:

Comment on lines +88 to 96
export const getOverdueText = (dueDate: Date | null): string => {
if (!dueDate) {
return "" // or some default text like "No due date"
}

const due = new Date(dueDate)
const now = new Date()
const diffInDays = differenceInDays(now, due)
const diffInDays = differenceInDays(now, dueDate)

return `since ${diffInDays} ${diffInDays === 1 ? "day" : "days"}`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix overdue calculation and improve clarity.

The current implementation has several issues:

  1. The overdue calculation differenceInDays(now, dueDate) returns positive days for overdue items, which is counterintuitive. Users typically expect negative numbers for overdue items.
  2. The text "since X days" doesn't clearly indicate if an item is overdue or due in the future.

Consider this improved implementation:

 export const getOverdueText = (dueDate: Date | null): string => {
   if (!dueDate) {
-    return "" // or some default text like "No due date"
+    return "No due date"
   }
 
   const now = new Date()
-  const diffInDays = differenceInDays(now, dueDate)
+  const diffInDays = differenceInDays(dueDate, now)
 
-  return `since ${diffInDays} ${diffInDays === 1 ? "day" : "days"}`
+  if (diffInDays < 0) {
+    const days = Math.abs(diffInDays)
+    return `overdue by ${days} ${days === 1 ? "day" : "days"}`
+  }
+  if (diffInDays === 0) {
+    return "due today"
+  }
+  return `due in ${diffInDays} ${diffInDays === 1 ? "day" : "days"}`
 }

This implementation:

  • Returns negative days for overdue items by swapping the order of dates in differenceInDays
  • Provides clear text indicating whether items are overdue or upcoming
  • Handles the special case of items due today
  • Uses a more meaningful default text for null dates
📝 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
export const getOverdueText = (dueDate: Date | null): string => {
if (!dueDate) {
return "" // or some default text like "No due date"
}
const due = new Date(dueDate)
const now = new Date()
const diffInDays = differenceInDays(now, due)
const diffInDays = differenceInDays(now, dueDate)
return `since ${diffInDays} ${diffInDays === 1 ? "day" : "days"}`
export const getOverdueText = (dueDate: Date | null): string => {
if (!dueDate) {
return "No due date"
}
const now = new Date()
const diffInDays = differenceInDays(dueDate, now)
if (diffInDays < 0) {
const days = Math.abs(diffInDays)
return `overdue by ${days} ${days === 1 ? "day" : "days"}`
}
if (diffInDays === 0) {
return "due today"
}
return `due in ${diffInDays} ${diffInDays === 1 ? "day" : "days"}`
}

This was referenced Nov 18, 2024
This was referenced Dec 4, 2024
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.

2 participants