feat: 전사 공유 일정(주간 캘린더) 기능 추가

상단 nav 독립 메뉴 '일정' — 주간 타임라인(간트형) 캘린더.
카테고리=레인, 일정=막대(다중일 지원), 우측 상세 패널, 일정/카테고리 CRUD.

백엔드(Schedule 모듈):
- ScheduleCategory/ScheduleEvent 엔티티 + 참석자(다대다) + 작성자
- 카테고리/일정 CRUD, 기간 겹침 조회, 참석자 풀(전사 사용자)
- 기본 카테고리 12종 onModuleInit 멱등 시드
- 인증 사용자 누구나 관리(전역 일정, 글로벌 관리자 개념 없음)
- 운영 마이그레이션(AddSchedule) 추가, @nestjs/schedule 임포트는 CronModule 로 alias

프론트:
- /schedule 라우트 + 사이드바 nav 항목
- 주간 네비(이전/다음/오늘), 유형 필터·카테고리 관리 드롭다운
- 타임라인/상세 패널/일정 모달/카테고리 모달(hue 슬라이더)/참석자 멀티셀렉트
- schedule.store + useSchedule + scheduleDate 유틸

런타임 검증: dev 부팅·시드(12건)·라우트 매핑 정상, 인증 게이트(401),
일정 생성/조회/수정/삭제 + 참석자/전직원/다중일 스모크 전부 정상.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 21:23:10 +09:00
parent 62df92e6eb
commit 346ec75553
22 changed files with 3889 additions and 5 deletions
+73
View File
@@ -0,0 +1,73 @@
import { useApi } from './useApi'
import type {
ApiScheduleCategory,
ApiScheduleEvent,
ApiSchedulePerson,
ScheduleCategoryPayload,
ScheduleEventPayload,
} from '@/types/schedule'
// 일정 도메인 API Composable — 인터셉터가 success/data 를 언래핑
export function useSchedule() {
const api = useApi()
// 카테고리
async function listCategories(): Promise<ApiScheduleCategory[]> {
return (await api.get('/schedule/categories')) as unknown as ApiScheduleCategory[]
}
async function createCategory(
payload: ScheduleCategoryPayload,
): Promise<ApiScheduleCategory> {
return (await api.post('/schedule/categories', payload)) as unknown as ApiScheduleCategory
}
async function updateCategory(
id: string,
payload: Partial<ScheduleCategoryPayload>,
): Promise<ApiScheduleCategory> {
return (await api.patch(`/schedule/categories/${id}`, payload)) as unknown as ApiScheduleCategory
}
async function deleteCategory(id: string): Promise<void> {
await api.delete(`/schedule/categories/${id}`)
}
// 참석자 풀
async function listPeople(): Promise<ApiSchedulePerson[]> {
return (await api.get('/schedule/people')) as unknown as ApiSchedulePerson[]
}
// 일정
async function listEvents(
start: string,
end: string,
): Promise<ApiScheduleEvent[]> {
return (await api.get('/schedule/events', {
params: { start, end },
})) as unknown as ApiScheduleEvent[]
}
async function createEvent(
payload: ScheduleEventPayload,
): Promise<ApiScheduleEvent> {
return (await api.post('/schedule/events', payload)) as unknown as ApiScheduleEvent
}
async function updateEvent(
id: string,
payload: Partial<ScheduleEventPayload>,
): Promise<ApiScheduleEvent> {
return (await api.patch(`/schedule/events/${id}`, payload)) as unknown as ApiScheduleEvent
}
async function deleteEvent(id: string): Promise<void> {
await api.delete(`/schedule/events/${id}`)
}
return {
listCategories,
createCategory,
updateCategory,
deleteCategory,
listPeople,
listEvents,
createEvent,
updateEvent,
deleteEvent,
}
}