diff --git a/src/entities/calendar-event/api/get-calendar-events-by-workspace-id.ts b/src/entities/calendar-event/api/get-calendar-events-by-workspace-id.ts index 0bf32b1..6137acc 100644 --- a/src/entities/calendar-event/api/get-calendar-events-by-workspace-id.ts +++ b/src/entities/calendar-event/api/get-calendar-events-by-workspace-id.ts @@ -7,7 +7,7 @@ export async function getCalendarEventsByWorkspaceId(workspaceId: string): Promi const supabase = getSupabaseBrowserClient(); const { data, error } = await supabase .from('calendar_events') - .select('id, workspace_id, title, starts_at, description') + .select('id, workspace_id, title, starts_at, description, event_type') .eq('workspace_id', workspaceId) .order('starts_at'); diff --git a/src/entities/calendar-event/model/calendar-event.mapper.ts b/src/entities/calendar-event/model/calendar-event.mapper.ts index 93823e1..27600be 100644 --- a/src/entities/calendar-event/model/calendar-event.mapper.ts +++ b/src/entities/calendar-event/model/calendar-event.mapper.ts @@ -6,6 +6,7 @@ export interface CalendarEventRow { title: string; starts_at: string; description: string | null; + event_type: 'meeting' | 'deadline'; } interface CalendarEventMetadata { @@ -119,6 +120,7 @@ export function toCalendarEvent(row: CalendarEventRow): CalendarEvent { date: formatIsoDateInKst(row.starts_at), time: normalizeCalendarEventTime(metadata.time), color: isCalendarEventColor(metadata.color) ? metadata.color : defaultColor, + eventType: row.event_type, }; } diff --git a/src/entities/calendar-event/model/calendar-event.types.ts b/src/entities/calendar-event/model/calendar-event.types.ts index 9a4014e..9399df2 100644 --- a/src/entities/calendar-event/model/calendar-event.types.ts +++ b/src/entities/calendar-event/model/calendar-event.types.ts @@ -8,6 +8,7 @@ export interface CalendarEvent { date: string; time: string | null; color: CalendarEventColor; + eventType: 'meeting' | 'deadline'; } export interface CalendarEventFormValues { diff --git a/src/entities/side-project/schedule-event/index.ts b/src/entities/side-project/schedule-event/index.ts index c9977ed..2457386 100644 --- a/src/entities/side-project/schedule-event/index.ts +++ b/src/entities/side-project/schedule-event/index.ts @@ -5,4 +5,7 @@ export { type ScheduleEventType, type CalendarMonth, } from './model/schedule-event.types'; -export { mockTodaySchedule, mockCalendar } from './model/schedule-event.mock'; +export { + buildCalendarMonthFromEvents, + selectTodayScheduleEvents, +} from './model/schedule-event.selectors'; diff --git a/src/entities/side-project/schedule-event/model/schedule-event.mock.ts b/src/entities/side-project/schedule-event/model/schedule-event.mock.ts deleted file mode 100644 index 2cec3ca..0000000 --- a/src/entities/side-project/schedule-event/model/schedule-event.mock.ts +++ /dev/null @@ -1,18 +0,0 @@ -// 오늘 일정·월간 캘린더 목데이터 -import type { CalendarMonth, ScheduleEvent } from './schedule-event.types'; - -export const mockTodaySchedule: ScheduleEvent[] = [ - { id: 'event-1', title: '디자인 리뷰 회의', time: '11:00', type: 'meeting' }, - { id: 'event-2', title: '팀 점심', time: '12:30', type: 'meeting' }, - { id: 'event-3', title: '스프린트 데일리', time: '14:00', type: 'meeting' }, - { id: 'event-4', title: '코드 리뷰', time: '15:00', type: 'meeting' }, - { id: 'event-5', title: 'API 명세 마감', time: '16:00', type: 'deadline' }, - { id: 'event-6', title: '회고 준비', time: '17:00', type: 'meeting' }, -]; - -export const mockCalendar: CalendarMonth = { - year: 2025, - month: 7, - today: 30, - eventDays: [2, 5, 8, 10, 14, 22, 28], -}; diff --git a/src/entities/side-project/schedule-event/model/schedule-event.selectors.ts b/src/entities/side-project/schedule-event/model/schedule-event.selectors.ts new file mode 100644 index 0000000..ee7662b --- /dev/null +++ b/src/entities/side-project/schedule-event/model/schedule-event.selectors.ts @@ -0,0 +1,70 @@ +import type { CalendarEvent } from '@/entities/calendar-event'; +import { formatCalendarEventTimeLabel } from '@/entities/calendar-event'; + +import type { CalendarMonth, ScheduleEvent } from './schedule-event.types'; + +function formatIsoDate(date: Date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; +} + +function sortByTime(left: CalendarEvent, right: CalendarEvent) { + if (left.time === right.time) { + return left.title.localeCompare(right.title, 'ko'); + } + + if (!left.time) { + return 1; + } + + if (!right.time) { + return -1; + } + + return left.time.localeCompare(right.time); +} + +export function selectTodayScheduleEvents( + calendarEvents: CalendarEvent[], + currentDate = new Date(), +): ScheduleEvent[] { + const todayIsoDate = formatIsoDate(currentDate); + + return calendarEvents + .filter((event) => event.date === todayIsoDate) + .sort(sortByTime) + .map((event) => ({ + id: event.id, + title: event.title, + time: formatCalendarEventTimeLabel(event.time), + type: event.eventType, + })); +} + +export function buildCalendarMonthFromEvents( + calendarEvents: CalendarEvent[], + currentDate = new Date(), +): CalendarMonth { + const year = currentDate.getFullYear(); + const month = currentDate.getMonth() + 1; + const currentMonthPrefix = `${year}-${String(month).padStart(2, '0')}-`; + + const eventDays = Array.from( + new Set( + calendarEvents + .filter((event) => event.date.startsWith(currentMonthPrefix)) + .map((event) => Number(event.date.slice(-2))) + .filter((day) => Number.isInteger(day) && day > 0), + ), + ).sort((left, right) => left - right); + + return { + year, + month, + today: currentDate.getDate(), + eventDays, + }; +} diff --git a/src/features/manage-progress-chart/ui/ProgressChartView.tsx b/src/features/manage-progress-chart/ui/ProgressChartView.tsx index b90cfaf..c0f30c4 100644 --- a/src/features/manage-progress-chart/ui/ProgressChartView.tsx +++ b/src/features/manage-progress-chart/ui/ProgressChartView.tsx @@ -71,7 +71,9 @@ function OverallProgressCard({ function AssigneeBarChartCard({ items }: { items: ProgressChartAssigneeItem[] }) { const maxValue = Math.max(...items.map((item) => item.count), 0); - const gridValues = [0, 2, 4, 6, 8]; + const gridStep = maxValue <= 4 ? 1 : Math.ceil(maxValue / 4); + const gridMaxValue = Math.max(gridStep * 4, 4); + const gridValues = Array.from({ length: 5 }, (_, index) => index * gridStep); return (