import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import isToday from 'dayjs/plugin/isToday'; import localeData from 'dayjs/plugin/localeData'; import updateLocale from 'dayjs/plugin/updateLocale'; import { Button, ToggleField } from 'components'; import { useSettingsStore } from 'stores'; import { Wrapper, StyledLabel, StyledSubLabel, CalendarHeader, CalendarDays, CalendarBody, Date, Day, } from './calendarFieldStyle'; import localeImports from 'res/dayjs_locales'; dayjs.extend(isToday); dayjs.extend(localeData); dayjs.extend(updateLocale); const calculateMonth = (month, year, weekStart) => { const date = dayjs().month(month).year(year); const daysInMonth = date.daysInMonth(); const daysBefore = date.date(1).day() - weekStart; const daysAfter = 6 - date.date(daysInMonth).day() + weekStart; let dates = []; let curDate = date.date(1).subtract(daysBefore, 'day'); let y = 0; let x = 0; for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) { if (x === 0) dates[y] = []; dates[y][x] = curDate.clone(); curDate = curDate.add(1, 'day'); x++; if (x > 6) { x = 0; y++; } } return dates; }; const CalendarField = ({ label, subLabel, id, register, ...props }) => { const weekStart = useSettingsStore(state => state.weekStart); const { t, i18n } = useTranslation('home'); const [type, setType] = useState(0); const [dates, setDates] = useState(calculateMonth(dayjs().month(), dayjs().year(), weekStart)); const [month, setMonth] = useState(dayjs().month()); const [year, setYear] = useState(dayjs().year()); const [selectedDates, setSelectedDates] = useState([]); const [selectingDates, _setSelectingDates] = useState([]); const staticSelectingDates = useRef([]); const setSelectingDates = newDates => { staticSelectingDates.current = newDates; _setSelectingDates(newDates); }; const [selectedDays, setSelectedDays] = useState([]); const [selectingDays, _setSelectingDays] = useState([]); const staticSelectingDays = useRef([]); const setSelectingDays = newDays => { staticSelectingDays.current = newDays; _setSelectingDays(newDays); }; const startPos = useRef({}); const staticMode = useRef(null); const [mode, _setMode] = useState(staticMode.current); const setMode = newMode => { staticMode.current = newMode; _setMode(newMode); }; useEffect(() => { if (Object.keys(localeImports).includes(i18n.language)) { localeImports[i18n.language]().then(() => { dayjs.locale(i18n.language); if (weekStart !== dayjs.Ls[i18n.language].weekStart) { dayjs.updateLocale(i18n.language, { weekStart }); } }); } else { // Fallback if (weekStart !== dayjs.Ls.en.weekStart) { dayjs.updateLocale('en', { weekStart }); } } setDates(calculateMonth(month, year, weekStart)); }, [weekStart, month, year, i18n.language]); return ( {label && {label}} {subLabel && {subLabel}} setType(value === 'specific' ? 0 : 1)} /> {type === 0 ? ( <> {dayjs.months()[month]} {year} {(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map(name => {name} )} {dates.length > 0 && dates.map((dateRow, y) => dateRow.map((date, x) => { startPos.current = {x, y}; setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add'); setSelectingDates([date]); e.currentTarget.releasePointerCapture(e.pointerId); document.addEventListener('pointerup', () => { if (staticMode.current === 'add') { setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]); } else if (staticMode.current === 'remove') { const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY')); setSelectedDates(selectedDates.filter(d => !toRemove.includes(d))); } setMode(null); }, { once: true }); }} onPointerEnter={() => { if (staticMode.current) { let found = []; for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) { for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) { found.push({y: cy, x: cx}); } } setSelectingDates(found.map(d => dates[d.y][d.x])); } }} >{date.date()} ) )} ) : ( {(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map((name, i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart] === name} title={(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart] === name ? t('form.dates.tooltips.today') : ''} selected={selectedDays.includes(((i + weekStart) % 7 + 7) % 7)} selecting={selectingDays.includes(((i + weekStart) % 7 + 7) % 7)} mode={mode} onPointerDown={(e) => { startPos.current = i; setMode(selectedDays.includes(((i + weekStart) % 7 + 7) % 7) ? 'remove' : 'add'); setSelectingDays([((i + weekStart) % 7 + 7) % 7]); e.currentTarget.releasePointerCapture(e.pointerId); document.addEventListener('pointerup', () => { if (staticMode.current === 'add') { setSelectedDays([...selectedDays, ...staticSelectingDays.current]); } else if (staticMode.current === 'remove') { const toRemove = staticSelectingDays.current; setSelectedDays(selectedDays.filter(d => !toRemove.includes(d))); } setMode(null); }, { once: true }); }} onPointerEnter={() => { if (staticMode.current) { let found = []; for (let ci = Math.min(startPos.current, i); ci < Math.max(startPos.current, i)+1; ci++) { found.push(((ci + weekStart) % 7 + 7) % 7); } setSelectingDays(found); } }} >{name} )} )} ); }; export default CalendarField;