Skip to content

feat: 달력 월별 조회 및 감정일기 날짜별 조회 API 구현 - #98

Merged
evenif99 merged 2 commits into
devfrom
feature/calendar-daily-record-api
Jun 22, 2026
Merged

feat: 달력 월별 조회 및 감정일기 날짜별 조회 API 구현#98
evenif99 merged 2 commits into
devfrom
feature/calendar-daily-record-api

Conversation

@SG-Develope

@SG-Develope SG-Develope commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

기능구현

GET /api/calendar — 월별 감정일기·사건 요약 반환 (달력 마킹용)
GET /api/diary — 특정 날짜 감정일기 목록 반환
GET /api/disputes — date·status 쿼리 파라미터 필터 추가

Summary by CodeRabbit

릴리스 노트

  • New Features
    • 캘린더에서 선택한 연/월의 감정일기·분쟁을 조회해 날짜별로 표시
    • “새 일기” 버튼으로 일기 작성 페이지로 이동
    • 감정/분쟁 아이콘에 개수 배지 표시
  • Bug Fixes
    • 일기/분쟁 목록의 로딩·오류·빈 상태가 실제 데이터 기준으로 반영
  • Style
    • 캘린더 오버레이 및 카드/아이콘 정렬·간격 개선
    • 반응형 스케일링 값 일관화

@SG-Develope SG-Develope self-assigned this Jun 20, 2026
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
talky-owl Ready Ready Preview, Comment Jun 22, 2026 2:11am

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f60335a5-8562-4701-b640-ac6d315b08ab

📥 Commits

Reviewing files that changed from the base of the PR and between 3f758cf and ba7c079.

📒 Files selected for processing (4)
  • src/app/api/calendar/route.ts
  • src/app/api/diary/route.ts
  • src/components/calendar/RecordList.tsx
  • src/components/diary/EmotionDiaryList.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/diary/EmotionDiaryList.tsx
  • src/components/calendar/RecordList.tsx
  • src/app/api/diary/route.ts
  • src/app/api/calendar/route.ts

📝 Walkthrough

Walkthrough

캘린더 월별 레코드, 날짜별 일기, 날짜별 분쟁 조회 API 엔드포인트를 신규 추가하고, 대응하는 React Query 훅 및 API 함수를 구현했습니다. 캘린더 뷰, 기록 목록, 감정일기 목록 컴포넌트를 더미 데이터에서 실데이터로 전환했으며, Prisma 스키마의 enum 값 2개도 변경되었습니다.

Changes

캘린더·일기·분쟁 실데이터 연동

Layer / File(s) Summary
캘린더 타입 정의 및 CalendarViewProps 변경
src/types/calendar.ts, src/types/diary.ts
CalendarDiarySummary, CalendarDisputeSummary, CalendarRecordItem, CalendarMonthResponse 인터페이스 신규 정의. CalendarViewPropsdiaryDatacalendarData: CalendarRecordItem[]로 교체.
GET /api/calendar 라우트 신규 구현
src/app/api/calendar/route.ts
인증·파라미터 검증, emotionDiary/dispute 병렬 조회, 날짜별 맵 집계 후 CalendarRecordItem[] 구성 및 반환.
GET /api/diary 라우트 신규 구현
src/app/api/diary/route.ts
date 파라미터 검증, 하루 범위 emotionDiary 조회, DiaryItem[] 매핑 후 반환.
GET /api/disputes date/status 필터 추가
src/app/api/disputes/route.ts
date 하루 범위 필터와 status 직접 지정 분기 추가, 기존 completed 분기 제거.
클라이언트 API 함수 및 React Query 훅
src/domains/calendar/*, src/domains/diary/*, src/domains/dispute/*
fetchCalendarRecords, fetchDiariesByDate, fetchDisputesByDate API 함수와 대응하는 React Query 훅(useCalendarRecords, useDiariesByDate, useDisputesByDate) 및 쿼리 키 팩토리 신규 추가.
CalendarView 실데이터 날짜 셀 렌더링
src/components/calendar/CalendarView.tsx, src/components/calendar/CalendarView.module.scss
calendarDatarecordMap(Map)으로 변환해 날짜 셀에서 diary(EmotionIcon) + dispute(CategoryIcon)를 조건부 렌더링. emotionIcon flex 레이아웃 스타일 추가.
CategoryIcon 배지 카운트
src/components/ui/CategoryIcon.tsx, src/components/ui/CategoryIcon.module.scss
count prop 추가 및 count > 1일 때 배지 표시. wrapper/badge SCSS 신규 추가.
RecordList 실분쟁 목록 렌더링
src/components/calendar/RecordList.tsx, src/components/calendar/RecordList.module.scss
더미 데이터 제거, useDisputesByDate('closed') 연동, Link/CaseCard/Avatar/StatusBadge 렌더링. 카드 푸터 SCSS 추가.
EmotionDiaryList 실일기 목록 렌더링
src/components/diary/EmotionDiaryList.tsx
더미 배열 제거, useDiariesByDate 훅 연동, 로딩/빈 상태 분기 추가.
Calendar 페이지 라우팅·로딩·동적 요약
src/app/(page)/calendar/page.tsx, src/app/(page)/calendar/page.module.scss
useCalendarRecords 연동, 로딩 오버레이 스피너, 동적 화해횟수 계산, FAB 클릭 시 /diary/create 이동. 오버레이 SCSS 추가.

Prisma 스키마 enum 값 변경

Layer / File(s) Summary
enum ai_chat → ai_room 변경
prisma/schema.prisma
enum ai_log_typeenum statistics_source_type에서 ai_chatai_room으로 교체.

Sequence Diagram(s)

sequenceDiagram
  participant CalendarPage as Calendar Page
  participant useCalendarRecords
  participant CalendarRoute as GET /api/calendar
  participant DB as Prisma DB

  CalendarPage->>useCalendarRecords: year, month 전달
  useCalendarRecords->>CalendarRoute: fetch /api/calendar?year&month
  CalendarRoute->>DB: emotionDiary 조회 (월 범위)
  CalendarRoute->>DB: dispute 조회 (CLOSED, 참여자 포함)
  DB-->>CalendarRoute: diaries[], disputes[]
  CalendarRoute->>CalendarRoute: diaryMap, disputeMap 집계 → records 구성
  CalendarRoute-->>useCalendarRecords: { success:true, data: { records } }
  useCalendarRecords-->>CalendarPage: calendarData, isLoading
  CalendarPage->>CalendarPage: 로딩 시 오버레이 스피너, 완료 시 CalendarView + 화해횟수 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • I5-Project/TALKY-OWL#41: 동일 경로(CalendarView, RecordList, EmotionIcon)의 캘린더 UI를 더미 기반에서 구성한 PR로, 이번 PR이 해당 구조를 실데이터 기반으로 전환하는 연장선입니다.
  • I5-Project/TALKY-OWL#97: GET /api/disputesactive/completed 필터 로직을 다루는 PR로, 이번 PR이 해당 분기를 date/status 기반으로 교체한 것과 직접 연결됩니다.
  • I5-Project/TALKY-OWL#70: prisma/schema.prismaenum ai_log_type/enum statistics_source_type을 동일하게 수정한 PR입니다.

Suggested reviewers

  • juahcheon
  • lyla-bae

Poem

🐰 토끼가 달력을 펼쳤어요
더미 데이터는 풀밭 너머로 사라지고
진짜 일기와 화해 기록이 쏙쏙 들어왔죠!
useCalendarRecords 한 번에 월별 레코드 뿅~
배지 달린 아이콘도 반짝반짝 🌟
이제 캘린더엔 진짜 이야기가 가득해요

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 필수 섹션 대부분이 비어있으며, 간단한 기능 구현 내용만 포함되어 있습니다. 템플릿의 작업 내용, 담당 영역, 테스트 결과 등 많은 필수 정보가 누락되어 있습니다. PR 템플릿의 모든 섹션을 작성해주세요. 특히 담당 영역(calendar-diary 체크), 테스트 결과, 작업 범위 확인, 보안 확인 항목들을 명시적으로 완료해주시기 바랍니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. 달력 월별 조회 API와 감정일기 날짜별 조회 API 구현이라는 핵심 내용을 간결하게 표현했습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/calendar-daily-record-api

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (4)
src/domains/dispute/dispute.hooks.ts (1)

32-37: ⚡ Quick win

staleTime 설정 추가를 권장합니다.

useCalendarRecordsuseDiariesByDate는 모두 staleTime: 1000 * 60 * 5 (5분)을 설정하고 있지만, 이 훅에는 staleTime이 없어 기본값 0이 사용됩니다. 일관성을 위해 동일한 staleTime을 설정하는 것이 좋습니다.

♻️ 권장 수정안
 export function useDisputesByDate(date: string, status?: DisputeStatus) {
   return useQuery({
     queryKey: disputeKeys.byDate(date, status),
     queryFn: () => fetchDisputesByDate(date, status),
     enabled: !!date,
+    staleTime: 1000 * 60 * 5,
   });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/dispute/dispute.hooks.ts` around lines 32 - 37, The
useDisputesByDate hook is missing the staleTime configuration that is present in
similar hooks like useCalendarRecords and useDiariesByDate. Add staleTime: 1000
* 60 * 5 (5 minutes) to the options object passed to the useQuery call in the
useDisputesByDate function to maintain consistency across the codebase and
prevent unnecessary refetches.
src/domains/diary/diary.api.ts (1)

4-9: ⚡ Quick win

URLSearchParams 사용을 권장합니다.

현재 템플릿 리터럴로 쿼리 파라미터를 구성하고 있는데, date 값에 특수문자가 포함될 경우 URL 인코딩 문제가 발생할 수 있습니다. dispute.api.tsfetchDisputesByDate처럼 URLSearchParams를 사용하는 것이 더 안전하고 일관성 있습니다.

♻️ 권장 수정안
 export async function fetchDiariesByDate(date: string): Promise<DiaryItem[]> {
-  const res = await fetch(`/api/diary?date=${date}`)
+  const params = new URLSearchParams({ date })
+  const res = await fetch(`/api/diary?${params.toString()}`)
   const json: ApiResponse<{ items: DiaryItem[] }> = await res.json()
   if (!json.success || !json.data) throw new Error(json.error?.message ?? '감정일기 조회 실패')
   return json.data.items
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/diary/diary.api.ts` around lines 4 - 9, In the fetchDiariesByDate
function, replace the template literal approach for constructing the query
parameter with URLSearchParams to properly handle URL encoding. Instead of using
backticks with `date=${date}` directly in the URL, create a URLSearchParams
object, append the date parameter to it, and pass it to the fetch URL. This
ensures special characters in the date value are properly encoded and maintains
consistency with similar patterns used in other API files like dispute.api.ts.
src/components/calendar/CalendarView.tsx (2)

86-86: 💤 Low value

선택적 성능 최적화: recordMap 메모이제이션을 고려하세요.

recordMap 변환이 매 렌더링마다 실행되는데, calendarData가 변경될 때만 재계산하도록 useMemo로 감쌀 수 있습니다. 월별 레코드 수가 적다면 현재 구현도 충분하지만, 성능 개선을 원한다면 고려해보세요.

♻️ 선택적 개선안
+import { useMemo } from 'react';
+
 export default function CalendarView({
   onDateChange,
   selectedDate,
   calendarData = [],
 }: CalendarViewProps) {
-  const recordMap = new Map(calendarData.map((r) => [r.date, r]));
+  const recordMap = useMemo(
+    () => new Map(calendarData.map((r) => [r.date, r])),
+    [calendarData]
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/calendar/CalendarView.tsx` at line 86, The recordMap variable
in CalendarView.tsx is being recreated on every render regardless of whether
calendarData has changed. Wrap the Map creation with the useMemo hook to memoize
the recordMap transformation, passing calendarData as a dependency so that the
Map is only recalculated when calendarData actually changes. This will prevent
unnecessary object creation on subsequent renders with the same data.

70-70: ⚖️ Poor tradeoff

타입 단언 대신 런타임 검증을 고려하세요.

as CategoryGroup 타입 단언은 API 응답의 category 값이 항상 유효한 CategoryGroup임을 가정합니다. API 계약을 신뢰한다면 현재 구현도 괜찮지만, 방어적 코드를 위해 타입 가드를 추가할 수 있습니다.

🛡️ 선택적 개선안: 타입 가드 추가
+const isValidCategory = (cat: string): cat is CategoryGroup => 
+  ['romance', 'family', 'friend', 'work'].includes(cat);
+
 function CustomDay({ recordMap, ...pickerDayProps }: CustomDayProps) {
   // ...
   {record.dispute && (
+    isValidCategory(record.dispute.category) && (
       <CategoryIcon
-        category={record.dispute.category as CategoryGroup}
+        category={record.dispute.category}
         count={record.dispute.count}
       />
+    )
   )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/calendar/CalendarView.tsx` at line 70, The type assertion `as
CategoryGroup` on the `category` prop being passed to the component assumes the
API response value is always a valid CategoryGroup without runtime validation.
Instead of relying solely on the type assertion, implement a type guard function
to validate that the `record.dispute.category` value is actually a valid
CategoryGroup at runtime before passing it to the category prop. This provides
defensive programming by catching invalid values from the API that might not
match the expected CategoryGroup type, allowing you to handle edge cases
appropriately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/calendar/route.ts`:
- Around line 11-35: The GET handler function in route.ts needs exception
handling to ensure all responses follow the common ApiResponse format. Wrap the
entire function body (starting from const session line through the end) in a
try-catch block, then in the catch block log the actual error details and return
an ApiResponse with appropriate error information and status code. Additionally,
locate the code around lines 104-108 (likely a database operation) and add error
logging there as well to ensure all potential exceptions throughout the handler
are properly captured with context for operational tracking.

In `@src/app/api/diary/route.ts`:
- Around line 24-33: The date validation at the regex check only verifies the
format pattern (YYYY-MM-DD) but does not validate whether the date actually
exists on the calendar, allowing invalid dates like 2026-02-31 to pass through
and potentially cause errors later in the DB query. After creating the
targetDate variable from the date string, add additional validation to ensure
the date is calendar-valid by comparing the original date string with the ISO
date string of the parsed targetDate. If they do not match, return a
NextResponse.json with a 400 status code and VALIDATION_ERROR similar to the
existing format check, indicating that the date is invalid.

In `@src/app/api/disputes/route.ts`:
- Around line 71-72: The date and rawStatus parameters extracted from
searchParams are used without validation, causing Prisma exceptions to result in
500 errors instead of proper 400 responses. Add validation immediately after
extracting these parameters to check that the date is a valid date format before
using it in the Date range operations around lines 103-107, and validate that
rawStatus matches a valid enum value before casting it at line 121. Return 400
Bad Request with appropriate error messages when validation fails to prevent
Prisma exceptions.

In `@src/components/calendar/CalendarView.tsx`:
- Around line 17-23: The EMOTION_TYPE_TO_FEEL mapping in CalendarView.tsx only
defines translations for five hardcoded emotion types (happy, sad, neutral,
annoyed, angry), but the Prisma schema allows emotionType to be any string
value, which means unmapped values could reach the EmotionIcon component and
cause rendering failures. To fix this, take one of these approaches: define an
explicit enum in the Prisma schema to restrict emotionType to only allowed
values, add validation in the API layer to enforce the allowed emotion types
before they reach the database, or identify all actual emotion values currently
stored in the database and add their Korean translations to the
EMOTION_TYPE_TO_FEEL mapping to ensure complete coverage for all possible
values.

In `@src/components/calendar/RecordList.tsx`:
- Around line 20-28: The component currently only handles the `isLoading` state
from the `useDisputesByDate` hook and treats empty data as "no records", which
masks API errors as empty results. Add error state handling from the
`useDisputesByDate` hook and insert a separate error check condition between the
loading check and the empty disputes check to display a distinct error message
when data retrieval fails, ensuring users and operators can distinguish between
actual empty records and query failures.
- Line 46: The StatusBadge component at line 46 directly accesses
STATUS_MAP[status] without runtime validation, creating a crash risk if the
server returns an unexpected status value. First, consolidate the duplicate
DisputeStatus type definitions currently split between src/types/dispute.ts and
src/components/ui/StatusBadge.tsx into a single source file. Then, add runtime
validation to the StatusBadge component by either implementing a guard function
like isValidStatus to check if a status exists in STATUS_MAP before accessing
it, or provide a sensible default fallback value when the status is not found.
This will eliminate the need for the unsafe as DisputeStatus type assertion and
prevent rendering crashes from invalid status values.

In `@src/components/diary/EmotionDiaryList.tsx`:
- Around line 12-18: The current code does not handle the error state from the
useDiariesByDate hook, so when network or server failures occur, the empty items
array is treated as "no diaries registered" instead of showing a proper error
message. Destructure an isError property from the useDiariesByDate hook
alongside data and isLoading, then add a conditional check after the isLoading
check but before the items.length === 0 check in EmotionDiaryList to display an
appropriate error message when isError is true. This way, network/server
failures will be properly distinguished from the case where no diaries have
actually been registered.

In `@src/domains/dispute/dispute.hooks.ts`:
- Around line 52-89: In all four mutation hooks (useSaveStatement,
useSubmitStatement, useRequestJudgment, and useCloseDispute), the onSuccess
callbacks currently only invalidate the detail query using
disputeKeys.detail(disputeId). You need to extend each onSuccess callback to
also invalidate the list-related queries using disputeKeys.completedList and
disputeKeys.byDate, since modifications to disputes (saving, submitting,
requesting judgment, or closing) can affect both the detail view and the list
views. Add additional queryClient.invalidateQueries calls with these additional
query keys alongside the existing detail query invalidation in each hook's
onSuccess handler.

---

Nitpick comments:
In `@src/components/calendar/CalendarView.tsx`:
- Line 86: The recordMap variable in CalendarView.tsx is being recreated on
every render regardless of whether calendarData has changed. Wrap the Map
creation with the useMemo hook to memoize the recordMap transformation, passing
calendarData as a dependency so that the Map is only recalculated when
calendarData actually changes. This will prevent unnecessary object creation on
subsequent renders with the same data.
- Line 70: The type assertion `as CategoryGroup` on the `category` prop being
passed to the component assumes the API response value is always a valid
CategoryGroup without runtime validation. Instead of relying solely on the type
assertion, implement a type guard function to validate that the
`record.dispute.category` value is actually a valid CategoryGroup at runtime
before passing it to the category prop. This provides defensive programming by
catching invalid values from the API that might not match the expected
CategoryGroup type, allowing you to handle edge cases appropriately.

In `@src/domains/diary/diary.api.ts`:
- Around line 4-9: In the fetchDiariesByDate function, replace the template
literal approach for constructing the query parameter with URLSearchParams to
properly handle URL encoding. Instead of using backticks with `date=${date}`
directly in the URL, create a URLSearchParams object, append the date parameter
to it, and pass it to the fetch URL. This ensures special characters in the date
value are properly encoded and maintains consistency with similar patterns used
in other API files like dispute.api.ts.

In `@src/domains/dispute/dispute.hooks.ts`:
- Around line 32-37: The useDisputesByDate hook is missing the staleTime
configuration that is present in similar hooks like useCalendarRecords and
useDiariesByDate. Add staleTime: 1000 * 60 * 5 (5 minutes) to the options object
passed to the useQuery call in the useDisputesByDate function to maintain
consistency across the codebase and prevent unnecessary refetches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ec9c0d04-39ca-4862-9990-ed246725859f

📥 Commits

Reviewing files that changed from the base of the PR and between 72d96d7 and 3f758cf.

📒 Files selected for processing (23)
  • prisma/schema.prisma
  • src/app/(page)/calendar/page.module.scss
  • src/app/(page)/calendar/page.tsx
  • src/app/api/calendar/.gitkeep
  • src/app/api/calendar/route.ts
  • src/app/api/diary/route.ts
  • src/app/api/disputes/route.ts
  • src/components/calendar/CalendarView.module.scss
  • src/components/calendar/CalendarView.tsx
  • src/components/calendar/RecordList.module.scss
  • src/components/calendar/RecordList.tsx
  • src/components/diary/EmotionDiaryList.tsx
  • src/components/ui/CategoryIcon.module.scss
  • src/components/ui/CategoryIcon.tsx
  • src/domains/calendar/.gitkeep
  • src/domains/calendar/calendar.api.ts
  • src/domains/calendar/calendar.hooks.ts
  • src/domains/diary/diary.api.ts
  • src/domains/diary/diary.hooks.ts
  • src/domains/dispute/dispute.api.ts
  • src/domains/dispute/dispute.hooks.ts
  • src/types/calendar.ts
  • src/types/diary.ts

Comment thread src/app/api/calendar/route.ts
Comment thread src/app/api/diary/route.ts Outdated
Comment on lines +71 to +72
const date = searchParams.get('date')
const rawStatus = searchParams.get('status')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

status/date 쿼리를 선검증해서 500 대신 400을 반환해주세요

Line 103~107은 date 유효성 확인 없이 Date 범위를 만들고, Line 121은 rawStatus를 enum 검증 없이 캐스팅합니다. 잘못된 값이 들어오면 Prisma 예외로 500 처리됩니다.

제안 수정안
   const rawCategory = searchParams.get('categoryGroup')
   const date = searchParams.get('date')
   const rawStatus = searchParams.get('status')
@@
+  const parsedDate = date ? new Date(`${date}T00:00:00.000Z`) : null
+  if (date && (Number.isNaN(parsedDate!.getTime()) || parsedDate!.toISOString().slice(0, 10) !== date)) {
+    return NextResponse.json<ApiResponse>(
+      { success: false, error: { code: 'VALIDATION_ERROR', message: '유효하지 않은 date 파라미터입니다.' } },
+      { status: 400 },
+    )
+  }
+
+  const normalizedStatus = rawStatus ? rawStatus.toUpperCase() : null
+  if (normalizedStatus && !Object.values(DisputeStatus).includes(normalizedStatus as DisputeStatus)) {
+    return NextResponse.json<ApiResponse>(
+      { success: false, error: { code: 'VALIDATION_ERROR', message: '유효하지 않은 status 파라미터입니다.' } },
+      { status: 400 },
+    )
+  }
+
   const where = {
@@
-    ...(date ? {
+    ...(parsedDate ? {
       createdAt: {
-        gte: new Date(date),
-        lt: new Date(new Date(date).setDate(new Date(date).getDate() + 1)),
+        gte: parsedDate,
+        lt: new Date(parsedDate.getTime() + 24 * 60 * 60 * 1000),
       },
     } : {}),
@@
-    ...(rawStatus ? { status: rawStatus.toUpperCase() as DisputeStatus } : {}),
+    ...(normalizedStatus ? { status: normalizedStatus as DisputeStatus } : {}),
   }

Also applies to: 103-107, 121-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/disputes/route.ts` around lines 71 - 72, The date and rawStatus
parameters extracted from searchParams are used without validation, causing
Prisma exceptions to result in 500 errors instead of proper 400 responses. Add
validation immediately after extracting these parameters to check that the date
is a valid date format before using it in the Date range operations around lines
103-107, and validate that rawStatus matches a valid enum value before casting
it at line 121. Return 400 Bad Request with appropriate error messages when
validation fails to prevent Prisma exceptions.

Comment on lines +17 to +23
const EMOTION_TYPE_TO_FEEL: Record<string, string> = {
happy: '기쁨',
sad: '슬픔',
neutral: '보통',
annoyed: '짜증',
angry: '화남',
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Prisma schema에서 emotionType enum 정의 확인
rg -A 10 'enum.*emotion' prisma/schema.prisma

Repository: I5-Project/TALKY-OWL

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Find Prisma schema file
fd -e prisma -type f | head -20

Repository: I5-Project/TALKY-OWL

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Search for emotionType definition in the codebase
rg 'emotionType' --type-list | head -20
rg -i 'enum.*emotion|emotion.*enum' -A 5 -B 1 | head -100

Repository: I5-Project/TALKY-OWL

Length of output: 454


🏁 Script executed:

#!/bin/bash
# View CalendarView.tsx lines 17-23 and around line 62
fd -i 'CalendarView.tsx' -type f -exec cat -n {} \;

Repository: I5-Project/TALKY-OWL

Length of output: 235


🏁 Script executed:

#!/bin/bash
# Find Prisma schema file
find . -name "schema.prisma" -o -name "*.prisma" | head -20

Repository: I5-Project/TALKY-OWL

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Find CalendarView.tsx
find . -name "CalendarView.tsx" -type f

Repository: I5-Project/TALKY-OWL

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Search for emotionType in the codebase
rg 'emotionType' -B 2 -A 2 | head -150

Repository: I5-Project/TALKY-OWL

Length of output: 4834


🏁 Script executed:

#!/bin/bash
# View CalendarView.tsx with line numbers, focusing on lines 1-80
cat -n src/components/calendar/CalendarView.tsx | head -90

Repository: I5-Project/TALKY-OWL

Length of output: 3574


🏁 Script executed:

#!/bin/bash
# Check EmotionType definition in diary.ts
cat -n src/types/diary.ts | head -80

Repository: I5-Project/TALKY-OWL

Length of output: 1402


EMOTION_TYPE_TO_FEEL 매핑의 불완전성을 해결하세요.

Prisma 스키마에서 emotionTypeString? 타입(enum 아님)으로 정의되어 있어 이론상 80자 이내의 모든 문자열을 저장할 수 있습니다. API_SPEC.md에서도 "emotionType 허용 값 목록 및 마스터 데이터 관리 방식"이 아직 확정되지 않았다고 명시되어 있습니다.

현재 EMOTION_TYPE_TO_FEEL 매핑은 5개 값(happy, sad, neutral, annoyed, angry)만 포함하고 있는데, 스키마상 이 외의 값이 데이터베이스에 저장될 수 있습니다. 이 경우 line 62의 fallback이 매핑되지 않은 영문 문자열을 EmotionIcon에 전달하면서 렌더링이 실패할 수 있습니다.

해결 방안:

  • emotionType enum을 Prisma에서 명시적으로 정의하거나
  • API 레이어에서 허용 값을 제한하는 검증을 추가하거나
  • 데이터베이스에 실제 존재하는 모든 emotion 값을 파악하여 매핑을 완성하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/calendar/CalendarView.tsx` around lines 17 - 23, The
EMOTION_TYPE_TO_FEEL mapping in CalendarView.tsx only defines translations for
five hardcoded emotion types (happy, sad, neutral, annoyed, angry), but the
Prisma schema allows emotionType to be any string value, which means unmapped
values could reach the EmotionIcon component and cause rendering failures. To
fix this, take one of these approaches: define an explicit enum in the Prisma
schema to restrict emotionType to only allowed values, add validation in the API
layer to enforce the allowed emotion types before they reach the database, or
identify all actual emotion values currently stored in the database and add
their Korean translations to the EMOTION_TYPE_TO_FEEL mapping to ensure complete
coverage for all possible values.

Comment thread src/components/calendar/RecordList.tsx Outdated
<Avatar src={dispute.participants[0]?.profileImageUrl ?? undefined} alt="참여자" size="s" />
<time className={styles.cardDate}>{formatDate(dispute.createdAt)}</time>
</div>
<StatusBadge status={dispute.status as DisputeStatus} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 상태 타입 계약(서버/훅/UI) 정합성 확인용 읽기 전용 점검
# 기대 결과:
# 1) DisputeStatus 정의와 API 응답 status 값 집합이 일치
# 2) UI에서 불필요한 `as DisputeStatus` 단언이 제거 가능하거나, 가드가 존재

rg -n -C3 --type=ts --type=tsx "export type DisputeStatus|STATUS_MAP|as DisputeStatus|useDisputesByDate|status"

Repository: I5-Project/TALKY-OWL

Length of output: 92


🏁 Script executed:

# Find the RecordList.tsx file first
find . -name "RecordList.tsx" -type f

Repository: I5-Project/TALKY-OWL

Length of output: 105


🏁 Script executed:

# Search for DisputeStatus definition, StatusBadge, and related code
rg "export type DisputeStatus|STATUS_MAP|as DisputeStatus|useDisputesByDate" --type ts -A 2 -B 2

Repository: I5-Project/TALKY-OWL

Length of output: 4854


🏁 Script executed:

# Search for the StatusBadge component definition
find . -name "*StatusBadge*" -type f

Repository: I5-Project/TALKY-OWL

Length of output: 144


🏁 Script executed:

cat -n src/components/ui/StatusBadge.tsx

Repository: I5-Project/TALKY-OWL

Length of output: 1561


🏁 Script executed:

cat -n src/types/dispute.ts

Repository: I5-Project/TALKY-OWL

Length of output: 3371


🏁 Script executed:

cat -n src/domains/dispute/dispute.hooks.ts

Repository: I5-Project/TALKY-OWL

Length of output: 3378


StatusBadge 컴포넌트에서 런타임 유효성 검증이 없어 크래시 위험이 존재합니다.

STATUS_MAP[status]에 직접 접근하는데, 타입스크립트의 Record<DisputeStatus, ...> 정의는 런타임에 유효성을 보장하지 않습니다. 서버가 예상 외의 상태값을 반환하면 undefined에서 속성 접근 시 렌더링이 실패합니다.

src/types/dispute.tssrc/components/ui/StatusBadge.tsx에 중복된 DisputeStatus 정의가 있어서 불필요한 as DisputeStatus 단언도 필요한 상황입니다. 타입 정의를 통합하고, StatusBadge에 런타임 가드(예: 기본값 fallback 또는 isValidStatus 함수)를 추가해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/calendar/RecordList.tsx` at line 46, The StatusBadge component
at line 46 directly accesses STATUS_MAP[status] without runtime validation,
creating a crash risk if the server returns an unexpected status value. First,
consolidate the duplicate DisputeStatus type definitions currently split between
src/types/dispute.ts and src/components/ui/StatusBadge.tsx into a single source
file. Then, add runtime validation to the StatusBadge component by either
implementing a guard function like isValidStatus to check if a status exists in
STATUS_MAP before accessing it, or provide a sensible default fallback value
when the status is not found. This will eliminate the need for the unsafe as
DisputeStatus type assertion and prevent rendering crashes from invalid status
values.

Comment thread src/components/diary/EmotionDiaryList.tsx Outdated
Comment on lines 52 to +89
export function useSaveStatement(disputeId: string) {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation({
mutationFn: (content: string) => saveStatement(disputeId, content),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) })
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
},
})
});
}

export function useSubmitStatement(disputeId: string) {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => submitStatement(disputeId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) })
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
},
})
});
}

export function useRequestJudgment(disputeId: string) {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => requestJudgment(disputeId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) })
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
},
})
});
}

export function useCloseDispute(disputeId: string) {
const queryClient = useQueryClient()
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => closeDispute(disputeId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) })
queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
},
})
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

mutation 성공 시 목록 쿼리도 무효화해야 합니다.

현재 모든 mutation 훅(useSaveStatement, useSubmitStatement, useRequestJudgment, useCloseDispute)이 disputeKeys.detail(disputeId)만 무효화하고 있습니다. 하지만 분쟁을 수정/제출/종료하면 목록 데이터(disputeKeys.completedList, disputeKeys.byDate)도 변경될 수 있으므로, 이들도 함께 무효화하지 않으면 목록 화면에 오래된 데이터가 표시될 수 있습니다.

🔄 권장 수정안 (예: useCloseDispute)
 export function useCloseDispute(disputeId: string) {
   const queryClient = useQueryClient();
   return useMutation({
     mutationFn: () => closeDispute(disputeId),
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
+      queryClient.invalidateQueries({ queryKey: ['disputes'] });
     },
   });
 }

또는 더 세밀한 무효화:

       queryClient.invalidateQueries({ queryKey: disputeKeys.detail(disputeId) });
+      queryClient.invalidateQueries({ queryKey: ['disputes', 'completed'] });
+      queryClient.invalidateQueries({ queryKey: ['disputes', 'byDate'] });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domains/dispute/dispute.hooks.ts` around lines 52 - 89, In all four
mutation hooks (useSaveStatement, useSubmitStatement, useRequestJudgment, and
useCloseDispute), the onSuccess callbacks currently only invalidate the detail
query using disputeKeys.detail(disputeId). You need to extend each onSuccess
callback to also invalidate the list-related queries using
disputeKeys.completedList and disputeKeys.byDate, since modifications to
disputes (saving, submitting, requesting judgment, or closing) can affect both
the detail view and the list views. Add additional queryClient.invalidateQueries
calls with these additional query keys alongside the existing detail query
invalidation in each hook's onSuccess handler.

@evenif99
evenif99 merged commit 886f8ef into dev Jun 22, 2026
3 checks passed
@evenif99
evenif99 deleted the feature/calendar-daily-record-api branch June 22, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants