Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
2 changes: 2 additions & 0 deletions src/entities/calendar-event/model/calendar-event.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface CalendarEventRow {
title: string;
starts_at: string;
description: string | null;
event_type: 'meeting' | 'deadline';
}

interface CalendarEventMetadata {
Expand Down Expand Up @@ -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,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/entities/calendar-event/model/calendar-event.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface CalendarEvent {
date: string;
time: string | null;
color: CalendarEventColor;
eventType: 'meeting' | 'deadline';
}

export interface CalendarEventFormValues {
Expand Down
5 changes: 4 additions & 1 deletion src/entities/side-project/schedule-event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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}`;
}
Comment thread
JiWoongE marked this conversation as resolved.

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,
};
}
6 changes: 4 additions & 2 deletions src/features/manage-progress-chart/ui/ProgressChartView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="row-span-2 rounded-[22px] border border-[#e7eaff] bg-white px-6 pt-6 pb-5 shadow-[0_6px_20px_rgba(91,78,232,0.03)]">
Expand Down Expand Up @@ -106,7 +108,7 @@ function AssigneeBarChartCard({ items }: { items: ProgressChartAssigneeItem[] })
<div
className="w-full max-w-[18px] rounded-t-[7px] bg-[#ddd9ff]"
style={{
height: `${maxValue === 0 ? 0 : (item.count / maxValue) * 214}px`,
height: `${gridMaxValue === 0 ? 0 : (item.count / gridMaxValue) * 214}px`,
}}
/>
<span className="mt-3 text-[13px] font-medium tracking-[-0.03em] text-[#8f97bf]">
Expand Down
4 changes: 2 additions & 2 deletions src/views/dashboard/config/widget-catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const WIDGET_CATALOG = {
'today-schedule': {
layout: { i: 'today-schedule', x: 6, y: 9, w: 6, h: 4, minW: 2, minH: 3 },
title: '오늘 일정',
render: (size) => <TodaySchedule size={size} />,
render: (size, { workspaceId }) => <TodaySchedule workspaceId={workspaceId} size={size} />,
},
'overall-progress': {
layout: { i: 'overall-progress', x: 9, y: 10, w: 3, h: 5, minW: 3, minH: 4 },
Expand All @@ -87,7 +87,7 @@ export const WIDGET_CATALOG = {
calendar: {
layout: { i: 'calendar', x: 6, y: 13, w: 6, h: 8, minW: 4, minH: 6 },
title: '캘린더',
render: (size) => <Calendar size={size} />,
render: (size, { workspaceId }) => <Calendar workspaceId={workspaceId} size={size} />,
},
} satisfies Record<string, WidgetDefinition>;

Expand Down
4 changes: 0 additions & 4 deletions src/views/progress-chart/ui/ProgressChartPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
// side-project 진행률만 실 DB 연동 완료.
// 그 외 purpose는 manage-progress-chart 뷰(다른 담당자, mock 기반) — 실 API 연동 전이라
// workspaceId 'test' 하드코딩 상태. 실 연동은 해당 담당자 작업으로 남김.
// TODO(담당자): ProgressChartView 실 API 연동 + workspaceId={workspaceId} 전달
import { ProgressChartView } from '@/features/manage-progress-chart';
import { ProgressChartView as SideProjectProgressChartView } from '@/views/side-project/progress-chart';
import { plusJakartaSans } from '@/shared/lib/fonts';
Expand Down
28 changes: 24 additions & 4 deletions src/widgets/side-project/dashboard-calendar/ui/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use client';

// 캘린더 위젯 — 월간 달력. 오늘 날짜 강조 + 이벤트 점 표시.
// · sm: 오늘 날짜 + 이벤트 건수 요약
// · md/lg: 월간 그리드(요일 헤더 + 날짜 셀)
import { mockCalendar } from '@/entities/side-project/schedule-event';
import { useCalendarEventsByWorkspaceId } from '@/entities/calendar-event';
import { buildCalendarMonthFromEvents } from '@/entities/side-project/schedule-event';
import { cn } from '@/shared/lib/utils';
import type { WidgetSize } from '@/shared/dashboard/lib/widget-size';
import { WidgetStateMessage } from '@/shared/dashboard/ui/widget-state-message';
import { WidgetCard, WidgetCardAction, WidgetCardHeader } from '@/shared/dashboard/ui/widget-card';

const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'];
Expand All @@ -18,16 +22,32 @@ function buildMonthCells(year: number, month: number): (number | null)[] {
return cells;
}

export default function Calendar({ size = 'md' }: { size?: WidgetSize }) {
const { year, month, today, eventDays } = mockCalendar;

export default function Calendar({
workspaceId,
size = 'md',
}: {
workspaceId: string;
size?: WidgetSize;
}) {
const calendarEventsQuery = useCalendarEventsByWorkspaceId(workspaceId);
const { year, month, today, eventDays } = buildCalendarMonthFromEvents(
calendarEventsQuery.data ?? [],
);
const header = (
<WidgetCardHeader
title={`${month}월 캘린더`}
action={<WidgetCardAction>전체 보기</WidgetCardAction>}
/>
);

if (calendarEventsQuery.isPending) {
return <WidgetStateMessage header={header} message="캘린더를 불러오는 중..." />;
}

if (calendarEventsQuery.isError) {
return <WidgetStateMessage header={header} message="캘린더를 불러오지 못했습니다." />;
}

if (size === 'sm') {
return (
<WidgetCard>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
'use client';

// 오늘 일정 위젯 — 타일 크기에 따라 밀도가 다른 변형을 렌더
// · sm: 다음 일정 1건(액센트 바 + 제목 + 시간 + 외 N건)
// · md: 3건 리스트
// · lg: 전체 리스트 (일정 유형별 액센트 바 색상 — 마감=빨강)
import { mockTodaySchedule, SCHEDULE_TYPE_COLOR } from '@/entities/side-project/schedule-event';
import { useCalendarEventsByWorkspaceId } from '@/entities/calendar-event';
import {
selectTodayScheduleEvents,
SCHEDULE_TYPE_COLOR,
} from '@/entities/side-project/schedule-event';
import type { WidgetSize } from '@/shared/dashboard/lib/widget-size';
import { WidgetStateMessage } from '@/shared/dashboard/ui/widget-state-message';
import { WidgetCard, WidgetCardAction, WidgetCardHeader } from '@/shared/dashboard/ui/widget-card';

const header = (
<WidgetCardHeader title="오늘 일정" action={<WidgetCardAction>전체 보기</WidgetCardAction>} />
);

export default function TodaySchedule({ size = 'md' }: { size?: WidgetSize }) {
export default function TodaySchedule({
workspaceId,
size = 'md',
}: {
workspaceId: string;
size?: WidgetSize;
}) {
const calendarEventsQuery = useCalendarEventsByWorkspaceId(workspaceId);
const todaySchedule = selectTodayScheduleEvents(calendarEventsQuery.data ?? []);

if (calendarEventsQuery.isPending) {
return <WidgetStateMessage header={header} message="오늘 일정을 불러오는 중..." />;
}

if (calendarEventsQuery.isError) {
return <WidgetStateMessage header={header} message="오늘 일정을 불러오지 못했습니다." />;
}

if (todaySchedule.length === 0) {
return <WidgetStateMessage header={header} message="오늘 등록된 일정이 없습니다." />;
}

if (size === 'sm') {
const [next, ...rest] = mockTodaySchedule;
const [next, ...rest] = todaySchedule;
return (
<WidgetCard>
{header}
Expand All @@ -35,7 +63,7 @@ export default function TodaySchedule({ size = 'md' }: { size?: WidgetSize }) {
}

// md: 3건, lg: 전체
const events = size === 'md' ? mockTodaySchedule.slice(0, 3) : mockTodaySchedule;
const events = size === 'md' ? todaySchedule.slice(0, 3) : todaySchedule;
return (
<WidgetCard>
{header}
Expand Down