import { useCallback, useEffect, useRef, useState } from "react"; import { useAppSelector } from "../store"; import { locationService } from "../services"; import { DAILY_TIMESTAMP } from "../helpers/consts"; import * as utils from "../helpers/utils"; import "../less/usage-heat-map.less"; const tableConfig = { width: 12, height: 7, }; const getInitialUsageStat = (usedDaysAmount: number, beginDayTimestemp: number): DailyUsageStat[] => { const initialUsageStat: DailyUsageStat[] = []; for (let i = 1; i <= usedDaysAmount; i++) { initialUsageStat.push({ timestamp: beginDayTimestemp + DAILY_TIMESTAMP * i, count: 0, }); } return initialUsageStat; }; interface DailyUsageStat { timestamp: number; count: number; } interface Props {} const UsageHeatMap: React.FC = () => { const todayTimeStamp = utils.getDateStampByDate(Date.now()); const todayDay = new Date(todayTimeStamp).getDay() + 1; const nullCell = new Array(7 - todayDay).fill(0); const usedDaysAmount = (tableConfig.width - 1) * tableConfig.height + todayDay; const beginDayTimestemp = todayTimeStamp - usedDaysAmount * DAILY_TIMESTAMP; const { memos } = useAppSelector((state) => state.memo); const [allStat, setAllStat] = useState(getInitialUsageStat(usedDaysAmount, beginDayTimestemp)); const [popupStat, setPopupStat] = useState(null); const [currentStat, setCurrentStat] = useState(null); const containerElRef = useRef(null); const popupRef = useRef(null); useEffect(() => { const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestemp); for (const m of memos) { const index = (utils.getDateStampByDate(m.createdTs) - beginDayTimestemp) / (1000 * 3600 * 24) - 1; if (index >= 0) { newStat[index].count += 1; } } setAllStat([...newStat]); }, [memos]); const handleUsageStatItemMouseEnter = useCallback((event: React.MouseEvent, item: DailyUsageStat) => { setPopupStat(item); if (!popupRef.current) { return; } const bounding = utils.getElementBounding(event.target as HTMLElement); popupRef.current.style.left = bounding.left + "px"; popupRef.current.style.top = bounding.top - 4 + "px"; }, []); const handleUsageStatItemMouseLeave = useCallback(() => { setPopupStat(null); }, []); const handleUsageStatItemClick = useCallback((item: DailyUsageStat) => { if (locationService.getState().query?.duration?.from === item.timestamp) { locationService.setFromAndToQuery(); setCurrentStat(null); } else if (item.count > 0) { if (!["/"].includes(locationService.getState().pathname)) { locationService.setPathname("/"); } locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP); setCurrentStat(item); } }, []); return (
Sun Tue Thu Sat
{popupStat?.count} memos on {new Date(popupStat?.timestamp as number).toDateString()}
{allStat.map((v, i) => { const count = v.count; const colorLevel = count <= 0 ? "" : count <= 1 ? "stat-day-L1-bg" : count <= 2 ? "stat-day-L2-bg" : count <= 4 ? "stat-day-L3-bg" : "stat-day-L4-bg"; return ( handleUsageStatItemMouseEnter(e, v)} onMouseLeave={handleUsageStatItemMouseLeave} onClick={() => handleUsageStatItemClick(v)} > ); })} {nullCell.map((_, i) => ( ))}
); }; export default UsageHeatMap;