diff --git a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx index 3ed0fff..032be5a 100644 --- a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx +++ b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsCalendar.tsx @@ -1,3 +1,5 @@ +"use client"; + import { StatisticsClockEmptyIcon, StatisticsClockFilledIcon, @@ -5,6 +7,7 @@ import { StatisticsClockOutlineIcon, } from "@repo/timo-design-system/icons"; import { cn } from "@repo/timo-design-system/utils"; +import { useLocale } from "next-intl"; import type { StatisticsCalendarResponse } from "@/app/[locale]/(main)/statistics/_types/statistics"; @@ -32,14 +35,20 @@ const STATUS_ICON = { filled: StatisticsClockFilledIcon, }; -const getIconStatus = (completionRate: number | null): CalendarIconStatus => { - if (completionRate === null) return "disabled"; - if (completionRate === 0) return "empty"; +const getIconStatus = ( + completionRate: number | null, + isFutureDate: boolean, +): CalendarIconStatus => { + if (isFutureDate) return "disabled"; + if (completionRate === null || completionRate === 0) return "empty"; if (completionRate < 50) return "outline"; if (completionRate < 100) return "light"; return "filled"; }; +const getDateTime = (date: Date) => + new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); + const WEEKDAYS = [ { label: "M", ariaLabel: "Monday" }, { label: "T", ariaLabel: "Tuesday" }, @@ -52,27 +61,33 @@ const WEEKDAYS = [ interface StatisticsCalendarProps { currentMonth: Date; + selectedDate: Date; + onSelectDate: (date: Date) => void; calendarData: StatisticsCalendarResponse; } export const StatisticsCalendar = ({ currentMonth, + selectedDate, + onSelectDate, calendarData, }: StatisticsCalendarProps) => { + const locale = useLocale(); const today = new Date(calendarData.today); const calendarDates = getCalendarDates(currentMonth); const firstDayOffset = getFirstDayOffset(currentMonth); const completionRateByDate = new Map( calendarData.days.map(({ date, completionRate }) => [date, completionRate]), ); - const todayLabel = formatStatisticsCalendarDate(today); + const todayLabel = formatStatisticsCalendarDate(today, locale); + const todayTime = getDateTime(today); return ( -
+

- {formatStatisticsMonth(currentMonth)} + {formatStatisticsMonth(currentMonth, locale)}

@@ -100,17 +115,39 @@ export const StatisticsCalendar = ({ {calendarDates.map((calendarDate) => { const dateKey = formatDateKey(calendarDate.date); + const isSelected = dateKey === formatDateKey(selectedDate); + const isFutureDate = getDateTime(calendarDate.date) > todayTime; const completionRate = completionRateByDate.get(dateKey) ?? null; - const status = getIconStatus(completionRate); + const status = getIconStatus(completionRate, isFutureDate); const Icon = STATUS_ICON[status]; return ( -

- - +
+ ); })}
diff --git a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsSidePanel.tsx b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsSidePanel.tsx index eec7b33..4b36fa8 100644 --- a/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsSidePanel.tsx +++ b/apps/timo-web/app/[locale]/(main)/statistics/_components/StatisticsSidePanel.tsx @@ -1,5 +1,8 @@ +"use client"; + import { TagIcon } from "@repo/timo-design-system/ui"; import { cn } from "@repo/timo-design-system/utils"; +import { useTranslations } from "next-intl"; import type { StatisticsDayDetail, @@ -10,7 +13,7 @@ import { SummaryTimeBlock } from "@/app/[locale]/(main)/statistics/_components/S import { formatStatisticsClockText, formatStatisticsHourText, -} from "@/app/[locale]/(main)/statistics/_utils/formatStatisticsTime"; +} from "@/app/[locale]/(main)/statistics/_utils/format-statistics-time"; interface StatisticsMonthSidePanelProps { variant: "month"; @@ -27,11 +30,14 @@ type StatisticsSidePanelProps = | StatisticsDaySidePanelProps; const SIDE_PANEL_CLASS_NAME = - "border-timo-gray-500 min-h-full w-76 border-l text-timo-black"; - -const getDiffLabel = (diffMinutes: number) => { - if (diffMinutes > 0) return "+초과"; - if (diffMinutes < 0) return "-단축"; + "border-timo-gray-500 min-h-full w-[304px] shrink-0 border-l text-timo-black"; + +const getDiffLabel = ( + diffMinutes: number, + t: ReturnType>, +) => { + if (diffMinutes > 0) return t("overtime"); + if (diffMinutes < 0) return t("shortened"); return ""; }; @@ -42,34 +48,36 @@ const getDiffClassName = (diffMinutes: number) => { }; export const StatisticsSidePanel = (props: StatisticsSidePanelProps) => { + const t = useTranslations("Statistics.sidePanel"); + if (props.variant === "month") { const { summary } = props; return ( -