Skip to content

Conversation

@dioo1461
Copy link
Contributor

@dioo1461 dioo1461 commented Feb 18, 2025

#️⃣ 연관된 이슈>

📝 작업 내용> 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

  • 홈 화면에 API를 연결했습니다.
  • 불러올 데이터가 없는 경우, Fallback 컴포넌트를 표시합니다.
image

확정되지 않은 일정

  • list의 오른쪽에 있는 논의 세부 정보는 아직 구현 중입니다.
image

지난 일정

image
  • 현재 백엔드 구현상 지난 일정 데이터를 받아올 때, 특정 연도에 대한 정보만을 받아올 수 있습니다.
  • 따라서 지난 일정의 Fallback 표시 로직은, 검색하려는 연도에 종속적으로 동작할 수밖에 없습니다.
  • 때문에 부득이하게 임의로 디자인을 약간 수정하였습니다. (연도 선택 control이 가려지지 않도록 변경, 배경 색 흰색으로 변경)
  • 추가로 그래픽을 svg로 임포트하니 좀 이상하게 나오는데, 원인 파악해보고 안되면 이미지로 넣어야 할 것 같습니다 .. (시계만 이래요)

🙏 여기는 꼭 봐주세요! > 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

segment control 컴포넌트에 value만을 넘기던 기존 구현에서

segmentOptions: {label: string, value: string}

을 넘기도록 구현이 변경되었습니다.
button에 표시될 문구는 label에, 실제로 탭 전환을 관리할 키 값은 value에 할당하면 됩니다.

Summary by CodeRabbit

  • New Features

    • Introduced new visual icons and dynamic schedule components featuring dedicated views for upcoming, ongoing, and past events.
    • Added improved fallback displays and enhanced pagination with interactive hover effects.
    • Implemented prefetching for faster schedule data retrieval.
  • Improvements

    • Refined date/time formatting for clearer scheduling information.
    • Streamlined navigation and routing for a smoother user experience.

@dioo1461 dioo1461 requested a review from hamo-o as a code owner February 18, 2025 09:45
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2025

Walkthrough

This PR makes numerous enhancements to the frontend. It introduces new SVG icon components (CheckGraphic and ClockGraphic) and refactors the SegmentControl to use objects for options. The shared-schedule layer now features new API endpoints, query keys, prefetch functions, and custom query hooks, along with Zod schemas and type definitions for upcoming, ongoing, and finished schedules. Additionally, new UI components for displaying finished, ongoing, and upcoming schedules have been implemented while obsolete unconfirmed/expired components have been removed. Updates were also made to pagination, routing, date utilities, and styling.

Changes

File(s) Change Summary
frontend/src/components/Icon/component/CheckGraphic.tsx, .../ClockGraphic.tsx, .../Icon/index.ts Added new React SVG icon components (CheckGraphic and ClockGraphic) with customizable props and exported them.
frontend/src/components/SegmentControl/ControlButton.tsx, .../SegmentControl.stories.tsx, .../SegmentControl/index.tsx Refactored SegmentControl: replaced simple string props with segmentOption objects including label/value pairs.
frontend/src/features/discussion/api/queries.ts, .../discussion/ui/DiscussionRank/index.tsx Minor formatting updates in discussion queries and updated segment control prop handling in DiscussionRank.
frontend/src/features/shared-schedule/api/index.ts, .../keys.ts, .../prefetch.ts, .../queries.ts, .../queryOptions.ts Introduced a shared schedule API module with new endpoints, query keys, prefetch functions, custom hooks, and query options for upcoming, ongoing, and finished schedules.
frontend/src/features/shared-schedule/model/finishedSchedules.ts, .../model/index.ts, .../ongoingSchedules.ts, .../upcomingSchedules.ts Added Zod schemas and TypeScript types for finished, ongoing, and upcoming schedules, and updated model exports with a new MeetingMethod type.
frontend/src/features/shared-schedule/ui/Fallbacks/FinishedFallback.tsx, .../Fallbacks/OngoingFallback.tsx, .../Fallbacks/UpcomingFallback.tsx, .../Fallbacks/index.css.ts Added new fallback components for finished, ongoing, and upcoming schedules along with a CSS module for fallback container styling.
frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleList.tsx, .../FinishedScheduleListItem.tsx, .../FinishedSchedules/index.tsx Developed components to list and display finished schedules and renamed expired components to FinishedSchedule.
frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleList.tsx, .../OngoingScheduleListItem.tsx, .../RecommendedSchedules.tsx, .../ScheduleDetails.css.ts, .../ScheduleDetails.tsx, .../index.css.ts, .../index.tsx, .../ongoingScheduleListItem.css.ts Added a suite of new ongoing schedule UI components, including list, details, recommendations, and updated styling; supports segmented control interaction and detailed views.
frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/* Removed components related to unconfirmed schedules.
frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx, .../UpcomingCarousel.tsx, .../index.tsx, .../scheduleCard.css.ts Updated UpcomingSchedules components to switch to schedule-based props with new DdayChip and MeetingDateTimeInfo, and adjusted styling variants.
frontend/src/hooks/usePagination.ts Introduced a custom usePagination hook for managing pagination state.
frontend/src/pages/HomePage/index.tsx, frontend/src/pages/UpcomingSchedulePage/index.css.ts, frontend/src/pages/UpcomingSchedulePage/index.tsx Updated pages to incorporate new schedule components, adjust layout styling, and remove obsolete props.
frontend/src/routes/_main/discussion/candidate/index.lazy.tsx, frontend/src/routes/_main/home/index.lazy.tsx, frontend/src/routes/_main/home/index.tsx Modified route paths and restructured home routes—including prefetching schedule queries—while removing an outdated home route file.
frontend/src/utils/date/date.ts, .../date/format.ts, .../date/time.ts, .../zod/index.ts Enhanced date utilities with new constants (e.g. DAY_IN_MILLISECONDS), functions (getDateTimeRangeString, getDday, formatDateToDdayString), additional time constants, and added zCoerceToDate for schema coercion.

Sequence Diagram(s)

sequenceDiagram
    participant U as User Interface
    participant OS as OngoingSchedules Component
    participant SC as SegmentControl
    participant OQL as useOngoingQuery Hook
    participant API as schedulesApi
    participant DS as ScheduleDetails Component
    participant DQ as useDiscussionQuery Hook

    U->>OS: Loads Ongoing Schedules screen
    OS->>SC: Render segment options (All, Created, Shared)
    SC->>OS: User selects a segment option
    OS->>OQL: Call hook with current page & segment option
    OQL->>API: Fetch ongoing schedules data
    API-->>OQL: Return schedule data
    OQL-->>OS: Provide schedule list
    OS->>U: Render OngoingScheduleList with schedule items
    U->>OS: Click on a schedule item
    OS->>DQ: Call useDiscussionQuery with discussionId
    DQ->>API: Fetch discussion details
    API-->>DQ: Return discussion details
    DQ-->>DS: Render detailed schedule info
Loading

Suggested reviewers

  • hamo-o

Poem

I’m a little rabbit, hopping with delight,
New icons and components shine so bright.
APIs and queries now dance in the code,
Segment controls changed, lightening our load.
With every update, our UI’s a treat—
Leaping through changes on nimble feet!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

@dioo1461 dioo1461 added the 🖥️ FE Frontend label Feb 18, 2025
@dioo1461 dioo1461 added this to the 5️⃣ 5차 스프린트 milestone Feb 18, 2025
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: 17

🔭 Outside diff range comments (3)
frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (1)

43-49: 🛠️ Refactor suggestion

Add accessibility attributes to the chevron button.

The button element lacks essential accessibility attributes such as aria-label and role.

Apply this diff to improve accessibility:

-      <button className={chevronButtonStyle({ latest })}>
+      <button 
+        aria-label="View schedule details"
+        className={chevronButtonStyle({ latest })}
+        role="button"
+      >
frontend/src/pages/UpcomingSchedulePage/index.tsx (1)

10-10: ⚠️ Potential issue

Replace mock data with real API data.

The component is using mock data ([{}, {}, {}, {}, {}]) instead of real schedule data. Consider using the new schedule API hooks for data fetching.

 const UpcomingSchedulePage = () => {
-  const schedules = [{}, {}, {}, {}, {}];
+  const { data: schedules } = useUpcomingQuery();
   return (
     <Flex
       className={containerStyle}
       direction='column'
       gap={700}
     > 
       <UpcomingSchedules>
         <Text typo='h2'>다가오는 일정</Text>
       </UpcomingSchedules>
-      <UpcomingScheduleList schedules={schedules} />
+      <UpcomingScheduleList schedules={schedules ?? []} />
     </Flex>
   );
 };

Also applies to: 20-20

frontend/src/utils/date/time.ts (1)

20-24: 🛠️ Refactor suggestion

Remove duplicate constant definition.

The MINUTE_IN_MILLISECONDS constant is now exported at the module level. Remove the local constant in getMinuteDiff and use the exported one.

Apply this diff to remove the duplicate constant:

 export const getMinuteDiff = (startTime: Date, endTime: Date): number => { 
-  const MINUTE_IN_MILLISECONDS = 60000;
   const diff = endTime.getTime() - startTime.getTime();
   return Math.floor(diff / MINUTE_IN_MILLISECONDS);
 };
🧹 Nitpick comments (29)
frontend/src/features/shared-schedule/ui/Fallbacks/index.css.ts (1)

5-8: Consider using a more specific style name.

While the style definition is clean and follows best practices by using theme variables, the name containerStyle is quite generic. Since this is specifically for a Fallback component (as indicated in the PR context), consider using a more descriptive name.

-export const containerStyle = style({
+export const fallbackContainerStyle = style({
   backgroundColor: vars.color.Ref.CoolGrey[50],
   borderRadius: vars.radius[700],
 });
frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (1)

54-75: Consider optimizing the new subcomponents.

The extracted components improve code organization. Consider these enhancements:

  1. Memoize components to prevent unnecessary re-renders
  2. Move to separate files if they grow in complexity

Example memoization:

-const DdayChip = ({ endDateTime, latest }: {
+const DdayChip = React.memo(({ endDateTime, latest }: {
   endDateTime: Date;
   latest: boolean;
-}) => (
+}) => (
   <Chip
     color={latest ? 'black' : 'coolGray'}
     radius='max'
     size='md'
     style={latest ? 'filled' : 'weak'}
   >
     {formatDateToDdayString(endDateTime)}
   </Chip>
-);
+));

-const MeetingDateTimeInfo = ({ startDateTime, endDateTime }: {
+const MeetingDateTimeInfo = React.memo(({ startDateTime, endDateTime }: {
   startDateTime: Date;
   endDateTime: Date;
-}) => (
+}) => (
   <Text color={vars.color.Ref.Netural[600]} typo='b2R'>
     {getDateTimeRangeString(startDateTime, endDateTime)}
   </Text>
-);
+));
frontend/src/components/SegmentControl/ControlButton.tsx (1)

12-18: Consider memoizing the destructured values.

While the current implementation is correct, consider memoizing the destructured values if they're used in multiple places or if the component re-renders frequently.

 const ControlButton = ({ segmentOption, segmentControlStyle }: ControlButtonProps ) => {
   const { selectedValue, handleSelect } = useSafeContext(SegmentControlContext);
-  const { label, value } = segmentOption;
+  const { label, value } = useMemo(() => segmentOption, [segmentOption]);

Also applies to: 27-27

frontend/src/components/SegmentControl/SegmentControl.stories.tsx (1)

31-43: Consider adding JSDoc comments to document the WithContent pattern.

The WithContent story demonstrates a complex use case. Consider adding JSDoc comments to explain the relationship between segmentOptions and SegmentControl.Content values.

+/**
+ * Demonstrates using SegmentControl with content panels.
+ * Each SegmentControl.Content's value must match a value in segmentOptions.
+ */
 export const WithContent = () => (
frontend/src/features/discussion/ui/DiscussionRank/index.tsx (2)

61-64: Consider separating labels from internal values.

Currently, the labels and values are identical. Consider using more maintainable internal values:

 segmentOptions={[
-  { label: '참가자 많은 순', value: '참가자 많은 순' },
-  { label: '빠른 시간 순', value: '빠른 시간 순' },
+  { label: '참가자 많은 순', value: 'most_participants' },
+  { label: '빠른 시간 순', value: 'earliest_time' },
 ]}

67-67: Update Content values to match the suggested internal values.

If you implement the suggested value changes above, update the Content values accordingly:

-<SegmentControl.Content value={'참가자 많은 순'}>
+<SegmentControl.Content value={'most_participants'}>
 
-<SegmentControl.Content value={'빠른 시간 순'}>
+<SegmentControl.Content value={'earliest_time'}>

Also applies to: 70-70

frontend/src/components/SegmentControl/index.tsx (2)

27-27: Consider adding validation for empty segmentOptions.

The current default value handling uses optional chaining but could benefit from explicit validation:

+if (segmentOptions.length === 0) {
+  throw new Error('SegmentControl requires at least one option');
+}
 const [selectedValue, setSelectedValue] = useState(defaultValue);

49-55: Consider using value as the key instead of composite key.

Since values should be unique within the options, consider using just the value as the key:

 {segmentOptions.map((segmentOption, idx) => (
   <ControlButton 
-    key={`${segmentOption.value}-${idx}`}
+    key={segmentOption.value}
     segmentControlStyle={style}
     segmentOption={segmentOption}
   />
 ))}
frontend/src/features/shared-schedule/ui/Fallbacks/FinishedFallback.tsx (2)

11-12: Consider using responsive dimensions

The fixed dimensions (height='41.375rem' width='83.25rem') might cause layout issues on different screen sizes. Consider using relative units or making the component more responsive.

-    height='41.375rem'
-    width='83.25rem'
+    height='100%'
+    width='100%'
+    minHeight='41.375rem'
+    maxWidth='83.25rem'

15-15: Move text to i18n/constants file

Consider moving the Korean text to a dedicated i18n or constants file for better maintainability and localization support.

-    <Text color={vars.color.Ref.Netural[700]} typo='h3'>지난 일정이 없어요</Text>
+    <Text color={vars.color.Ref.Netural[700]} typo='h3'>{t('schedule.finished.empty')}</Text>
frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1)

5-12: Add JSDoc comments for better documentation

Consider adding JSDoc comments to describe the schema properties and their purposes.

+/**
+ * Schema for an upcoming schedule
+ * @property {number} id - Unique identifier for the schedule
+ * @property {string} title - Title of the schedule
+ * @property {Date} startDateTime - Start date and time
+ * @property {Date} endDateTime - End date and time
+ * @property {'ONLINE' | 'OFFLINE'} meetingMethodOrLocation - Meeting method
+ * @property {string[]} participantPictureUrls - List of participant picture URLs
+ */
const UpcomingScheduleSchema = z.object({
  id: z.number(),
  title: z.string(),
  startDateTime: zCoerceToDate,
  endDateTime: zCoerceToDate,
  meetingMethodOrLocation: z.enum(['ONLINE', 'OFFLINE']),
  participantPictureUrls: z.array(z.string()),
});
frontend/src/features/shared-schedule/ui/Fallbacks/UpcomingFallback.tsx (1)

14-15: Consider using responsive dimensions.

The hardcoded dimensions (height='19.75rem' and width='83.25rem') might not adapt well to different screen sizes. Consider using relative units or responsive design patterns.

-    height='19.75rem'
-    width='83.25rem'
+    height='100%'
+    width='100%'
+    minHeight='19.75rem'
+    maxWidth='83.25rem'
frontend/src/features/shared-schedule/api/prefetch.ts (1)

9-11: Consider parameterizing pagination values.

The pagination parameters (page=1, size=10) are hardcoded. Consider making these configurable to support different use cases and improve reusability.

-export const prefetchOngoingSchedules = async (queryClient: QueryClient) => {
-  await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(1, 10, 'ALL'));
+export const prefetchOngoingSchedules = async (
+  queryClient: QueryClient,
+  page = 1,
+  size = 10,
+) => {
+  await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(page, size, 'ALL'));
 };

-export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
+export const prefetchFinishedSchedules = async (
+  queryClient: QueryClient,
+  page = 1,
+  size = 10,
+) => {
   await queryClient.prefetchQuery(
-    sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
+    sharedSchedulesQueryOptions.finished(page, size, new Date().getFullYear()),
   );
 };

Also applies to: 13-17

frontend/src/features/shared-schedule/ui/UpcomingSchedules/UpcomingCarousel.tsx (1)

17-23: Add error handling for empty schedules.

The component doesn't handle the case when schedules array is empty. Consider adding a check or fallback UI.

+  if (!schedules.length) {
+    return <UpcomingFallback />;
+  }
   {schedules.map((schedule, index) => (
     <ScheduleCard
       key={index}
       latest={false}
       schedule={schedule}
     />
   ))}
frontend/src/pages/UpcomingSchedulePage/index.css.ts (1)

7-13: Consider responsive design implications of fixed width.

The fixed width of 1284px might cause horizontal scrolling on smaller screens. Consider using a responsive approach:

  • Use max-width instead of width
  • Add media queries for different screen sizes

Example improvement:

-  width: 1284,
+  width: '100%',
+  maxWidth: 1284,
frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1)

5-12: Enhance validation for timeLeft field.

The timeLeft field could benefit from additional validation:

  • Add minimum value check (should be >= 0)
  • Consider using a more specific type (e.g., milliseconds, seconds)

Example improvement:

-  timeLeft: z.number(),
+  timeLeft: z.number().min(0).describe('Time left in milliseconds'),
frontend/src/features/shared-schedule/api/queries.ts (1)

7-9: Add error handling and retry configuration.

The query hooks could benefit from:

  1. Error type definitions for better error handling
  2. Retry configuration for failed requests
  3. Loading state handling

Example improvement for one hook:

 export const useUpcomingQuery = () => useQuery<UpcomingSchedulesResponse>(
   sharedSchedulesQueryOptions.upcoming,
+  {
+    retry: (failureCount, error) => {
+      if (error instanceof NetworkError) return failureCount < 3;
+      return false;
+    },
+    onError: (error) => {
+      // Handle error (e.g., show toast notification)
+    }
+  }
 );

Also applies to: 11-14, 16-19

frontend/src/routes/_main/home/index.tsx (1)

21-22: Extract magic numbers into constants.

The page size (6) is hard-coded. Consider:

  1. Moving it to a constants file
  2. Making it configurable based on screen size

Example improvement:

+const SCHEDULES_PER_PAGE = 6;
+
 queryClient.prefetchQuery(
-  sharedSchedulesQueryOptions.ongoing(1, 6, 'ALL')
+  sharedSchedulesQueryOptions.ongoing(1, SCHEDULES_PER_PAGE, 'ALL')
 );
frontend/src/features/shared-schedule/model/finishedSchedules.ts (1)

5-17: LGTM! Well-structured schemas with strong type safety.

The schemas provide excellent type safety and runtime validation. Consider making the shared schema exportable for reuse in other schedule types.

Apply this diff to make the shared schema reusable:

-const SharedEventDtoSchema = z.object({
+export const SharedEventDtoSchema = z.object({
   id: z.number(),
   startDateTime: zCoerceToDate,
   endDateTime: zCoerceToDate,
 });
frontend/src/features/shared-schedule/ui/OngoingSchedules/ongoingScheduleListItem.css.ts (1)

12-12: Consider using min-height for better content flexibility.

Using a fixed height might cause content overflow issues. Consider using min-height instead to accommodate varying content lengths while maintaining consistent spacing.

Apply this diff to improve content flexibility:

-    height: '5rem',
+    minHeight: '5rem',
frontend/src/features/shared-schedule/api/queryOptions.ts (1)

11-12: Extract time constants to avoid duplication.

The stale and cache times are duplicated across all query configurations. Consider extracting these as named constants for better maintainability.

+const STALE_TIME = 4 * MINUTE_IN_MILLISECONDS;
+const CACHE_TIME = 5 * MINUTE_IN_MILLISECONDS;

 export const sharedSchedulesQueryOptions = {
   upcoming: {
     queryKey: sharedScheduleQuerykeys.upcoming,
     queryFn: () => schedulesApi.getUpcomingSchedules(),
-    staleTime: 4 * MINUTE_IN_MILLISECONDS,
-    cacheTime: 5 * MINUTE_IN_MILLISECONDS, 
+    staleTime: STALE_TIME,
+    cacheTime: CACHE_TIME,
   },
   // Apply similar changes to ongoing and finished options

Also applies to: 17-18, 23-24

frontend/src/features/shared-schedule/ui/FinishedSchedules/index.tsx (1)

23-27: Add hover states to navigation icons.

The clickable navigation icons should have hover states for better user interaction.

+const iconStyle = {
+  transition: 'fill 0.2s',
+  '&:hover': {
+    fill: vars.color.Ref.Netural[700],
+  },
+};

 <ChevronLeft 
   clickable={true}
   fill={vars.color.Ref.Netural[400]} 
+  style={iconStyle}
   onClick={() => setCurrentYear(currentYear - 1)}
 />

Also applies to: 31-35

frontend/src/features/shared-schedule/ui/UpcomingSchedules/index.tsx (1)

19-20: Enhance loading and error states.

The current loading and error states are too basic. Consider using proper loading spinners and error components for better user experience.

-  if (isPending) return <div>pending...</div>;
-  if (!data || !data.data) return <div>No data available</div>;
+  if (isPending) return <LoadingSpinner size="lg" />;
+  if (!data || !data.data) return (
+    <ErrorState
+      message="일정을 불러올 수 없습니다"
+      action={<Button onClick={() => refetch()}>다시 시도</Button>}
+    />
+  );
frontend/src/components/Icon/component/ClockGraphic.tsx (1)

4-6: Consider handling style prop conflicts.

The spread operator for styles could potentially override the cursor style. Consider merging styles in reverse order:

-style={{ cursor: clickable ? "pointer": "default", ...rest.style }}
+style={{ ...rest.style, cursor: clickable ? "pointer": "default" }}
frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleList.tsx (1)

22-23: Enhance loading and empty states.

Consider using proper loading and empty state components for better user experience.

-if (isPending) return <div>pending...</div>;
-if (!data || data.ongoingDiscussions.length === 0) return <div>no data available</div>;
+if (isPending) return <LoadingSpinner />;
+if (!data || data.ongoingDiscussions.length === 0) return <OngoingFallback />;
frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleListItem.tsx (1)

41-44: Remove commented code.

Either implement the update notification feature or remove the commented code.

-      {/* {isUpdated && 
-      <Text color={vars.color.Ref.Primary[600]} typo='b3M'>
-        일정을 업데이트한 사람이 있어요!
-      </Text>} */}
frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleList.tsx (1)

26-31: Add accessibility attributes to Flex containers.

The Flex containers lack proper ARIA roles and labels for screen readers.

Add appropriate ARIA attributes:

 <Flex
   className={containerStyle}
   direction='column'
   gap={600}
   justify='space-between'
   width='full'
+  role="region"
+  aria-label="완료된 일정 목록"
 >
   <Flex
     direction='column'
     justify='flex-start'
     width='full'
+    role="list"
   >

Also applies to: 32-36

frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1)

18-18: Address the TODO comment about date type conversion.

The TODO comment indicates pending work on date type conversion.

Would you like me to help implement the date type conversion? I can provide a solution that aligns with the date utilities in your codebase.

frontend/src/components/Icon/component/CheckGraphic.tsx (1)

6-60: Consider optimizing the SVG for better performance.

While the SVG implementation is correct, consider using an SVG optimizer to:

  • Reduce path complexity
  • Remove unnecessary decimal places
  • Combine similar filter operations

You can use tools like SVGO to optimize the SVG code. Here's how to verify potential optimizations:

#!/bin/bash
# Install and run SVGO to analyze potential optimizations
npm install -g svgo
svgo --test frontend/src/components/Icon/component/CheckGraphic.tsx
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 211e47f and 6f1d038fc8dece8cdbc38a212a52810b64151256.

⛔ Files ignored due to path filters (2)
  • frontend/src/components/Icon/svg/check-graphic.svg is excluded by !**/*.svg
  • frontend/src/components/Icon/svg/clock-graphic.svg is excluded by !**/*.svg
📒 Files selected for processing (53)
  • frontend/src/components/Icon/component/CheckGraphic.tsx (1 hunks)
  • frontend/src/components/Icon/component/ClockGraphic.tsx (1 hunks)
  • frontend/src/components/Icon/index.ts (1 hunks)
  • frontend/src/components/SegmentControl/ControlButton.tsx (2 hunks)
  • frontend/src/components/SegmentControl/SegmentControl.stories.tsx (1 hunks)
  • frontend/src/components/SegmentControl/index.tsx (3 hunks)
  • frontend/src/features/discussion/api/queries.ts (1 hunks)
  • frontend/src/features/discussion/ui/DiscussionRank/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/api/index.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/keys.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/prefetch.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/queries.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/queryOptions.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/finishedSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/index.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/ExpiredScheduleList.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/index.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/FinishedFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/OngoingFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/UpcomingFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/index.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleList.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleListItem.tsx (3 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleList.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleListItem.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/index.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ongoingScheduleListItem.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/ScheduleDetails.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleList.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleListItem.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/index.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (2 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/UpcomingCarousel.tsx (2 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/scheduleCard.css.ts (2 hunks)
  • frontend/src/hooks/usePagination.ts (1 hunks)
  • frontend/src/pages/HomePage/index.tsx (2 hunks)
  • frontend/src/pages/UpcomingSchedulePage/index.css.ts (1 hunks)
  • frontend/src/pages/UpcomingSchedulePage/index.tsx (1 hunks)
  • frontend/src/routes/_main/discussion/candidate/index.lazy.tsx (1 hunks)
  • frontend/src/routes/_main/home/index.lazy.tsx (0 hunks)
  • frontend/src/routes/_main/home/index.tsx (1 hunks)
  • frontend/src/utils/date/date.ts (2 hunks)
  • frontend/src/utils/date/format.ts (4 hunks)
  • frontend/src/utils/date/time.ts (1 hunks)
  • frontend/src/utils/zod/index.ts (1 hunks)
💤 Files with no reviewable changes (7)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/index.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleListItem.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/ScheduleDetails.tsx
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/ExpiredScheduleList.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleList.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/index.tsx
  • frontend/src/routes/_main/home/index.lazy.tsx
✅ Files skipped from review due to trivial changes (4)
  • frontend/src/features/discussion/api/queries.ts
  • frontend/src/components/Icon/index.ts
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.css.ts
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleListItem.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Send PR Notification to Reviewers
🔇 Additional comments (29)
frontend/src/features/shared-schedule/ui/Fallbacks/index.css.ts (1)

1-3: LGTM! Well-structured imports.

Good practice using a centralized theme module for consistent styling across components.

frontend/src/features/shared-schedule/ui/UpcomingSchedules/scheduleCard.css.ts (1)

14-14: LGTM! Semantic improvement in variant naming.

The change from 'selected' to 'latest' better reflects the purpose of highlighting the most recent schedule, improving code readability and maintainability.

Also applies to: 37-37

frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (1)

13-16: LGTM! Props interface is now more data-driven.

The change from a simple selected boolean to a schedule object with latest flag improves type safety and makes the component's data requirements explicit.

frontend/src/components/SegmentControl/ControlButton.tsx (1)

4-4: LGTM! Props interface refactoring improves type safety.

The change from value: string to segmentOption: SegmentOption provides better type safety and clearer separation between display text and internal values.

Also applies to: 7-10

frontend/src/components/SegmentControl/SegmentControl.stories.tsx (1)

22-28: LGTM! Story updates clearly demonstrate the new API.

The story updates effectively showcase the new segmentOptions prop structure with clear examples of label/value pairs.

frontend/src/components/SegmentControl/index.tsx (1)

10-13: LGTM! Clear and well-structured SegmentOption interface.

The new interface provides a clear contract for segment options with explicit types for both label and value.

frontend/src/hooks/usePagination.ts (1)

12-17: LGTM!

The return object provides a clean interface with all necessary pagination controls.

frontend/src/features/shared-schedule/model/index.ts (2)

1-3: LGTM!

The exports are well-organized and follow a consistent naming pattern, making the code structure clear and maintainable.


5-5: LGTM!

The MeetingMethod type is well-defined with clear, self-documenting values.

frontend/src/features/shared-schedule/api/keys.ts (1)

3-7: LGTM!

The query keys are well-structured and follow React Query best practices:

  • Properly typed parameters
  • Consistent key structure for cache management
  • Efficient cache invalidation support
frontend/src/utils/zod/index.ts (1)

11-12: LGTM!

The date coercion utility is well-implemented:

  • Comprehensive union type covering all common date formats
  • Efficient use of Zod's pipe and coerce features
frontend/src/routes/_main/discussion/candidate/index.lazy.tsx (1)

13-15:

Details

❓ Verification inconclusive

Verify the impact of route path changes.

The route path has been updated to include the '_main' prefix. Please ensure that:

  1. All route references are updated accordingly
  2. Navigation components handle the new path correctly
  3. No broken links exist in the application

🏁 Script executed:

#!/bin/bash
# Search for potential references to the old route path
rg -i "discussion/candidate" --type ts --type tsx

Length of output: 77


Below is a revised review comment recommending further verification due to the error from the initial search command. Please re-run the check using the script below to ensure that all route references (both old and new) in both .ts and .tsx files have been updated correctly.


Verify that all route references reflect the new '_main' prefix.

The update to include the '_main' prefix appears correct for the route definition. However, please perform these verifications before merging:

  • Ensure that there are no lingering references to the old "discussion/candidate" path in the codebase.
  • Confirm that all navigation components and links use the updated path "/_main/discussion/candidate/".
  • Manually verify that no broken links exist after the path change.

To help with this, re-run the following script:

#!/bin/bash
echo "Searching for old route references 'discussion/candidate' in ts and tsx files"
rg -i "discussion/candidate" --glob "*.ts" --glob "*.tsx"

echo "Searching for new route references '/_main/discussion/candidate/' in ts and tsx files"
rg -i "/_main/discussion/candidate/" --glob "*.ts" --glob "*.tsx"

After confirming the outputs, please update the review accordingly.

frontend/src/features/shared-schedule/ui/OngoingSchedules/index.css.ts (1)

13-15: LGTM! Style changes improve layout and spacing

The addition of full-width segment control and space-between justification enhances the visual layout and content distribution.

Also applies to: 20-20

frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1)

14-16: LGTM! Well-structured schema implementation

The schema implementation is clean and type-safe, with good use of Zod features.

frontend/src/features/shared-schedule/ui/Fallbacks/OngoingFallback.tsx (3)

14-15: Consider using responsive dimensions (same as FinishedFallback)

The fixed dimensions might cause layout issues on different screen sizes. Consider using relative units or making the component more responsive.


18-18: Move text to i18n/constants file (same as FinishedFallback)

Consider moving the Korean text to a dedicated i18n or constants file for better maintainability and localization support.


6-6: LGTM! Good use of shared styles

The component properly utilizes shared styles through containerStyle import.

Also applies to: 11-11

frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1)

14-20: LGTM! Well-structured pagination response schema.

The pagination schema is well-defined with all necessary fields for handling paginated data.

frontend/src/pages/HomePage/index.tsx (1)

15-27: LGTM! Clean component structure.

The changes align well with the PR objectives. The component structure follows a logical order: upcoming, ongoing, and finished schedules.

frontend/src/features/shared-schedule/api/queryOptions.ts (1)

7-26: Well-structured query configurations!

The query options are well-organized with proper typing and separation of concerns. The stale/cache time configuration ensures efficient data freshness management.

frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (1)

8-10: Implement the commented interface.

The commented out interface suggests this component should receive a discussionId. Consider implementing this to properly connect with API data.

frontend/src/features/shared-schedule/ui/FinishedSchedules/index.tsx (1)

22-27: Implement year validation for navigation.

The TODO comment indicates missing year validation. Consider adding upper/lower bounds to prevent navigating to invalid years.

+const MIN_YEAR = 2020; // Define based on business requirements
+const MAX_YEAR = new Date().getFullYear();

 const FinishedSchedules = () => {
-  const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
+  const [currentYear, setCurrentYear] = useState(MAX_YEAR);
+
+  const handleYearChange = (delta: number) => {
+    const newYear = currentYear + delta;
+    if (newYear >= MIN_YEAR && newYear <= MAX_YEAR) {
+      setCurrentYear(newYear);
+    }
+  };

   // ...
-            onClick={() => setCurrentYear(currentYear - 1)}
+            onClick={() => handleYearChange(-1)}
frontend/src/features/shared-schedule/ui/UpcomingSchedules/index.tsx (1)

11-14: Well-implemented data fetching!

The switch from prop-based to query-based data fetching is well done. The use of the nullish coalescing operator for default empty array is a good practice.

frontend/src/components/Icon/component/ClockGraphic.tsx (1)

6-23: LGTM! Well-structured SVG implementation.

The SVG implementation is well-organized with proper accessibility attributes and gradient definitions.

frontend/src/features/shared-schedule/ui/OngoingSchedules/index.tsx (1)

40-40:

Details

❓ Verification inconclusive

Verify initial selectedDiscussionId.

The selectedDiscussionId is initialized to 1 without verifying if a discussion with that ID exists.


🏁 Script executed:

#!/bin/bash
# Check if discussion with ID 1 exists in the codebase
rg -A 5 "id: 1" --type ts

Length of output: 6751


Address initialization of selectedDiscussionId

The current initialization of selectedDiscussionId to 1 assumes a discussion with this ID exists. While our codebase search shows that discussion objects with id: 1 appear in various parts (e.g., story data for Checkbox, Toggle, DiscussionCalendar, and DiscussionRank), this static assignment may not reliably match the actual discussion list used in the OngoingSchedules component.

  • Recommendation: Consider dynamically selecting the initial discussion ID based on the available discussions. For example, default to the first discussion’s ID when the discussion list is loaded, instead of hardcoding it to 1.
  • Action: Verify that the discussion data used in OngoingSchedules indeed includes an entry with id: 1 at runtime. If not, update the initialization logic accordingly.
frontend/src/components/Icon/component/CheckGraphic.tsx (1)

2-4: LGTM! Well-structured props with sensible defaults.

The component follows React best practices by:

  • Using a dedicated props interface
  • Providing default values
  • Supporting prop spreading for flexibility
frontend/src/utils/date/date.ts (3)

3-5: LGTM! Good use of a named constant for time calculations.

Extracting the milliseconds-per-day value into a constant improves maintainability and reduces the risk of calculation errors.


224-238: LGTM! Clean implementation with good edge case handling.

The function correctly:

  • Handles same-day and different-day cases
  • Uses consistent time formatting
  • Provides a clear and readable output format

213-222: ⚠️ Potential issue

Fix the logical error in year comparison.

The isSameYear check is inverted, causing the wrong format to be used. The year should only be included when the years are different.

Apply this fix:

-  const isSameYear = startY !== endY;
+  const isSameYear = startY === endY;

Likely an incorrect or invalid review comment.

Comment on lines 3 to 5
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 parameter validation and explicit return type.

The hook should validate its input parameters and have an explicit return type for better type safety.

Apply this diff to add validation and type safety:

+interface PaginationHook {
+  currentPage: number;
+  onPageChange: (page: number) => void;
+  totalPages: number;
+}

 export const usePagination = (
   initialPage: number,
   totalPages: number,
-) => {
+): PaginationHook => {
+  if (totalPages < 0) {
+    throw new Error('totalPages must be non-negative');
+  }
+  if (initialPage < 1 || initialPage > totalPages) {
+    throw new Error('initialPage must be between 1 and totalPages');
+  }
📝 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 usePagination = (
initialPage: number,
totalPages: number,
) => {
interface PaginationHook {
currentPage: number;
onPageChange: (page: number) => void;
totalPages: number;
}
export const usePagination = (
initialPage: number,
totalPages: number,
): PaginationHook => {
if (totalPages < 0) {
throw new Error('totalPages must be non-negative');
}
if (initialPage < 1 || initialPage > totalPages) {
throw new Error('initialPage must be between 1 and totalPages');
}
// ... remaining hook implementation ...
}

Comment on lines +8 to +9
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 page validation and memoize the callback.

The page change handler should validate the new page number and be memoized to prevent unnecessary re-renders.

Apply this diff to add validation and memoization:

+import { useState, useCallback } from 'react';

-  const onPageChange = (page: number) => {
+  const onPageChange = useCallback((page: number) => {
+    if (page < 1 || page > totalPages) {
+      throw new Error('Page must be between 1 and totalPages');
+    }
     setCurrentPage(page);
-  };
+  }, [totalPages]);
📝 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 onPageChange = (page: number) => {
setCurrentPage(page);
};
import { useState, useCallback } from 'react';
// ... other code may be present above
const onPageChange = useCallback((page: number) => {
if (page < 1 || page > totalPages) {
throw new Error('Page must be between 1 and totalPages');
}
setCurrentPage(page);
}, [totalPages]);
// ... rest of the code

Comment on lines 8 to 20
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Rename component to match file name.

The component is named OngoingFallback but exists in UpcomingFallback.tsx. This mismatch could lead to confusion. Consider renaming the component to UpcomingFallback to maintain consistency.

-const OngoingFallback = () => (
+const UpcomingFallback = () => (
   <Flex
     align='center'
     className={containerStyle}
     direction='column'
     gap={600}
     height='19.75rem'
     width='83.25rem'
   >
     <CheckGraphic height={180} width={180} />
     <Text color={vars.color.Ref.Netural[700]} typo='h3'>아직 다가오는 일정이 없어요!</Text>
   </Flex>
 );
📝 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 OngoingFallback = () => (
<Flex
align='center'
className={containerStyle}
direction='column'
gap={600}
height='19.75rem'
width='83.25rem'
>
<CheckGraphic height={180} width={180} />
<Text color={vars.color.Ref.Netural[700]} typo='h3'>아직 다가오는 일정이 없어요!</Text>
</Flex>
);
const UpcomingFallback = () => (
<Flex
align='center'
className={containerStyle}
direction='column'
gap={600}
height='19.75rem'
width='83.25rem'
>
<CheckGraphic height={180} width={180} />
<Text color={vars.color.Ref.Netural[700]} typo='h3'>아직 다가오는 일정이 없어요!</Text>
</Flex>
);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid using array index as key.

Using array indices as React keys can lead to issues with component updates and reordering. Consider using a unique identifier from the schedule object.

-          key={index}
+          key={schedule.id} // Assuming schedule has an id field
📝 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
key={index}
key={schedule.id} // Assuming schedule has an id field

Comment on lines +7 to +15
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 boundary for graceful failure handling.

The Home component should be wrapped with an error boundary to handle rendering errors gracefully.

Example improvement:

+import { ErrorBoundary } from '@/components/ErrorBoundary';
+
 const Home = () => (
   <>
     <GlobalNavBar>
       <GlobalNavBar.MyCalendarLink />
       <GlobalNavBar.NewDiscussionLink />
     </GlobalNavBar>
+    <ErrorBoundary fallback={<HomePage.ErrorFallback />}>
       <HomePage />
+    </ErrorBoundary>
   </>
 );
📝 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 Home = () => (
<>
<GlobalNavBar>
<GlobalNavBar.MyCalendarLink />
<GlobalNavBar.NewDiscussionLink />
</GlobalNavBar>
<HomePage />
</>
);
import { ErrorBoundary } from '@/components/ErrorBoundary';
const Home = () => (
<>
<GlobalNavBar>
<GlobalNavBar.MyCalendarLink />
<GlobalNavBar.NewDiscussionLink />
</GlobalNavBar>
<ErrorBoundary fallback={<HomePage.ErrorFallback />}>
<HomePage />
</ErrorBoundary>
</>
);

Comment on lines +42 to +46
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 data existence check logic.

The current implementation only checks the first page with hardcoded parameters, which might miss data on other pages.

Consider checking total count instead:

-  if (queryClient.getQueryData<OngoingSchedulesResponse>(
-    sharedScheduleQuerykeys.ongoing(1, 6, 'ALL'),
-  )?.totalPages === 0)
+  const data = queryClient.getQueryData<OngoingSchedulesResponse>(
+    sharedScheduleQuerykeys.ongoing(1, 1, 'ALL'),
+  );
+  if (!data || data.totalCount === 0)
📝 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
if (queryClient.getQueryData<OngoingSchedulesResponse>(
sharedScheduleQuerykeys.ongoing(1, 6, 'ALL'),
)?.totalPages === 0)
return <OngoingFallback />;
const data = queryClient.getQueryData<OngoingSchedulesResponse>(
sharedScheduleQuerykeys.ongoing(1, 1, 'ALL'),
);
if (!data || data.totalCount === 0)
return <OngoingFallback />;

Comment on lines +57 to +58
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove or implement commented code.

The commented code for date formatting should either be implemented or removed.

Consider implementing the date formatting using the existing date utilities:

-      <Text color={vars.color.Ref.Red[500]} typo='b3M'>
-        {/* {`마감까지 ${getDday(dateRangeEnd)}일`} */}
-      </Text>
+      <Text color={vars.color.Ref.Red[500]} typo='b3M'>
+        {`마감까지 ${formatDateToDdayString(discussion.dateRangeEnd)}일`}
+      </Text>

Also applies to: 66-67

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 for link copy functionality.

The "링크 복사" (Copy Link) button lacks error handling for clipboard operations.

Add error handling:

-        <Button size='xl' style='borderless'>링크 복사</Button>
+        <Button
+          onClick={async () => {
+            try {
+              await navigator.clipboard.writeText(window.location.href);
+              // TODO: Add toast notification for success
+            } catch (error) {
+              // TODO: Add toast notification for error
+            }
+          }}
+          size='xl'
+          style='borderless'
+        >
+          링크 복사
+        </Button>
📝 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
<Button size='xl' style='borderless'>링크 복사</Button>
<Button
onClick={async () => {
try {
await navigator.clipboard.writeText(window.location.href);
// TODO: Add toast notification for success
} catch (error) {
// TODO: Add toast notification for error
}
}}
size='xl'
style='borderless'
>
링크 복사
</Button>

Comment on lines +90 to +103
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 input validation and optimize the D-day calculation.

The formatDateToDdayString function lacks input validation and could be more efficient.

Consider this improved implementation:

 export const formatDateToDdayString = (date: Date): string => {
+  if (!(date instanceof Date) || isNaN(date.getTime())) {
+    throw new Error('Invalid date provided');
+  }
   const diffDays = getDday(date);
-
-  if (diffDays === 0) return 'D-Day';
-  if (diffDays > 0) return `D-${diffDays}`;
-  return `D+${Math.abs(diffDays)}`;
+  return diffDays === 0 ? 'D-Day' : `D${diffDays > 0 ? '-' : '+'}${Math.abs(diffDays)}`;
 };
📝 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 formatDateToDdayString = (date: Date): string => {
const diffDays = getDday(date);
if (diffDays === 0) return 'D-Day';
if (diffDays > 0) return `D-${diffDays}`;
return `D+${Math.abs(diffDays)}`;
};
export const formatDateToDdayString = (date: Date): string => {
if (!(date instanceof Date) || isNaN(date.getTime())) {
throw new Error('Invalid date provided');
}
const diffDays = getDday(date);
return diffDays === 0 ? 'D-Day' : `D${diffDays > 0 ? '-' : '+'}${Math.abs(diffDays)}`;
};

💡 Verification agent

❓ Verification inconclusive

Add unit tests for the new D-day formatting function.

The new formatDateToDdayString function requires test coverage.

Would you like me to help create unit tests for this function? I can provide test cases covering various scenarios including edge cases.


🏁 Script executed:

#!/bin/bash
# Check for existing tests
rg -g "*.test.ts" "formatDateToDdayString"

Length of output: 42


Unit tests required for formatDateToDdayString

The new D-day formatting function currently lacks test coverage. Please add unit tests that cover the following scenarios:

  • Zero offset: When getDday(date) returns 0, ensure it outputs "D-Day".
  • Positive offset: When getDday(date) returns a positive value, verify it outputs "D-{n}" (e.g., "D-3").
  • Negative offset: When getDday(date) returns a negative value, verify it outputs "D+{n}" (e.g., "D+2").

Consider placing these tests in a new file at frontend/src/utils/date/format.test.ts.

Would you like assistance creating these test cases?

Comment on lines +240 to +244
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 timezone handling in D-day calculation.

The current implementation might have off-by-one errors near midnight due to timezone differences. Consider normalizing the dates to midnight in the local timezone.

Apply this improvement:

 export const getDday = (date: Date): number => {
-  const today = new Date();
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  const targetDate = new Date(date);
+  targetDate.setHours(0, 0, 0, 0);
-  const diff = date.getTime() - today.getTime();
+  const diff = targetDate.getTime() - today.getTime();
   return Math.floor(diff / DAY_IN_MILLISECONDS);
 };
📝 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 getDday = (date: Date): number => {
const today = new Date();
const diff = date.getTime() - today.getTime();
return Math.floor(diff / DAY_IN_MILLISECONDS);
};
export const getDday = (date: Date): number => {
const today = new Date();
today.setHours(0, 0, 0, 0);
const targetDate = new Date(date);
targetDate.setHours(0, 0, 0, 0);
const diff = targetDate.getTime() - today.getTime();
return Math.floor(diff / DAY_IN_MILLISECONDS);
};

Copy link
Contributor

@hamo-o hamo-o left a comment

Choose a reason for hiding this comment

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

오 저도 바꾸신대로 연도에 따라서 다르게 렌더링 하는 것이 좋을 것 같습니다. 수고하셨습니다!

Comment on lines 8 to 18
Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
https://tanstack.com/query/latest/docs/reference/QueryClient#queryclient
요 문서 참고해서 전역적으로 설정해주시면 좋을 것 같습니다.

Copy link
Contributor Author

@dioo1461 dioo1461 Feb 19, 2025

Choose a reason for hiding this comment

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

반영했습니다~
d4998d2
일단 문서에서 본 대로 staletime = Infinity로 설정했는데 이래도 괜찮나 싶긴 하네요
cacheTime만 제대로 설정해 주면 별 탈 없을 것 같긴 하지만요..

Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
로딩화면 디자인에서 주신 애니메이션으로 넣어주면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

맞다 .. 다음 PR에서 구현해보겠습니다

Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
일관성을 위해서 구현하신 훅을 사용해주시면 좋을 것 같습니다! (페이지네이션을 하는 부분에서 로직 수정이 있을 때 용이할 것 같습니다.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영했습니다~

Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
페이지네이션에서, 다음 페이지의 데이터를 prefetch 해오도록 처리하면 좋을 것 같아요.

Copy link
Contributor Author

@dioo1461 dioo1461 Feb 19, 2025

Choose a reason for hiding this comment

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

d4998d2
282dd36
반영했습니다~

@dioo1461 dioo1461 force-pushed the feature/fe/home-page branch from 6f1d038 to 797402a Compare February 19, 2025 09:48
@dioo1461 dioo1461 merged commit 0f66312 into dev Feb 19, 2025
1 check was pending
@dioo1461 dioo1461 deleted the feature/fe/home-page branch February 19, 2025 09:53
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: 4

🔭 Outside diff range comments (1)
frontend/src/utils/date/time.ts (1)

31-35: 🛠️ Refactor suggestion

Reuse the MINUTE_IN_MILLISECONDS constant.

The constant is defined twice. Reuse the exported constant to maintain DRY principle.

 export const getMinuteDiff = (startTime: Date, endTime: Date): number => { 
-  const MINUTE_IN_MILLISECONDS = 60000;
   const diff = endTime.getTime() - startTime.getTime();
   return Math.floor(diff / MINUTE_IN_MILLISECONDS);
 };
♻️ Duplicate comments (3)
frontend/src/utils/date/date.ts (1)

240-244: ⚠️ Potential issue

Consider timezone handling in D-day calculation.

The current implementation might have off-by-one errors near midnight due to timezone differences.

Apply this improvement:

 export const getDday = (date: Date): number => {
-  const today = new Date();
+  const today = new Date();
+  today.setHours(0, 0, 0, 0);
+  const targetDate = new Date(date);
+  targetDate.setHours(0, 0, 0, 0);
-  const diff = date.getTime() - today.getTime();
+  const diff = targetDate.getTime() - today.getTime();
   return Math.floor(diff / DAY_IN_MILLISECONDS);
 };
frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (2)

15-17: ⚠️ Potential issue

Replace hardcoded schedule items with dynamic data.

The component currently uses multiple hardcoded instances of RecommendedScheduleItem.

Apply this diff to use dynamic data:

-    <RecommendedScheduleItem />
-    <RecommendedScheduleItem />
-    <RecommendedScheduleItem />
+    {recommendations.map((recommendation) => (
+      <RecommendedScheduleItem
+        key={recommendation.id}
+        {...recommendation}
+      />
+    ))}

29-30: ⚠️ Potential issue

Replace hardcoded date and time with dynamic values.

The schedule item contains hardcoded date and time values.

Apply this diff to use dynamic data:

-      <Text typo='b2M'>12월 11일 목요일</Text>
-      <Text color={vars.color.Ref.Netural[700]} typo='b3R'>오전 11시 ~ 오후 2시 (3시간)</Text>
+      <Text typo='b2M'>{formatDate(date)}</Text>
+      <Text color={vars.color.Ref.Netural[700]} typo='b3R'>
+        {formatTimeRange(startTime, endTime)}
+      </Text>
🧹 Nitpick comments (12)
frontend/src/pages/HomePage/index.css.ts (1)

3-11: Consider using design tokens for spacing values.

The spacing values (marginTop: 80, gap: 120, paddingBottom: 126) appear to be magic numbers. Consider extracting these values into design tokens or constants for better maintainability and consistency.

+// Define spacing tokens at the top of the file or in a separate tokens file
+const SPACING = {
+  CONTAINER_TOP: 80,
+  SECTION_GAP: 120,
+  CONTAINER_BOTTOM: 126,
+} as const;

 export const containerStyle = style({
   width: 1284,
-  marginTop: 80,
+  marginTop: SPACING.CONTAINER_TOP,
   display: 'flex',
   flexDirection: 'column',
-  gap: 120,
+  gap: SPACING.SECTION_GAP,
-  paddingBottom: 126,
+  paddingBottom: SPACING.CONTAINER_BOTTOM,
 });
frontend/src/utils/zod/index.ts (1)

16-17: Consider adding error handling for time coercion.

While the implementation is correct, consider wrapping the parseTime transformation in a try-catch block to handle potential parsing errors gracefully.

 export const zCoerceToTime = z.string().time()
-  .transform((timeStr) => parseTime(timeStr));
+  .transform((timeStr) => {
+    try {
+      return parseTime(timeStr);
+    } catch (error) {
+      throw new z.ZodError([{
+        code: z.ZodIssueCode.custom,
+        path: [],
+        message: `Invalid time format: ${error.message}`
+      }]);
+    }
+  });
frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1)

14-20: Consider adding min/max constraints for pagination fields.

While the schema is well-structured, consider adding constraints for pagination fields to prevent potential issues.

 export const OngoingSchedulesResponseSchema = z.object({
-  currentPage: z.number(),
-  totalPages: z.number(),
+  currentPage: z.number().min(1),
+  totalPages: z.number().min(0),
   hasNext: z.boolean(),
   hasPrevious: z.boolean(),
   ongoingDiscussions: z.array(OngoingScheduleSchema),
 });
frontend/src/utils/date/time.ts (1)

63-82: Consider adding regex validation for time format.

While the current validation is thorough, consider adding a regex check for the time format before splitting the string.

 export const parseTime = (timeStr: string): Time => {
+  const TIME_FORMAT = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
+  if (!TIME_FORMAT.test(timeStr)) {
+    throw new Error('parseTime: Invalid time format');
+  }
+
   const parts = timeStr.trim().split(':');
-  if (parts.length < 2 || parts.length > 3) {
-    throw new Error('parseTime: Invalid time format');
-  }
frontend/src/features/discussion/model/invitation.ts (1)

5-15: Consider adding validation rules for better data integrity.

While the schema structure is good, consider adding these validations:

  • Title length constraints
  • Host name format validation
  • Reasonable duration range limits
  • Ensure dateRangeEnd is after dateRangeStart
  • Ensure timeRangeEnd is after timeRangeStart

Example enhancement:

 export const InvitationResponseSchema = z.object({
-  host: z.string(),
-  title: z.string(),
+  host: z.string().min(1).max(50),
+  title: z.string().min(1).max(100),
   dateRangeStart: zCoerceToDate,
   dateRangeEnd: zCoerceToDate,
   timeRangeStart: zCoerceToTime,
   timeRangeEnd: zCoerceToTime,
-  duration: z.number(),
+  duration: z.number().min(15).max(480), // 15 mins to 8 hours
   isFull: z.boolean(),
   requirePassword: z.boolean(),
 }).refine(
+  (data) => data.dateRangeEnd >= data.dateRangeStart,
+  { message: "End date must be after start date" }
+).refine(
+  (data) => data.timeRangeEnd > data.timeRangeStart,
+  { message: "End time must be after start time" }
 );
frontend/src/features/shared-schedule/api/prefetch.ts (1)

19-23: Consider parameterizing the hardcoded values.

The function uses hardcoded values (page: 1, size: 10, current year) which reduces flexibility and reusability.

-export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
+export const prefetchFinishedSchedules = async (
+  queryClient: QueryClient,
+  page = 1,
+  size = 10,
+  year = new Date().getFullYear(),
+) => {
   await queryClient.prefetchQuery(
-    sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
+    sharedSchedulesQueryOptions.finished(page, size, year),
   );
 };
frontend/src/features/shared-schedule/ui/UpcomingSchedules/index.tsx (1)

20-20: Enhance the loading state UI.

The current loading state shows a simple text message. Consider using a proper loading skeleton or spinner component for better user experience.

-  if (isPending) return <div>pending...</div>;
+  if (isPending) return <UpcomingSchedulesSkeleton />;
frontend/src/components/Icon/component/CheckGraphic.tsx (3)

2-4: Remove unused props.

The stroke and fill props are declared but not utilized in the SVG implementation. Consider removing these props to maintain a clean API surface.

-export const CheckGraphic = ({ clickable = false, className, width = 24, height = 24 , stroke = "white", fill = "white", ...rest }: IconProps) => {
+export const CheckGraphic = ({ clickable = false, className, width = 24, height = 24, ...rest }: IconProps) => {

6-6: Improve style handling.

The current style spread could override the cursor style. Consider merging styles in reverse order to ensure cursor behavior is preserved.

-<svg width={width} height={height || width} viewBox="0 0 180 180"  xmlns="http://www.w3.org/2000/svg" aria-label="check-graphic icon" fill="none" className={className} style={{ cursor: clickable ? "pointer": "default", ...rest.style }} {...rest}>
+<svg width={width} height={height || width} viewBox="0 0 180 180"  xmlns="http://www.w3.org/2000/svg" aria-label="check-graphic icon" fill="none" className={className} style={{ ...rest.style, cursor: clickable ? "pointer": "default" }} {...rest}>

7-59: Consider namespacing SVG IDs.

The SVG uses IDs like clip0_894_193 which could potentially clash with other SVGs in the application. Consider adding a component-specific prefix.

Example for one ID (apply similar pattern to others):

-<g clipPath="url(#clip0_894_193)">
+<g clipPath="url(#CheckGraphic_clip0)">
frontend/src/components/SegmentControl/index.tsx (1)

28-28: Consider more robust default value handling.

The current default value fallback could be improved to handle edge cases better.

-  defaultValue = segmentOptions[0]?.value ?? '',
+  defaultValue = segmentOptions?.[0]?.value ?? '',

Also consider validating that the defaultValue exists in segmentOptions to prevent invalid states.

frontend/src/features/shared-schedule/ui/OngoingSchedules/index.tsx (1)

52-55: Improve type safety by avoiding type assertion.

The TODO comment indicates a need for generic typing. Consider implementing proper type constraints instead of using type assertion.

-      onButtonHover={(value) => prefetchOngoingSchedules(
-        queryClient, 1, ONGOING_PAGE_SIZE, value as AttendType,
-      )}
+      onButtonHover={<T extends AttendType>(value: T) => prefetchOngoingSchedules(
+        queryClient, 1, ONGOING_PAGE_SIZE, value,
+      )}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1d038fc8dece8cdbc38a212a52810b64151256 and 797402a.

⛔ Files ignored due to path filters (2)
  • frontend/src/components/Icon/svg/check-graphic.svg is excluded by !**/*.svg
  • frontend/src/components/Icon/svg/clock-graphic.svg is excluded by !**/*.svg
📒 Files selected for processing (58)
  • frontend/src/components/Icon/component/CheckGraphic.tsx (1 hunks)
  • frontend/src/components/Icon/component/ClockGraphic.tsx (1 hunks)
  • frontend/src/components/Icon/index.ts (1 hunks)
  • frontend/src/components/Pagination/PaginationItem.tsx (2 hunks)
  • frontend/src/components/Pagination/index.tsx (2 hunks)
  • frontend/src/components/SegmentControl/ControlButton.tsx (1 hunks)
  • frontend/src/components/SegmentControl/SegmentControl.stories.tsx (1 hunks)
  • frontend/src/components/SegmentControl/index.tsx (2 hunks)
  • frontend/src/features/discussion/api/queries.ts (1 hunks)
  • frontend/src/features/discussion/model/invitation.ts (1 hunks)
  • frontend/src/features/discussion/ui/DiscussionRank/index.tsx (2 hunks)
  • frontend/src/features/shared-schedule/api/index.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/keys.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/prefetch.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/queries.ts (1 hunks)
  • frontend/src/features/shared-schedule/api/queryOptions.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/finishedSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/index.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/ExpiredScheduleList.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/index.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/FinishedFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/OngoingFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/UpcomingFallback.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/Fallbacks/index.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleList.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleListItem.tsx (3 hunks)
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleList.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleListItem.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/index.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ongoingScheduleListItem.css.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/ScheduleDetails.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleList.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleListItem.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/index.tsx (0 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (2 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/UpcomingCarousel.tsx (2 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/index.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/scheduleCard.css.ts (2 hunks)
  • frontend/src/hooks/usePagination.ts (1 hunks)
  • frontend/src/main.tsx (1 hunks)
  • frontend/src/pages/HomePage/index.css.ts (1 hunks)
  • frontend/src/pages/HomePage/index.tsx (1 hunks)
  • frontend/src/pages/UpcomingSchedulePage/index.css.ts (1 hunks)
  • frontend/src/pages/UpcomingSchedulePage/index.tsx (1 hunks)
  • frontend/src/routes/_main/discussion/candidate/index.lazy.tsx (1 hunks)
  • frontend/src/routes/_main/home/index.lazy.tsx (0 hunks)
  • frontend/src/routes/_main/home/index.tsx (1 hunks)
  • frontend/src/utils/date/date.ts (2 hunks)
  • frontend/src/utils/date/format.ts (3 hunks)
  • frontend/src/utils/date/time.ts (2 hunks)
  • frontend/src/utils/zod/index.ts (1 hunks)
💤 Files with no reviewable changes (7)
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/index.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/index.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleListItem.tsx
  • frontend/src/routes/_main/home/index.lazy.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/ScheduleDetails.tsx
  • frontend/src/features/shared-schedule/ui/UnConfirmedSchedules/UnconfirmedScheduleList.tsx
  • frontend/src/features/shared-schedule/ui/ExpiredSchedules/ExpiredScheduleList.tsx
🚧 Files skipped from review as they are similar to previous changes (27)
  • frontend/src/features/shared-schedule/ui/Fallbacks/index.css.ts
  • frontend/src/features/discussion/api/queries.ts
  • frontend/src/features/shared-schedule/ui/Fallbacks/OngoingFallback.tsx
  • frontend/src/components/Icon/index.ts
  • frontend/src/routes/_main/home/index.tsx
  • frontend/src/pages/UpcomingSchedulePage/index.tsx
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ongoingScheduleListItem.css.ts
  • frontend/src/utils/date/format.ts
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/index.tsx
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/scheduleCard.css.ts
  • frontend/src/features/shared-schedule/api/keys.ts
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.css.ts
  • frontend/src/features/shared-schedule/api/queryOptions.ts
  • frontend/src/routes/_main/discussion/candidate/index.lazy.tsx
  • frontend/src/components/Icon/component/ClockGraphic.tsx
  • frontend/src/pages/UpcomingSchedulePage/index.css.ts
  • frontend/src/features/shared-schedule/model/index.ts
  • frontend/src/features/shared-schedule/ui/Fallbacks/FinishedFallback.tsx
  • frontend/src/features/shared-schedule/api/index.ts
  • frontend/src/features/shared-schedule/model/upcomingSchedules.ts
  • frontend/src/features/discussion/ui/DiscussionRank/index.tsx
  • frontend/src/features/shared-schedule/ui/UpcomingSchedules/UpcomingCarousel.tsx
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleListItem.tsx
  • frontend/src/features/shared-schedule/api/queries.ts
  • frontend/src/features/shared-schedule/model/finishedSchedules.ts
  • frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleListItem.tsx
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/index.css.ts
🔇 Additional comments (30)
frontend/src/features/shared-schedule/ui/UpcomingSchedules/ScheduleCard.tsx (4)

7-8: LGTM! Clean interface changes.

The transition from selection-based to schedule-based props improves type safety and makes the component's requirements more explicit.

Also applies to: 10-10, 13-16


18-52: LGTM! Well-structured component with good separation of concerns.

The component effectively:

  • Uses data-driven rendering with the schedule prop
  • Delegates date/time formatting to specialized components
  • Applies conditional styling based on latest status

54-66: Consider accessibility for color contrast.

While the component is well-implemented, ensure that the color combinations (especially for the 'weak' style) maintain sufficient contrast ratios for accessibility.

Let's verify the color contrast ratios:

#!/usr/bin/env python3
from colorsys import rgb_to_hls
import math

def luminance(r, g, b):
    def adjust(value):
        value = value / 255
        return value / 12.92 if value <= 0.03928 else math.pow((value + 0.055) / 1.055, 2.4)
    return 0.2126 * adjust(r) + 0.7152 * adjust(g) + 0.0722 * adjust(b)

def contrast_ratio(l1, l2):
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

# Test common background/foreground combinations
color_pairs = [
    # Format: ((bg_r, bg_g, bg_b), (fg_r, fg_g, fg_b), "description")
    ((255, 255, 255), (115, 115, 115), "weak style - gray on white"),
    ((0, 0, 0), (255, 255, 255), "filled style - white on black")
]

print("Color Contrast Analysis:")
for bg, fg, desc in color_pairs:
    bg_luminance = luminance(*bg)
    fg_luminance = luminance(*fg)
    ratio = contrast_ratio(bg_luminance, fg_luminance)
    print(f"\n{desc}:")
    print(f"Contrast ratio: {ratio:.2f}")
    print(f"Meets WCAG AA for large text: {ratio >= 3}")
    print(f"Meets WCAG AA for small text: {ratio >= 4.5}")

68-75: LGTM! Clean and focused component.

The component has a single, clear responsibility and is well-typed. The use of the utility function for date formatting promotes consistency.

frontend/src/pages/HomePage/index.css.ts (1)

3-11:

Details

❓ Verification inconclusive

Verify the alignment of child components.

The removal of alignItems: 'center' (mentioned in the AI summary) could affect the alignment of child components, particularly the year selection control and schedule components. Please verify that this change doesn't adversely affect the layout.


🏁 Script executed:

#!/bin/bash
# Search for any flex alignment styles in related components that might compensate for this change
rg -t ts -t tsx "alignItems|align-items" frontend/src/pages/HomePage/

Length of output: 99


Action Required: Verify Visual Alignment Impact

The removal of alignItems: 'center' may affect how child components such as the year selection control and schedule components are laid out. The initial automated search encountered an issue with the file type filter (tsx), so we couldn't conclusively verify if any compensating alignment is applied elsewhere in the frontend/src/pages/HomePage/ folder.

Please run the following command or manually inspect the HomePage-related components to ensure that no alternate flex alignment styles are in place that would unintentionally alter the layout:

#!/bin/bash
# Search for any flex alignment styles in the HomePage directory
rg "alignItems|align-items" frontend/src/pages/HomePage/
frontend/src/utils/zod/index.ts (1)

13-14: LGTM! Well-structured date coercion implementation.

The union type and pipe approach for date coercion is clean and handles multiple input types effectively.

frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1)

5-12: LGTM! Well-structured schedule schema.

The schema is well-defined with appropriate types for all fields. Good use of zCoerceToDate for date fields.

frontend/src/features/discussion/model/invitation.ts (3)

1-3: LGTM! Clean import structure.

The imports are well-organized, using proper module aliasing and separating external from internal dependencies.


26-28: LGTM! Well-structured type exports.

The type definitions are correctly inferred from the schemas and follow a consistent naming convention.


17-24:

Details

❓ Verification inconclusive

Verify password attempt limit implementation.

The failedCount in the response suggests a password attempt limit feature. Ensure this is properly implemented in the backend and that the frontend handles the limit appropriately.


🏁 Script executed:

#!/bin/bash
# Search for password attempt limit handling in the codebase
rg -A 5 "failedCount|attempt.*limit|max.*attempts" --type ts

Length of output: 639


Password Attempt Limit Verification Required

The schema definitions themselves look correct; however, the failedCount field is currently only declared in the frontend’s join response schema with no corresponding handling logic found from our search. Please ensure that:

  • The backend implements the necessary password attempt limit logic.
  • The frontend properly interprets and handles the failedCount field, displaying appropriate feedback when the attempt limit is reached.
frontend/src/utils/date/date.ts (2)

3-5: LGTM! Well-structured constant definition.

The DAY_IN_MILLISECONDS constant is correctly defined and promotes code reusability.


224-238: LGTM! Well-implemented date-time range formatting.

The function has good optimization for same-day cases and clear logic for different-day cases.

frontend/src/features/shared-schedule/ui/Fallbacks/UpcomingFallback.tsx (1)

8-20: LGTM! Clean and semantic implementation of the fallback component.

The component follows best practices with:

  • Semantic layout using Flex component
  • Clear visual hierarchy with icon and text
  • Responsive design using 'full' width
frontend/src/main.tsx (1)

11-17: Review the staleTime configuration.

Setting staleTime: Infinity means queries will never become stale automatically. Consider if this is appropriate for your use case:

  • Pros: Reduces unnecessary refetches
  • Cons: Data might become outdated without manual invalidation

Consider setting a more appropriate staleTime based on your data update frequency. For example:

 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
-      staleTime: Infinity,
+      staleTime: 5 * 60 * 1000, // 5 minutes
     },
   },
 });
frontend/src/components/Icon/component/CheckGraphic.tsx (1)

65-65: LGTM!

The component name and displayName are properly set following React best practices.

frontend/src/hooks/usePagination.ts (1)

3-15: Add type safety, validation, and memoization.

The hook implementation needs improvements for better type safety and performance.

Apply this diff to enhance the hook:

+interface PaginationHook {
+  currentPage: number;
+  onPageChange: (page: number) => void;
+}

-export const usePagination = (
+export const usePagination = (
   initialPage: number,
+  totalPages: number,
-) => {
+): PaginationHook => {
+  if (initialPage < 1 || initialPage > totalPages) {
+    throw new Error('initialPage must be between 1 and totalPages');
+  }

   const [currentPage, setCurrentPage] = useState(initialPage);
 
-  const onPageChange = (page: number) => {
+  const onPageChange = useCallback((page: number) => {
+    if (page < 1 || page > totalPages) {
+      throw new Error('Page must be between 1 and totalPages');
+    }
     setCurrentPage(page);
-  };
+  }, [totalPages]);

   return {
     currentPage,
     onPageChange,
   };
 };
frontend/src/pages/HomePage/index.tsx (1)

17-30: LGTM! Clean component structure and proper layout implementation.

The changes effectively reorganize the home page layout with proper component hierarchy and navigation.

frontend/src/components/SegmentControl/SegmentControl.stories.tsx (1)

22-28: LGTM! Well-structured stories demonstrating the new API.

The stories effectively showcase the new segmentOptions prop structure with proper separation of labels and values.

Also applies to: 31-43

frontend/src/components/Pagination/PaginationItem.tsx (1)

14-14: LGTM! Well-structured hover functionality implementation.

The addition of the optional hover handler is clean and type-safe, following React best practices.

Also applies to: 17-22, 39-39

frontend/src/components/SegmentControl/ControlButton.tsx (1)

4-4: LGTM! Good refactor to use structured segment options.

The changes improve component flexibility by using a structured object for segment options while maintaining type safety.

Also applies to: 8-10, 13-17, 19-19, 24-24, 30-30

frontend/src/features/shared-schedule/ui/FinishedSchedules/FinishedScheduleList.tsx (2)

21-22: Enhance loading and error states with user-friendly messages.

The loading and error messages should be improved with proper styling and animations as discussed in previous feedback.


25-53: LGTM! Clean implementation of paginated schedule list.

The component structure is well-organized with proper data fetching, pagination, and list rendering.

frontend/src/components/SegmentControl/index.tsx (1)

10-13: LGTM! Well-structured interface changes.

The introduction of the SegmentOption interface and corresponding prop updates improve type safety and component flexibility.

Also applies to: 15-15, 21-21

frontend/src/components/Pagination/index.tsx (1)

16-16: LGTM! Good performance optimization.

The addition of onPageButtonHover prop enables prefetching on hover, which can improve the user experience by preloading data before page changes.

Also applies to: 23-23, 35-35

frontend/src/features/shared-schedule/ui/OngoingSchedules/OngoingScheduleList.tsx (2)

41-47: Replace array index with unique identifier as key prop.

Using array index as key prop can lead to rendering issues when the list changes.


53-55: LGTM! Efficient data prefetching implementation.

The implementation of prefetching data on pagination hover is a good optimization that will improve the user experience.

frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (2)

57-58: Remove or implement commented code.

The commented code for date formatting should either be implemented or removed.

Also applies to: 66-67


38-38: Add error handling for link copy functionality.

The "링크 복사" (Copy Link) button lacks error handling for clipboard operations.

frontend/src/features/shared-schedule/ui/OngoingSchedules/index.tsx (2)

43-46: Improve data existence check logic.

The current implementation only checks the first page with hardcoded parameters, which might miss data on other pages.


28-72: LGTM! Well-structured component with good separation of concerns.

The component effectively manages state and renders appropriate content based on data availability. The use of SegmentControl for filtering and the separation into Content component improves maintainability.

Comment on lines +213 to +222
export const getDateRangeString = (startDate: Date, endDate: Date): string => {
const { year: startY, month: startM, day: startD } = getDateParts(startDate);
const { year: endY, month: endM, day: endD } = getDateParts(endDate);

const isSameYear = startY !== endY;
const format = (year: number, month: number, day: number): string =>
isSameYear ? `${year}${month + 1}${day}일` : `${month + 1}${day}일`;

return `${format(startY, startM, startD)} ~ ${format(endY, endM, endD)}`;
};
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 incorrect year comparison logic.

There's a bug in the year comparison that will cause the year to always be displayed:

  • isSameYear = startY !== endY is the opposite of what it should be
  • The format function uses this incorrect condition

Apply this fix:

-  const isSameYear = startY !== endY;
+  const isSameYear = startY === endY;
   const format = (year: number, month: number, day: number): string =>
-    isSameYear ? `${year}년 ${month + 1}월 ${day}일` : `${month + 1}월 ${day}일`;
+    isSameYear ? `${month + 1}월 ${day}일` : `${year}년 ${month + 1}월 ${day}일`;
📝 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 getDateRangeString = (startDate: Date, endDate: Date): string => {
const { year: startY, month: startM, day: startD } = getDateParts(startDate);
const { year: endY, month: endM, day: endD } = getDateParts(endDate);
const isSameYear = startY !== endY;
const format = (year: number, month: number, day: number): string =>
isSameYear ? `${year}${month + 1}${day}일` : `${month + 1}${day}일`;
return `${format(startY, startM, startD)} ~ ${format(endY, endM, endD)}`;
};
export const getDateRangeString = (startDate: Date, endDate: Date): string => {
const { year: startY, month: startM, day: startD } = getDateParts(startDate);
const { year: endY, month: endM, day: endD } = getDateParts(endDate);
const isSameYear = startY === endY;
const format = (year: number, month: number, day: number): string =>
isSameYear ? `${month + 1}${day}일` : `${year}${month + 1}${day}일`;
return `${format(startY, startM, startD)} ~ ${format(endY, endM, endD)}`;
};

Comment on lines +6 to +23
export const prefetchUpcomingSchedules = async (queryClient: QueryClient) => {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.upcoming);
};

export const prefetchOngoingSchedules = async (
queryClient: QueryClient,
page: number,
size: number,
attendType: AttendType,
) => {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(page, size, attendType));
};

export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
await queryClient.prefetchQuery(
sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
);
};
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 for prefetch operations.

Consider wrapping the prefetch operations in try-catch blocks to handle potential network failures gracefully.

 export const prefetchUpcomingSchedules = async (queryClient: QueryClient) => {
+  try {
     await queryClient.prefetchQuery(sharedSchedulesQueryOptions.upcoming);
+  } catch (error) {
+    console.error('Failed to prefetch upcoming schedules:', error);
+    throw error;
+  }
 };

 export const prefetchOngoingSchedules = async (
   queryClient: QueryClient,
   page: number,
   size: number,
   attendType: AttendType,
 ) => {
+  try {
     await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(page, size, attendType));
+  } catch (error) {
+    console.error('Failed to prefetch ongoing schedules:', error);
+    throw error;
+  }
 };

 export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
+  try {
     await queryClient.prefetchQuery(
       sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
     );
+  } catch (error) {
+    console.error('Failed to prefetch finished schedules:', error);
+    throw error;
+  }
 };
📝 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 prefetchUpcomingSchedules = async (queryClient: QueryClient) => {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.upcoming);
};
export const prefetchOngoingSchedules = async (
queryClient: QueryClient,
page: number,
size: number,
attendType: AttendType,
) => {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(page, size, attendType));
};
export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
await queryClient.prefetchQuery(
sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
);
};
export const prefetchUpcomingSchedules = async (queryClient: QueryClient) => {
try {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.upcoming);
} catch (error) {
console.error('Failed to prefetch upcoming schedules:', error);
throw error;
}
};
export const prefetchOngoingSchedules = async (
queryClient: QueryClient,
page: number,
size: number,
attendType: AttendType,
) => {
try {
await queryClient.prefetchQuery(sharedSchedulesQueryOptions.ongoing(page, size, attendType));
} catch (error) {
console.error('Failed to prefetch ongoing schedules:', error);
throw error;
}
};
export const prefetchFinishedSchedules = async (queryClient: QueryClient) => {
try {
await queryClient.prefetchQuery(
sharedSchedulesQueryOptions.finished(1, 10, new Date().getFullYear()),
);
} catch (error) {
console.error('Failed to prefetch finished schedules:', error);
throw error;
}
};

Comment on lines +13 to +14
const { data, isPending } = useUpcomingQuery();
const schedules = data?.data ?? [];
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 for the query.

The query's error state is not handled. Consider adding error handling to show an appropriate error message to users.

-  const { data, isPending } = useUpcomingQuery();
+  const { data, isPending, error } = useUpcomingQuery();
   const schedules = data?.data ?? [];
+
+  if (error) {
+    return <ErrorMessage message="Failed to load upcoming schedules" />;
+  }
📝 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 { data, isPending } = useUpcomingQuery();
const schedules = data?.data ?? [];
const { data, isPending, error } = useUpcomingQuery();
const schedules = data?.data ?? [];
if (error) {
return <ErrorMessage message="Failed to load upcoming schedules" />;
}

Comment on lines +8 to +10
// interface RecommendedSchedulesProps {
// discussionId: number;
// }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Uncomment and implement the interface.

The commented interface suggests this component should receive a discussionId prop. This should be implemented to fetch the correct data.

Apply this diff to enable the interface:

-// interface RecommendedSchedulesProps {
-//   discussionId: number;
-// }
+interface RecommendedSchedulesProps {
+  discussionId: number;
+}

-const RecommendedSchedules = () => (
+const RecommendedSchedules = ({ discussionId }: RecommendedSchedulesProps) => (
📝 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 RecommendedSchedulesProps {
// discussionId: number;
// }
interface RecommendedSchedulesProps {
discussionId: number;
}
const RecommendedSchedules = ({ discussionId }: RecommendedSchedulesProps) => (
// ... rest of the component implementation
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🖥️ FE Frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants