import * as React from "react"; // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; type Data = { date: string; activities: any[]; }; // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; type MonthKey = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; type MonthLabel = Record; // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; type WeekdayKey = 0 | 1 | 2 | 3 | 4 | 5 | 6; // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; type WeekdayLabel = Record; // components // constants // types // import { Data } from "../types/Data"; // import { MonthLabel } from "../types/MonthLabel"; // import { WeekdayLabel } from "../types/WeekdayLabel"; // components type DaysProps = { // General props data: Data[]; daysToRender?: number; // Event Handler clickHandler?: Function; // General props - summary showSummary?: boolean; summaryText?: string; // General props - levels showLevels?: boolean; levelColorMode?: "light" | "dark"; levelColors?: string[]; levelLabelLess?: string; levelLabelMore?: string; // General props - tooltip showTooltip?: boolean; tooltipBgColor?: string; tooltipTextColor?: string; tooltipText?: string; // 'day' specific props weekStart?: WeekdayKey; showWeekdayLabels?: boolean; weekdayLabel?: WeekdayLabel; showMonthLabels?: boolean; monthLabel?: MonthLabel; }; // Util import CONSTANTS from "../constants/constants"; import Day from "./Day"; import Month from "./Month"; import Week from "./Week"; // Util const getSafeWeekStart = (weekStart) => Math.max(Math.min(weekStart ?? 0, 6), 0); // Day Block // Day Block const getDayDiffToStartOfCalendar = (weekStart) => { const todayDt = new Date(); const oneYearAgoDt = new Date(); oneYearAgoDt.setDate(todayDt.getDate() - 364); const oneYearAgoDayOfWeek = oneYearAgoDt.getDay(); const dayDiffToStartOfCalendar = oneYearAgoDayOfWeek - weekStart; return dayDiffToStartOfCalendar; }; const getContainerWidth = (weekStart, daysToRender, showWeekdayLabels) => { const daysContainerWidth = getDaysContainerWidth( getSafeWeekStart(weekStart), daysToRender ); return daysContainerWidth + (showWeekdayLabels ?? true ? 42 : 0) + "px"; }; const getDaysContainerWidth = (weekStart, daysToRender) => { const _weekStart = getSafeWeekStart(weekStart); const todayDayOfWeek = new Date().getDay(); let numOfDaysInLastColumn = 0; if (!_weekStart) { numOfDaysInLastColumn = todayDayOfWeek + 1; } else { numOfDaysInLastColumn = _weekStart > todayDayOfWeek ? todayDayOfWeek + 7 + 1 - _weekStart : todayDayOfWeek + 1 - _weekStart; } let numOfDaysInRemainingColumns = 0; if (!daysToRender && daysToRender !== 0) { const dayDiffToStartOfCalendar = getDayDiffToStartOfCalendar(_weekStart); numOfDaysInRemainingColumns = 365 + dayDiffToStartOfCalendar - numOfDaysInLastColumn; } else { numOfDaysInRemainingColumns = daysToRender - numOfDaysInLastColumn; } return Math.ceil(numOfDaysInRemainingColumns / 7 + 1) * 14; }; const getDayLevel = ({ numOfLevels, activitiesPerc }) => { for (let i = 1; i <= numOfLevels; i++) { if (activitiesPerc <= i / numOfLevels) { return i; } } }; const getDays = ({ levelColors, data, weekStart, daysToRender }) => { const _weekStart = getSafeWeekStart(weekStart); // 1. Init helper variables const numOfLevels = levelColors?.length ? levelColors.length - 1 : 4; let maxNumOfContributions = 0; const dataMap = new Map(); (data || []).forEach((day) => { maxNumOfContributions = Math.max( maxNumOfContributions, day?.activities?.length || 0 ); dataMap.set(day.date, day.activities); }); // 3. Merge the activities to days array return new Array( daysToRender ? daysToRender : 365 + getDayDiffToStartOfCalendar(_weekStart) ) .fill(0) .map((_, index) => { const dt = new Date(); dt.setDate(dt.getDate() - index); const year = dt.getFullYear(); const month = dt.getMonth() + 1; const day = dt.getDate(); const id = year + "-" + (month + "").padStart(2, "0") + "-" + (day + "").padStart(2, "0"); const activities = dataMap.get(id) || []; const activitiesPerc = activities.length / maxNumOfContributions; const level = activities.length === 0 ? 0 : getDayLevel({ numOfLevels, activitiesPerc, }); return { id, year, month, day, dayOfWeek: dt.getDay(), dayDiffFromToday: index, activities, level, }; }); }; const getDayColor = ({ level, levelColors, levelColorMode }) => { const _levelColors = getLevelColors({ levelColors, levelColorMode, }); return _levelColors[level]; }; const getDayTop = (arg) => { const { dayOfWeek = 0, weekStart = 0 } = arg; const _weekStart = getSafeWeekStart(weekStart); if (dayOfWeek - _weekStart < 0) { return (dayOfWeek - _weekStart + 7) * 14; } return (dayOfWeek - _weekStart) * 14; }; const getDayRight = (arg) => { const { dayDiffFromToday = 0, dayOfWeek = 0, weekStart = 0 } = arg; const todayDayOfWeek = new Date().getDay(); return ( (Math.floor(dayDiffFromToday / 7) + (dayOfWeek > todayDayOfWeek ? 1 : 0) + (dayOfWeek < getSafeWeekStart(weekStart) ? 1 : 0) + (weekStart > todayDayOfWeek ? -1 : 0)) * 14 ); }; // Week Block // Week Block const getWeekdayLabels = (weekdayLabel?: WeekdayLabel, weekStart = 0) => { const _weekStart = getSafeWeekStart(weekStart); let _weekdayLabels: string[] = []; for (let i = 0; i < 7; i++) { let weekStartId = _weekStart + i >= 7 ? _weekStart + i - 7 : _weekStart + i; if (weekdayLabel) { // If `weekdayLabel` is available // => only render `weekdayLabel` value _weekdayLabels.push(weekdayLabel[weekStartId] || ""); } else if ([1, 3, 5].includes(i)) { // Otherwise, render only the 2nd, 4th, 6th value _weekdayLabels.push(CONSTANTS.WEEK_MAP[weekStartId]); } else { _weekdayLabels.push(""); } } return _weekdayLabels; }; // Month Block // Month Block const getMonthRight = (arg) => { // day's right position - width + day's width return getDayRight(arg) - 36 + 12; }; const getMonthLabel = ({ monthLabel, dt, CONSTANTS }) => { return monthLabel?.[dt.month] ?? CONSTANTS.MONTH_MAP[dt.month]; }; // Summary Block // Summary Block const getSummary = ({ summaryText, levelColors, data, weekStart, daysToRender, }) => { const days = getDays({ levelColors, data, weekStart, daysToRender, }); const count = days.reduce((acc, cur) => { return acc + cur.activities.length; }, 0); if (summaryText) { return summaryText.replaceAll("{{count}}", count); } return `${count} activities in this period`; }; // Level Block 1000 -> // Level Block 1000 -> const getLevelColors = ({ levelColors, levelColorMode }) => { return ( levelColors ?? (levelColorMode === "dark" ? CONSTANTS.LEVEL_COLOR.dark : CONSTANTS.LEVEL_COLOR.light) ); }; function Days(props: DaysProps) { return (
{props.showMonthLabels ?? true ? (
) : null}
{props.showWeekdayLabels ?? true ? (
{getWeekdayLabels(props.weekdayLabel, props.weekStart)?.map( (dayOfWeek, index) => ( ) )}
) : null}
{getDays({ data: props.data ?? [], levelColors: props.levelColors, weekStart: props.weekStart, daysToRender: props.daysToRender, })?.map((dt, index) => (
{(props.showMonthLabels ?? true) && dt.day === 1 ? ( ) : null} {})} dt={dt} color={getDayColor({ level: dt.level, levelColors: props.levelColors, levelColorMode: props.levelColorMode, })} top={ getDayTop({ dayOfWeek: dt.dayOfWeek, weekStart: props.weekStart, }) + "px" } right={ getDayRight({ dayDiffFromToday: dt.dayDiffFromToday, dayOfWeek: dt.dayOfWeek, weekStart: props.weekStart, }) + "px" } showTooltip={props.showTooltip ?? true} tooltipBgColor={props.tooltipBgColor} tooltipTextColor={props.tooltipTextColor} tooltipText={props.tooltipText} activities={dt.activities} year={dt.year} month={dt.month} day={dt.day} />
))}
{props.showSummary ?? true ? (
{getSummary({ summaryText: props.summaryText, levelColors: props.levelColors, data: props.data, weekStart: props.weekStart, daysToRender: props.daysToRender, })}
) : null} {props.showLevels ?? true ? (
{props.levelLabelLess ?? "Less"}
{getLevelColors({ levelColors: props.levelColors, levelColorMode: props.levelColorMode, })?.map((levelLabel, index) => (
))}
{props.levelLabelMore ?? "More"}
) : null}
); } export default Days;