캘린더 도메인 테크 스펙 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

1️⃣ 배경 (Background)

프로젝트 목표 (Objective)

  • 취업 준비 과정(서류/코테/면접/개인일정)을 캘린더 + 일정 카드 + To-Do로 한 화면에서 관리하고, AI 자동 일정 생성까지 연결해 사용자의 입력 부담을 줄임

핵심 결과 (Key Result) 1

  • 사용자가 월간/주간 전환 + 날짜 선택 + 일정 카드 확인의 핵심 흐름을 끊김 없이 수행함
    • 전환/이동/선택에 따른 UI 상태 꼬임(잘못된 날짜 표시, 카드 목록 불일치) 0건

핵심 결과 (Key Result) 2

  • 일정 CRUD(추가/조회/수정/삭제)가 안정적으로 동작함
    • 저장 실패/중복 생성/삭제 후 잔존 등 주요 버그 0건

핵심 결과 (Key Result) 3

  • AI 자동 추가 기능이 사용 가능한 수준으로 동작한다.
    • 파일/텍스트 첨부 → 분석 요청 → 대기 → 결과 확인(CAL-M2)까지 성공률/복구 UX 확보
    • 실패/취소 시 사용자가 어디까지 되었는지 이해 가능

문제 정의 (Problem):

  • 일정이 많아지면 지금 뭐가 임박했는지 파악이 어려움
  • 단계별(서류/코테/면접/개인일정)로 분류하지 않으면 우선순위가 흐려짐
  • 채용 공고를 보고 일정을 일일이 입력하는 비용이 큼(특히 여러 전형)

가설 (Hypothesis)

  • 캘린더 상단은 ‘날짜 탐색’, 하단은 ‘다가오는 일정/선택한 날짜 일정’을 카드로 보여주면 사용자가 빠르게 확인
  • 단계/태그/날짜 범위 필터를 제공하면 원하는 일정만 좁혀볼 수 있음
  • AI 자동 일정 생성(텍스트/이미지/PDF 기반)을 제공하면 입력 시간을 크게 줄일 수 있음

관련 자료

  • 화면 설계서: CAL-001, CAL-001-1, CAL-M1M6, CAL-M2M3
  • 공통 정책: 공통 도메인 스펙

2️⃣ 목표가 아닌 것 (Non-goals)

이번 프로젝트에서 다루지 않는 내용

  • 초 단위 리마인더/복잡한 반복 일정(RRULE) 같은 고급 캘린더 기능
  • 일정 공유/권한 관리(다른 사람과 일정 공유, 편집 권한) 기능

3️⃣ 설계 및 기술 자료 (Architecture and Technical Documentation)

아키텍처 개요 (Architecture Overview)

  • 화면 구성 핵심:
    • 상단: 캘린더(월/주 뷰, 이동, 오늘 하이라이트, 일정 점/기간 선)
    • 중단: 일정 카드 리스트(기본: 다가오는 일정 TOP5 / 선택 시: 해당 날짜 일정)
    • 하단: To-Do(선택 날짜 기준)
  • 상태 흐름 핵심:
    • viewMode(월/주) + focusedDate(현재 달/주 기준) + selectedDate(선택 날짜)
    • filters(단계/태그/날짜 범위) → 카드 목록과 점/선 표시에도 영향
  • 모달 흐름:
    • 일정 추가(CAL-M1) → AI 분석 대기(CAL-M1-2) → AI 결과 확인(CAL-M2) → 저장
    • 일정 상세(CAL-M4) → 삭제 확인(CAL-M5) / 수정(CAL-M3)
    • 작성 취소 확인(CAL-M6)

4️⃣ 주요 페이지 / 컴포넌트 구조

페이지 (Pages)

페이지 (Pages)

  • 페이지 이름: CalendarPage (CAL-001)
    • 주요 기능:
      • 월/주 토글
      • 월/주 이동
      • 날짜 선택
      • 일정 유무 표시(점/기간선)
      • 일정 카드 리스트(다가오는 일정/선택 날짜 일정)
      • To-Do 리스트(추가/완료/수정/삭제, 진행률)
      • 필터 토글(CAL-001-1)
      • 일정 추가 모달(CAL-M1) 오픈
    • 사용 컴포넌트:
      • CalendarHeader(월/주 토글 + 기간 표시 + 이동 버튼 + 필터 버튼 + 일정추가 버튼)
      • MiniCalendar 또는 CalendarGrid(월/주 UI)
      • ScheduleSectionHeader(“다가오는 일정”/“n월 n일의 일정”)
      • ScheduleCardList / ScheduleCard
      • TodoPanel(진행률 + 리스트)
      • FilterPanel(CAL-001-1)
      • GlobalModalHost(CAL-M1~M6)
      • Toast(첨부 실패/성공, 삭제 성공 등)
    • 데이터 로딩 시점:
      • 페이지 진입 시: 해당 기간(월/주)의 일정 목록 조회 + 선택 날짜 To-Do 조회
      • viewMode/focusedDate 변경 시: 해당 기간 일정 재조회
      • selectedDate 변경 시: 해당 날짜 To-Do 조회 + 카드 목록 전환
      • 필터 변경 시: 서버 필터링 or 클라이언트 필터링 정책에 따라 즉시 반영
    • 라우팅:
      • /calendar (보호 라우트, AuthGate 뒤)

주요 컴포넌트 (Components)

(1) CalendarHeader

  • 역할
    • 월/주 토글, 현재 기간(년월/주차) 표시, 이전/다음 이동, 필터 열기/닫기, 일정 추가 모달 오픈 버튼 제공
  • Props
    • viewMode: 'month' | 'week'
    • focusedDate: Date
    • isFilterOpen: boolean
    • hasActiveFilter: boolean (필터 적용 여부 뱃지/색상 표시용)
    • onChangeView(mode)
    • onPrev()
    • onNext()
    • onToggleFilter()
    • onOpenCreateModal()
  • 발생 이벤트 → 페이지에서 하는 일
    • 토글 클릭 → viewMode 변경 + focusedDate 재정렬(현재 월/주 기준) + 일정 재조회
    • 이전/다음 → focusedDate 이동 + 일정 재조회
    • 필터 버튼 → isFilterOpen 토글
    • 일정추가 → CAL-M1 열기

(2) MiniCalendar

  • 역할
    • 월/주 달력 UI 렌더링
    • 오늘 하이라이트
    • 일정 유무 점(단일 일정) / 기간선(기간 일정) 표시
    • 날짜 선택 처리
  • Props
    • viewMode
    • focusedDate
    • selectedDate
    • events: CalendarEvent[] (해당 기간 일정)
    • onSelectDate(date)
    • onChangeFocusedDate(date)
  • 발생 이벤트
    • 날짜 클릭 → selectedDate 변경
      • 하단: “선택 날짜 일정” 카드 리스트로 전환
      • To-Do: 해당 날짜 To-Do 조회
  • UI 규칙
    • 오늘 날짜 강조
    • 당일 점은 개수 무관 1개
    • 기간 일정은 선(두께 6px), 시작/끝 라운드 처리
    • 겹침/hover 시 z-index 상단 노출

(3) ScheduleSectionHeader

  • 역할
    • 하단 카드 리스트의 제목 영역
    • 기본: “다가오는 일정”
    • 날짜 선택 시: “n월 n일의 일정”
    • 일정 없으면 “일정이 없습니다” 문구는 여기서 처리 or 리스트에서 처리(둘 중 하나로 표준화)
  • Props
    • mode: 'upcoming' | 'selectedDate'
    • selectedDate?: Date
    • isEmpty: boolean

(4) ScheduleCardList / ScheduleCard

  • 역할
    • 일정 목록을 카드 형태로 렌더링
    • 카드 클릭 시 상세 모달(CAL-M4) 오픈
  • Props(List)
    • items: CalendarEvent[]
    • onClickItem(eventId) (상세 모달 오픈)
    • variant: 'upcoming' | 'day'
  • Props(Card)
    • 표시 요소:
      • 유형(태그/단계)
      • 임박 뱃지(3일 이내)
      • D-Day(D-3, D-Day)
      • 제목/회사/날짜/장소
      • 알림
  • 정렬 규칙
    • 선택 날짜 일정:
      1. 시작 시간 없는 당일 일정 우선
      2. 시작 시간 기준 정렬
      3. 동일 조건이면 ㄱㄴㄷ
    • 다가오는 일정: 시작일 기준 가까운 순 TOP 5

(5) TodoPanel

  • 역할
    • 선택 날짜의 To-Do 관리(추가/완료/수정/삭제)
    • 진행률(%) + 상태바 표시
  • Props
    • selectedDate: Date
    • todos: TodoItem[]
    • onAdd() / onAddWithTitle(title)
    • onToggle(todoId)
    • onUpdate(todoId, title)
    • onDelete(todoId)
  • 내부 UX 이벤트
    • 추가 시 빈 항목 생성 + 자동 focus
    • Enter → 아래에 새 항목 추가
    • Esc / focus out → 내용 있으면 저장, 없으면 삭제
  • 정렬
    • 미완료 위, 완료 아래
    • 생성 빠른 순
  • 진행률 표시
    • completed / total
    • 100%면 “전체 완료” + Bold + 상태바 색상 변경

(6) FilterPanel (CAL-001-1)

  • 역할
    • 단계 필터(단일 선택), 태그 검색 + 자동완성, 날짜 범위 필터
    • 활성 필터 칩 목록 표시 및 개별 삭제
    • 초기화 버튼 제공
  • Props
    • filters
    • tagInputValue
    • tagSuggestions: string[]
    • onChangeStage(stage|null)
    • onChangeTagInput(value)
    • onAddTag(tag)
    • onRemoveTag(tag)
    • onChangeRange(range)
    • onRemoveFilterChip(type)
    • onReset()
  • 중요
    • 필터 변경은 즉시 반영
      • 페이지에서 filters 갱신하면 일정 리스트 쿼리키가 바뀌도록 설계(React Query invalidate/자동 refetch)

(7) GlobalModalHost (CAL-M1~M6)

  • 역할
    • 모든 모달을 한 곳에서 관리(열림/닫힘/스택)
    • 페이지에서는 ‘모달 열기’만 호출하고, 실제 렌더링은 Host가 담당
  • 포함 모달
    • CAL-M1: 일정 추가(수동 + AI 입력)
    • CAL-M1-2: AI 생성 대기(취소 가능)
    • CAL-M2: AI 결과 확인/수정 후 생성
    • CAL-M4: 일정 상세
    • CAL-M3: 일정 수정
    • CAL-M5: 삭제 확인
    • CAL-M6: 작성 취소 확인
  • Props
    • modalState (현재 열린 모달 타입 + payload)
    • onClose()
    • onConfirm(payload) (삭제/저장/생성 등)

5️⃣ 컴포넌트 간 관계

(1) 전체 트리

  • CalendarPage (도메인 오케스트레이터)
    • CalendarHeader
    • CalendarGrid
    • FilterPanel
    • ScheduleCardList
    • TodoPanel
    • ModalHost
      • CreateScheduleModal (CAL-M1)
      • AIWaitingModal (CAL-M1-2)
      • AIGeneratedConfirmModal (CAL-M2)
      • ScheduleDetailModal (CAL-M4)
      • ScheduleEditModal (CAL-M3)
      • DeleteConfirmModal (CAL-M5)

(2) 관계를 ‘역할’로 나누기

  • 상태/데이터를 ‘결정’하는 곳
    • CalendarPage
      • viewMode / focusedDate / selectedDate
      • filters / isFilterOpen
      • 서버데이터: events(기간 일정), todos(선택 날짜)
    • 즉, 아래 컴포넌트는 보여주기 중심이고, 상태 결정은 CalendarPage가 한다.
  • 상태를 바꾸는 입력(UI 컨트롤)
    • CalendarHeader
    • CalendarGrid
    • FilterPanel
    • TodoPanel(추가/수정/완료 토글은 입력임)
    • ScheduleCardList(카드 클릭은 모달 입력임)
    • 입력이 발생하면 CalendarPage로 이벤트를 올림(onSomething)
  • 모달은 상태에 따른 UI 레이어
    • ModalHost
      • modalState를 보고 어떤 모달을 렌더할지 결정
      • 모달 내부에서 저장/삭제/취소를 누르면 다시 CalendarPage로 이벤트 전달

(3) props 내려주기 관계 (CalendarPage → child)

  • CalendarPage → CalendarHeader
    • 내려주는 값
      • viewMode
      • focusedDate
      • isFilterOpen
      • hasActiveFilter
    • 내려주는 핸들러
      • onChangeView(mode)
      • onPrev(), onNext()
      • onToggleFilter()
      • onOpenCreateModal()
  • CalendarPage → CalendarGrid
    • 내려주는 값
      • viewMode
      • focusedDate
      • selectedDate
      • events (해당 기간 일정 목록)
    • 내려주는 핸들러
      • onSelectDate(date) (선택 날짜 변경)
  • CalendarPage → FilterPanel
    • 내려주는 값
      • isOpen
      • filters
      • tagSuggestions
    • 내려주는 핸들러
      • onChangeStage(stage|null)
      • onAddTag(tag) / onRemoveTag(tag)
      • onChangeRange({start,end})
      • onReset()
  • CalendarPage → ScheduleCardList
    • 내려주는 값
      • items (다가오는 일정 TOP5 or 선택 날짜 일정)
      • variant (upcoming/day)
    • 내려주는 핸들러
      • onClickItem(eventId) (상세 모달 열기)
  • CalendarPage → TodoPanel
    • 내려주는 값
      • selectedDate
      • todos
      • progress (계산값이어도 됨: total/completed)
    • 내려주는 핸들러
      • onAddTodo()
      • onToggleTodo(todoId)
      • onUpdateTodo(todoId, title)
      • onDeleteTodo(todoId)
  • CalendarPage → ModalHost
    • 내려주는 값
      • modalState
        • 예: { type: 'CREATE' | 'DETAIL' | 'EDIT' | 'DELETE' | 'DISCARD' | 'AI_WAIT' | 'AI_CONFIRM', payload: ... }
    • 내려주는 핸들러
      • onClose()
      • onConfirm(payload) (저장/삭제/확정 등)

(4) 이벤트 올라오기 관계 (child → CalendarPage)

  • CalendarHeader에서 올라오는 이벤트
    • 월/주 토글 → setViewMode(mode) + 일정 재조회
    • 이전/다음 → setFocusedDate(prev/next) + 일정 재조회
    • 필터 열기/닫기 → toggleFilterOpen()
    • 일정 추가 → openModal('CREATE')
  • CalendarGrid에서 올라오는 이벤트
    • 날짜 선택 → setSelectedDate(date) + To-Do 재조회 + 카드 리스트 전환
  • FilterPanel에서 올라오는 이벤트
    • stage/tags/range 변경 → setFilters(next) + 일정 재조회(또는 클라필터)
    • reset → resetFilters() + 일정 재조회
  • ScheduleCardList에서 올라오는 이벤트
    • 카드 클릭 → openModal('DETAIL', { eventId })
  • TodoPanel에서 올라오는 이벤트
    • 추가/수정/토글/삭제 → 투두 mutation → 성공 시 refetchTodos(selectedDate) 또는 invalidate
  • ModalHost에서 올라오는 이벤트
    • CREATE 저장 → 일정 생성 mutation → 성공 시 일정 목록 invalidate
    • DETAIL에서 “수정” → openModal('EDIT', { eventId })
    • EDIT 저장 → 일정 수정 mutation → 성공 시 invalidate
    • DELETE 확인 → 일정 삭제 mutation → 성공 시 토스트 + invalidate
    • DISCARD 확인 → 모달 닫기
    • AI_WAIT 취소 → AI 요청 취소(가능하면) + 모달 닫기
    • AI_CONFIRM 저장 → 일정 일괄 생성 mutation → 성공 시 invalidate

(5) 모달별 payload 관계 (ModalHost 내부 분기 기준)

  • CreateScheduleModal(CAL-M1)
    • payload: { initialDate?: Date } (선택 날짜 기준으로 기본값 주려면)
  • AIWaitingModal(CAL-M1-2)
    • payload: { requestId: string }
  • AIGeneratedConfirmModal(CAL-M2)
    • payload: { draftId: string } 또는 { requestId: string }
  • ScheduleDetailModal(CAL-M4)
    • payload: { eventId: string }
  • ScheduleEditModal(CAL-M3)
    • payload: { eventId: string }
  • DeleteConfirmModal(CAL-M5)
    • payload: { eventId: string }
  • DiscardConfirmModal(CAL-M6)
    • payload: { from: 'CREATE' | 'EDIT', dirtyFields?: string[] }

6️⃣ 상태 관리 전략 (State Management Strategy)

Global State (Zustand Stores)

  • calendarUIStore
    • viewMode (month/week)
    • focusedDate (현재 보고 있는 기간 기준)
    • selectedDate (선택한 날짜)
    • isFilterOpen
    • filters
      • stage(서류/코테/1차/2차/개인일정 중 1개)
      • tags: string[]
      • range: { start?: string, end?: string }
  • modalStore (공통 모달 정책과 연결 가능)
    • 어떤 모달이 열려있는지
    • 모달에 전달할 payload(선택한 일정 id 등)

Local State (React useState/useReducer)

  • 모달 내부의 폼 입력값(제목/회사명/날짜/설명/태그/알림)
  • To-Do 입력의 임시 편집 상태(“현재 편집 중인 항목”)

Server Cache State (React Query)

  • 일정 목록(기간 단위)
  • 선택 날짜 To-Do
  • 태그 자동완성 후보
  • AI 분석 결과(요청 후 결과 받아오기)
  • 캐싱 키 예시
    • ['schedules', viewMode, focusedDate, filters]
    • ['todos', selectedDate]
    • ['tag-suggestions', query]
    • ['ai-schedule-draft', requestId]

7️⃣ API 연동 (API Integration)

호출할 백엔드 API 목록

  • 일정(Event)
    • GET /api/events?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD&step=String&tag=String
      • 기간 일정 목록 조회
      • startDate, endDate는 필수
      • step, tag는 선택(각각 단일 값)
    • POST /api/events
      • 일정 생성
      • 수동 입력 저장 / AI 결과 확정 저장에 사용
    • GET /api/events/{eventId}
      • 일정 상세 조회 (상세/수정 모달 초기값)
    • PUT /api/events/{eventId}
      • 일정 수정
    • DELETE /api/events/{eventId}
      • 일정 삭제 (성공 시 204)
  • To-Do
    • GET /api/todos?dueDate=YYYY-MM-DD
      • 선택 날짜 To-Do 조회
      • dueDate는 선택, 미입력 시 기본값: 현재 날짜
    • POST /api/todos
      • To-Do 추가
    • PATCH /api/todos/{todoId}
      • To-Do 수정 또는 완료 토글
      • 같은 endpoint 공유 (body가 다름)
    • DELETE /api/todos/{todoId}
      • To-Do 삭제 (성공 시 204)
  • AI 자동 일정 추출
    • POST /api/files/presigned
      • Presigned URL 발급 (S3 업로드용)
      • 성공 200presignedUrl, s3Key
    • POST /api/files
      • 파일 메타 등록 (fileId 획득)
      • 성공 201fileId
    • POST /api/ai/events/extraction
      • 일정 추출 요청(비동기 시작)
      • body: { fileId }
      • 성공 202taskId, status=PENDING
    • GET /api/ai/tasks/{taskId}
      • AI 작업 상태 조회(폴링)
      • 진행 중 202 (PROGRESSING) / 완료 200 (COMPLETED + result) / 실패 200 (FAILED + failReason)
    • DELETE /api/files/{fileId}
      • 첨부 취소/정리용 파일 삭제 (성공 시 204)

API 호출 처리 정책

  • axios 사용 (프로젝트 컨벤션)
  • 로딩 UI
    • 캘린더/리스트: Skeleton 또는 Spinner
    • AI 분석: 전용 대기 모달(CAL-M1-2)로 blocking 처리(배경 상호작용 불가)
  • 에러 처리
    • 전역 에러 핸들러로 토스트/모달 + 로깅
    • 401 → AuthGate/리프레시 흐름
    • 403/404 → “권한 없음/대상 없음” 안내
  • React Query로 중복 호출 방지/캐시/자동 refetc

API 호출 시점 + 캐시 반영 정책

1) GET /api/events (기간 일정 목록)

  • 호출 시점
    • /calendar 진입 시
    • viewMode 변경 (월 ↔ 주)
    • focusedDate 변경 (이전/다음 이동)
    • filters 변경 (step/tag 등)
  • QueryKey 예시
    • ['events', startDate, endDate, step ?? null, tag ?? null]
  • 비고(중요)
    • 백엔드 tag단일 파라미터라서, 프론트에서 태그 다중 선택을 허용하면
      • 서버에는 대표 tag 1개만 전달 + 나머지는 클라이언트 필터링(정책 필요)

2) GET /api/events (기간 일정 목록)

  • 호출 시점
    • /calendar 진입 시
    • viewMode 변경 (월 ↔ 주)
    • focusedDate 변경 (이전/다음 이동)
    • filters 변경 (step/tag 등)
  • QueryKey 예시
    • ['events', startDate, endDate, step ?? null, tag ?? null]
  • 비고(중요)
    • 백엔드 tag단일 파라미터라서, 프론트에서 태그 다중 선택을 허용하면
      • 서버에는 대표 tag 1개만 전달 + 나머지는 클라이언트 필터링(정책 필요)

3) GET /api/events/{eventId} (일정 상세)

  • 호출 시점
    • ScheduleCard 클릭 → CAL-M4(상세 모달) 오픈할 때
    • CAL-M3(수정 모달) 진입 시 초기 데이터로 사용
  • 캐시 반영
    • QueryKey 예: ['event', eventId]
    • 데이터는 상세 모달/수정 모달에 전달

4) PUT /api/events/{eventId} (일정 수정)

  • 호출 시점
    • CAL-M3에서 저장 클릭 시
  • 성공 시 캐시 반영
    • invalidateQueries(['events', ...])
    • invalidateQueries(['event', eventId]) (상세 캐시 갱신)
  • UI 처리
    • 성공: 모달 닫기 + 토스트
    • 실패: 모달 유지 + 에러 안내

5) DELETE /api/events/{eventId} (일정 삭제)

  • 호출 시점
    • CAL-M5에서 삭제 확인 클릭 시
  • 성공 시 캐시 반영
    • invalidateQueries(['events', ...])
    • removeQueries(['event', eventId]) (선택: 상세 캐시 정리)
  • UI 처리(스펙)
    • 성공: 모달 닫기 → 토스트 “${일정명}이 정상적으로 삭제되었습니다”(3초)

6) GET /api/todos?dueDate=... (To-Do 조회)

  • 호출 시점
    • /calendar 진입 시 (기본: 오늘)
    • selectedDate 변경 시
  • QueryKey 예시
    • ['todos', dueDate]

7) POST /api/todos (To-Do 추가)

  • 호출 시점
    • TodoPanel 입력 후 Enter/포커스아웃 (내용이 있을 때만)
  • 성공 시
    • invalidateQueries({ queryKey: ['todos', dueDate] })

8) PATCH /api/todos/{todoId} (수정 / 완료 토글)

  • 호출 시점
    • 인라인 편집 저장(Enter/포커스아웃)
    • 체크박스 클릭(완료 토글)
  • 성공 시
    • invalidateQueries({ queryKey: ['todos', dueDate] })
  • 권장(실수 방지)
    • 같은 endpoint 공유 → 프론트 함수 분리:
      • updateTodo(todoId, { title, dueDate })
      • toggleTodo(todoId, { isCompleted })

9) DELETE /api/todos/{todoId} (To-Do 삭제)

  • 호출 시점
    • 삭제 버튼 클릭 또는 “내용 완전 삭제 후 저장” 같은 UX 정책에 따름
  • 성공 시
    • invalidateQueries({ queryKey: ['todos', dueDate] })

10) POST /api/files/presigned (Presigned URL 발급)

  • 호출 시점
    • CAL-M1에서 파일 선택 직후(업로드 시작 전)
  • 캐시
    • 캐시보단 “업로드 세션” 성격 → 로컬 상태로 관리

11) POST /api/files (파일 메타 등록 → fileId 확보)

  • 호출 시점
    • S3 업로드 성공 직후
  • 결과 사용
    • fileId를 다음 AI 추출 요청에 전달

12) POST /api/ai/events/extraction (추출 요청)

  • 호출 시점
    • CAL-M1에서 “AI 분석하기” 클릭 시
  • 결과
    • taskId 반환 → 즉시 CAL-M1-2(AIWaitingModal) 오픈

13) GET /api/ai/tasks/{taskId} (상태 조회 폴링)

  • 호출 시점
    • AIWaitingModal이 열려있는 동안만 폴링
  • 성공 시
    • status=COMPLETED + result 수신 → CAL-M2(AIGeneratedConfirmModal)로 이동
  • 실패/취소 시
    • 토스트 안내 + CAL-M1로 복귀(첨부 유지)

14) DELETE /api/files/{fileId} (선택)

  • 호출 시점
    • 사용자가 첨부 취소/정리할 때
  • 정책
    • 서버/스토리지 비용 고려해서 “취소 시 삭제” 여부 팀 합의 필요

8️⃣ 라우팅 (Routing)

  • /calendar
    • 로그인 필요(보호 라우트)
  • 모달은 라우팅이 아니라 “UI 레이어”로 관리(필요 시 URL 연동은 범위 밖)

9️⃣ 폼 처리 및 유효성 검증 (Forms & Validation)

유효성 검증 로직

  • 클라이언트 측
    • 제목: 15자 이하, 필수
    • 회사명: 15자 이하, 필수
    • 단계: 필수(라디오)
    • 설명: 100자 이하(선택)
    • 태그: 최대 5개
    • 텍스트 입력(AI): 1000자 제한
    • 파일 첨부:
      • 이미지: jpg/jpeg/png/webp, 최대 5장, 각 2MB
      • 파일: pdf/doc/docx, 최대 1개, 10MB
    • 알림:
      • 분(159) / 시간(123) / 일(1365) / 주(152)
  • 서버 측(백엔드)에서도 별도 검증 로직 존재
    • 서버가 최종 validation 수행(보안/정합성)
    • 프론트는 “사용자 경험(즉시 피드백)” 목적의 1차 방어

이외 고려사항들 (Other Considerations) (Optional)

  • 테스트 전략
    • 단위 테스트: 날짜 계산(월/주 이동), 정렬 로직(일정/To-Do), 필터 적용
    • 통합 테스트: 모달 흐름(CAL-M1 → 대기 → CAL-M2 → 저장)
    • E2E:
      1. 월간/주간 전환 후 일정 카드가 일치하는지
      2. 일정 추가→저장→카드 반영
      3. 삭제 후 토스트 + 목록 제거
      4. AI 분석 취소/실패 시 복귀 UX
  • 로깅 및 분석(Logging & Analytics)
    • AI 분석 요청 성공/실패/취소 이벤트 로깅
    • 일정 생성/수정/삭제 주요 이벤트 로깅(에러 원인 파악)
  • 시간/타임존
    • date/datetime-local 혼용(스펙):
      • 시간 명시 일정: datetime
      • 시간 없는 일정: date
    • 서버와 클라이언트의 타임존 처리 기준 합의 필요
  • 성능
    • 월/주 변경 시 전체 재렌더 최소화
    • 일정 점/기간선 렌더링 최적화(메모이제이션)

용어 정의

  • viewMode(월간/주간): 캘린더가 월 단위로 보이냐, 주 단위로 보이냐
  • focusedDate: 현재 “보고 있는 기간”의 기준 날짜(이전/다음 이동 기준)
  • selectedDate: 사용자가 클릭해서 “선택한 날짜”(카드/To-Do가 이 날짜 기준으로 바뀜)
  • 단일 일정: 하루 안에서 끝나는 일정(점으로 표시)
  • 기간 일정: 시작~종료가 있는 일정(선으로 표시)
  • 임박 일정: 날짜 기준 3일 이내 일정(임박 뱃지 + D-day)
  • AI Draft: AI가 공고에서 추출해 만든 “임시 일정 세트”(CAL-M2에서 확인 후 저장)