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:
@@ -0,0 +1,94 @@
|
||||
// 일정(주간 캘린더) 날짜 유틸 — 모두 'YYYY-MM-DD' 로컬 날짜 문자열 기준(시간대 이동 방지).
|
||||
|
||||
// 월요일 시작 요일 라벨(월~일)
|
||||
export const DOW_KO = ['월', '화', '수', '목', '금', '토', '일'] as const
|
||||
|
||||
// Date → 로컬 'YYYY-MM-DD'
|
||||
export function isoOf(date: Date): string {
|
||||
const y = date.getFullYear()
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(date.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
|
||||
// 'YYYY-MM-DD' → 로컬 Date(자정)
|
||||
export function dateOf(iso: string): Date {
|
||||
const [y, m, d] = iso.split('-').map(Number)
|
||||
return new Date(y ?? 1970, (m ?? 1) - 1, d ?? 1)
|
||||
}
|
||||
|
||||
// 오늘(로컬) ISO
|
||||
export function todayIso(): string {
|
||||
return isoOf(new Date())
|
||||
}
|
||||
|
||||
// iso 가 속한 주의 월요일 ISO
|
||||
export function mondayOf(iso: string): string {
|
||||
const dt = dateOf(iso)
|
||||
// getDay(): 0=일 … 6=토 → 월요일까지 거슬러 올라갈 일수
|
||||
const back = (dt.getDay() + 6) % 7
|
||||
dt.setDate(dt.getDate() - back)
|
||||
return isoOf(dt)
|
||||
}
|
||||
|
||||
// iso + n일
|
||||
export function addDays(iso: string, n: number): string {
|
||||
const dt = dateOf(iso)
|
||||
dt.setDate(dt.getDate() + n)
|
||||
return isoOf(dt)
|
||||
}
|
||||
|
||||
// 요일 인덱스(0=월 … 6=일)
|
||||
function dowIndex(iso: string): number {
|
||||
return (dateOf(iso).getDay() + 6) % 7
|
||||
}
|
||||
|
||||
export interface WeekDay {
|
||||
iso: string
|
||||
dow: string
|
||||
d: number
|
||||
weekend: boolean
|
||||
isToday: boolean
|
||||
}
|
||||
|
||||
// 주 시작(월요일) ISO → 7일 배열
|
||||
export function weekDays(weekStartIso: string): WeekDay[] {
|
||||
const today = todayIso()
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const iso = addDays(weekStartIso, i)
|
||||
return {
|
||||
iso,
|
||||
dow: DOW_KO[i] ?? '',
|
||||
d: dateOf(iso).getDate(),
|
||||
weekend: i >= 5,
|
||||
isToday: iso === today,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 주 범위 라벨 — 예: '2026년 6월 22 – 28일' / 월이 다르면 '6월 29 – 7월 5일'
|
||||
export function weekRangeLabel(weekStartIso: string): string {
|
||||
const s = dateOf(weekStartIso)
|
||||
const e = dateOf(addDays(weekStartIso, 6))
|
||||
const sm = s.getMonth() + 1
|
||||
const em = e.getMonth() + 1
|
||||
if (sm === em) {
|
||||
return `${s.getFullYear()}년 ${sm}월 ${s.getDate()} – ${e.getDate()}일`
|
||||
}
|
||||
return `${s.getFullYear()}년 ${sm}월 ${s.getDate()}일 – ${em}월 ${e.getDate()}일`
|
||||
}
|
||||
|
||||
// 단일/다중일 표시용 — '6월 22일 (월)' 또는 '6월 22일(월) – 24일(수)'
|
||||
export function eventDateText(start: string, end: string): string {
|
||||
const s = dateOf(start)
|
||||
if (start === end) {
|
||||
return `${s.getMonth() + 1}월 ${s.getDate()}일 (${DOW_KO[dowIndex(start)]})`
|
||||
}
|
||||
const e = dateOf(end)
|
||||
return `${s.getMonth() + 1}월 ${s.getDate()}일(${DOW_KO[dowIndex(start)]}) – ${e.getMonth() + 1}월 ${e.getDate()}일(${DOW_KO[dowIndex(end)]})`
|
||||
}
|
||||
|
||||
// 다중일 일수(포함 양끝)
|
||||
export function spanDays(start: string, end: string): number {
|
||||
return Math.round((dateOf(end).getTime() - dateOf(start).getTime()) / 86400000) + 1
|
||||
}
|
||||
Reference in New Issue
Block a user