UNPKG

@onesy/ui-react

Version:
544 lines (541 loc) 19.8 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["date", "times", "events", "onOpen", "onTimeClick", "render", "statuses", "displayTime", "day", "className", "children"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React from 'react'; import { cleanValue, is, textToInnerHTML } from '@onesy/utils'; import { style as styleMethod, classNames, useOnesyTheme, colors } from '@onesy/style-react'; import { OnesyDate, add, endOf, format, set, startOf } from '@onesy/date'; import LineElement from '../Line'; import TypeElement from '../Type'; import TooltipElement from '../Tooltip'; import { formats, staticClassName } from '../utils'; import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; const useStyle = styleMethod(theme => ({ root: { color: theme.methods.palette.color.value('primary', 10), background: theme.palette.background.default.primary, paddingLeft: '12px' }, weekDay: { width: '44px', height: '44px', borderRadius: '50%' }, today: { background: theme.palette.color.primary[40], color: '#fff' }, days: { overflow: 'auto hidden' }, day: { minWidth: '184px', height: '134vh', width: 'calc((100% / 7) - 106px)' }, dayBody: { position: 'relative', borderLeft: `1px solid ${theme.palette.light ? '#dadada' : '#575757'}` }, dayHeader: { height: '78px', padding: '4px' }, hours: { position: 'sticky', top: '0px', left: '0px', width: '44px', height: '134vh', flex: '0 0 auto', marginRight: '8px', background: theme.palette.background.default.primary, zIndex: 14 }, hourWrapper: { position: 'relative' }, hour: { width: '50px', alignSelf: 'flex-start', left: '0px', position: 'absolute', top: '0', marginTop: '-8px', zIndex: '40', borderRadius: theme.methods.shape.radius.value(0.5) }, guide: { position: 'absolute', left: '-9px', height: '1px', width: 'calc(100% + 9px)', background: theme.palette.light ? '#dadada' : '#575757' }, guidelineHour: { position: 'absolute', left: '0px', height: '2px', transform: 'translateY(-50%)', background: theme.palette.color.tertiary[50], zIndex: 14, '&::before': { position: 'absolute', content: "''", width: '12px', height: '12px', borderRadius: '50%', background: theme.palette.color.tertiary[50], top: '-5px', left: '-6.5px' } }, range: { position: 'absolute', left: 0, right: 0, cursor: 'pointer', zIndex: 1, transition: theme.methods.transitions.make('box-shadow'), overflow: 'hidden', '& > *': { whiteSpace: 'pre-wrap', transition: theme.methods.transitions.make('opacity') }, '& > .onesy-work-day-time': { opacity: 0 }, '&:hover': { zIndex: 14, boxShadow: theme.shadows.values.default[2], '& > .onesy-work-day-time': { opacity: 1 } } }, time: { position: 'relative', zIndex: 1, padding: '0px 2px', borderRadius: 2 } }), { name: 'onesy-CalendarWeek' }); const CalendarWeek = props_ => { const theme = useOnesyTheme(); const l = theme.l; const props = _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyCalendarWeek?.props?.default), props_); const Line = theme?.elements?.Line || LineElement; const Type = theme?.elements?.Type || TypeElement; const Tooltip = theme?.elements?.Tooltip || TooltipElement; const { date, times: timesProps, events, onOpen, onTimeClick, render, statuses = {}, displayTime = true, day: dayProp, className, children } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [now, setNow] = React.useState(new OnesyDate()); const refs = { date: React.useRef(date), displayTime: React.useRef(displayTime), interval: React.useRef(undefined), days: React.useRef({}), overlaping: React.useRef({}), statuses: React.useRef(statuses) }; refs.date.current = date; refs.displayTime.current = displayTime; refs.statuses.current = statuses; const times = React.useMemo(() => { if (events) { return [{ dates: { active: true, values: (is('array', events) ? events : [events]).filter(Boolean) } }]; } return (is('array', timesProps) ? timesProps : [timesProps]).filter(Boolean); }, [events, timesProps]); const rangeShade = theme.palette.light ? 60 : 40; React.useEffect(() => { // 1 minute refs.interval.current = setInterval(() => { setNow(new OnesyDate()); }, 60 * 1e3); return () => { clearInterval(refs.interval.current); }; }, []); const getDates = available => { const values = available.dates?.values || []; return values.map(item => { if (item.entire) { if (item.from) { let from = new OnesyDate(item.from); let to; if (['day', 'week', 'month', 'year'].includes(item.entire)) from = startOf(from, 'day'); if (item.entire === 'minute') from = startOf(from, 'minute'); if (item.entire === 'hour') from = startOf(from, 'hour'); to = endOf(from, item.entire); item.from = from.milliseconds; item.to = to.milliseconds; } } return item; }); }; const optionsStatus = React.useMemo(() => { return [{ name: l('Working'), value: 'working' }, { name: l('Not working'), value: 'not-working' }, { name: l('On a break'), value: 'break' }, { name: l('Scheduled'), value: 'pending' }, { name: l('Rescheduled'), value: 'rescheduled' }, { name: l('Cancelled'), value: 'canceled' }, { name: l('Other'), value: 'other' }]; }, []); const getColor = item_0 => { let palette = theme.palette.color.neutral; if (item_0?.status === 'working') palette = theme.palette.color.success; if (item_0?.status === 'not-working') palette = theme.palette.color.info; if (item_0?.status === 'break') palette = theme.palette.color.warning; if (item_0?.status === 'pending') palette = theme.methods.color(colors.yellow[50]); if (item_0?.status === 'rescheduled') palette = theme.methods.color(colors.purple[50]); if (item_0?.status === 'canceled') palette = theme.palette.color.error; if (item_0?.status === 'other') palette = theme.palette.color.neutral; if (item_0.color) palette = theme.methods.color(item_0.color); return palette[rangeShade]; }; const itemToText = item_1 => { if (item_1 === 'pending') return l('Scheduled'); if (item_1 === 'not-count-workout-session') return l(`Don't count workout session`); return optionsStatus?.find(itemStatus => itemStatus.value === item_1)?.name ?? l(item_1); }; const renderTimes = (day, valuesAll, weekly = true, itemDay) => { if (itemDay !== undefined && !itemDay?.active) return null; if (weekly) { const ends_at = itemDay?.ends_at ? new OnesyDate(itemDay.ends_at) : undefined; if (ends_at) { const day_StartDay = startOf(day, 'day'); const ends_at_StartDay = startOf(ends_at, 'day'); if (day_StartDay.milliseconds >= ends_at_StartDay.milliseconds) return null; } } const dayStartOfDay = startOf(day, 'day'); const dayDate = format(day, formats.date); const values_0 = valuesAll?.filter(item_2 => { const from_0 = new OnesyDate(item_2.from); const fromStartOfDay = startOf(from_0, 'day'); const to_0 = new OnesyDate(item_2.to); const toStartOfDay = startOf(to_0, 'day'); return [undefined, true].includes(refs.statuses.current[item_2.status || 'working']) && (weekly ? weekly : // from (dayDate === format(from_0, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay.milliseconds) && ( // to dayDate === format(to_0, formats.date) || dayStartOfDay.milliseconds < toStartOfDay.milliseconds)); }); const elements = []; const renderTo = valueNew => { if (valueNew === '23:59') return '24:00'; return valueNew; }; values_0?.forEach((item_3, index) => { if (!(item_3.from && item_3.to)) return; let from_1 = new OnesyDate(item_3.from); let to_1 = new OnesyDate(item_3.to); if (!weekly) { const fromStartOfDay_0 = startOf(from_1, 'day'); const toStartOfDay_0 = startOf(to_1, 'day'); if (dayStartOfDay?.milliseconds > fromStartOfDay_0?.milliseconds && (dayDate === format(to_1, formats.date) || dayStartOfDay.milliseconds < toStartOfDay_0.milliseconds)) item_3.from = startOf(from_1, 'day').milliseconds; if ((dayDate === format(from_1, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay_0.milliseconds) && dayStartOfDay?.milliseconds < toStartOfDay_0?.milliseconds) item_3.to = endOf(to_1, 'day').milliseconds; } from_1 = new OnesyDate(item_3.from); to_1 = new OnesyDate(item_3.to); const itemDate = format(day, formats.date); const top = +(100 * ((from_1.hour * 60 + from_1.minute) / (24 * 60))).toFixed(4); const bottom = +(100 - 100 * ((to_1.hour * 60 + (to_1.minute === 59 ? 60 : to_1.minute)) / (24 * 60))).toFixed(4); if (!refs.days.current[itemDate]) refs.days.current[itemDate] = []; if (!refs.overlaping.current[itemDate]) refs.overlaping.current[itemDate] = []; const bottom_ = 100 - bottom; // intersections const overlaps = refs.days.current[itemDate].filter(([itemTop, itemBottom]) => { return !(top >= itemBottom || bottom_ <= itemTop); }); let level = 0; if (overlaps.length) { level = refs.overlaping.current[itemDate].filter(([itemTop_0, itemBottom_0]) => { return !(top >= itemBottom_0 || bottom_ <= itemTop_0); }).length + 1; refs.overlaping.current[itemDate].push([top, bottom_]); } refs.days.current[itemDate].push([top, bottom_]); const minimal = 100 - bottom - top < 5; const background = getColor(item_3); const WrapperElement = item_3.status ? Tooltip : React.Fragment; const WrapperElementProps = item_3.status ? { name: cleanValue(itemToText(item_3.status), { capitalize: true }), color: getColor(item_3) } : undefined; const itemProps = { onClick: event => onOpen(_objectSpread(_objectSpread({}, item_3), {}, { day, weekly }), event), className: classes.range }; const style = _objectSpread({ top: `${top}%`, bottom: `${bottom}%`, color: theme.methods.palette.color.text(background), background, left: `calc(0px + ${level * 10}px)` }, top === 0 && bottom === 0 && { border: 'none' }); const elementRendered = is('function', render) ? render(item_3, dayProp ? 'day' : 'week') : /*#__PURE__*/_jsxs(Line, _objectSpread(_objectSpread({ gap: 0.5, align: "center", justify: "center" }, itemProps), {}, { children: [!minimal && refs.displayTime.current && /*#__PURE__*/_jsxs(Type, { version: "l2", weight: 300, align: "center", className: classes.time, style: { background: getColor(item_3) }, children: [format(from_1, 'hh:mm a', { l }), " - ", renderTo(format(to_1, 'hh:mm a', { l }))] }), item_3.description && /*#__PURE__*/_jsx(Type, { version: "b2", dangerouslySetInnerHTML: { __html: textToInnerHTML(item_3.description) }, style: { maxHeight: 24, maxWidth: '90%' } })] }), index); elements.push(/*#__PURE__*/_jsx(WrapperElement, _objectSpread(_objectSpread({}, WrapperElementProps), {}, { children: /*#__PURE__*/React.cloneElement(elementRendered, _objectSpread(_objectSpread({}, itemProps), {}, { className: classNames([itemProps.className, elementRendered.props?.className]), style: _objectSpread(_objectSpread({}, style), elementRendered.props?.style) })) }))); }); return elements; }; const onTimeClickMethod = (itemDay_0, event_0) => { const rect = event_0.currentTarget.getBoundingClientRect(); const relativeY = event_0.clientY - rect.top; const relativePercentage = relativeY / rect.height * 100; let timeDate = set(Math.floor(24 * (relativePercentage / 100)), 'hour', itemDay_0); // start of the hour timeDate = startOf(timeDate, 'hour'); onTimeClick?.(timeDate, dayProp ? 'day' : 'week', event_0); }; const timesUI = dayDate_0 => { // clean up refs.days.current = {}; refs.overlaping.current = {}; const day_0 = dayDate_0.dayWeek === 0 ? 7 : dayDate_0.dayWeek; return /*#__PURE__*/_jsx(_Fragment, { children: times.map(item_4 => /*#__PURE__*/_jsxs(_Fragment, { children: [item_4.weekly && renderTimes(dayDate_0, item_4.weekly.days[day_0]?.values, true, item_4.weekly.days[day_0]), item_4.dates && renderTimes(dayDate_0, getDates(item_4), false)] })) }); }; const hours = React.useMemo(() => { return Array.from({ length: 24 }).map((item_5, index_0) => set(index_0, 'hour', date)); }, [date]); const days = React.useMemo(() => { const weekStartDate = set(4, 'hour', startOf(date, 'week')); return Array.from({ length: 7 }).map((_, index_1) => add(index_1, 'day', weekStartDate)); }, [date]); return /*#__PURE__*/_jsx(Line, _objectSpread(_objectSpread({ gap: 1.5, flex: true, fullWidth: true, className: classNames([staticClassName('CalendarWeek', theme) && ['onesy-CalendarWeek-root', dayProp && 'onesy-CalendarWeek-prop-day'], className, classes.root]) }, other), {}, { children: /*#__PURE__*/_jsxs(Line, { gap: 0, direction: "row", align: "stretch", flex: true, fullWidth: true, className: classes.days, children: [/*#__PURE__*/_jsxs(Line, { gap: 0, align: "center", fullWidth: true, className: classNames(['onesy-hours', classes.hours]), children: [/*#__PURE__*/_jsx(Line, { className: classes.dayHeader }), /*#__PURE__*/_jsx(Line, { gap: 0, flex: true, fullWidth: true, className: classes.hourWrapper, children: hours.map((itemHour, index_2) => /*#__PURE__*/_jsx(Line, { gap: 1, align: "unset", className: classes.hour, style: { top: `${100 / 24 * index_2}%` }, children: /*#__PURE__*/_jsx(Type, { version: "b3", whiteSpace: "nowrap", children: format(itemHour, 'h A', { l }) }) }, index_2)) })] }), dayProp && /*#__PURE__*/_jsxs(Line, { gap: 0, direction: "column", align: "center", flex: true, className: classNames(['onesy-day', classes.day]), children: [/*#__PURE__*/_jsxs(Line, { gap: 0, align: "center", fullWidth: true, className: classes.dayHeader, children: [/*#__PURE__*/_jsx(Type, { version: "l1", weight: 200, children: format(date, 'd', { l }) }), /*#__PURE__*/_jsx(Line, { align: "center", justify: "center", className: classNames([classes.weekDay, date.year === now.year && date.dayYear === now.dayYear && classes.today]), children: /*#__PURE__*/_jsx(Type, { version: "h3", weight: 400, align: "center", children: format(date, 'D', { l }) }) })] }), /*#__PURE__*/_jsxs(Line, { className: classes.dayBody, flex: true, fullWidth: true, onClick: event_1 => onTimeClickMethod(date, event_1), children: [timesUI(date), hours.map((itemGuide, indexGuide) => /*#__PURE__*/_jsx(Line, { className: classes.guide, fullWidth: true, style: { top: `${100 / 24 * indexGuide}%` } }, indexGuide)), format(now, formats.date) === format(date, formats.date) && /*#__PURE__*/_jsx(Line, { className: classes.guidelineHour, fullWidth: true, style: { top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%` } })] })] }), !dayProp && days.map((itemDay_1, index_3) => { return /*#__PURE__*/_jsxs(Line, { gap: 0, direction: "column", align: "center", flex: true, className: classNames(['onesy-day', classes.day]), children: [/*#__PURE__*/_jsxs(Line, { gap: 0, align: "center", fullWidth: true, className: classes.dayHeader, children: [/*#__PURE__*/_jsx(Type, { version: "l1", weight: 200, children: format(itemDay_1, 'd', { l }) }), /*#__PURE__*/_jsx(Line, { align: "center", justify: "center", className: classNames([classes.weekDay, itemDay_1.year === now.year && itemDay_1.dayYear === now.dayYear && classes.today]), children: /*#__PURE__*/_jsx(Type, { version: "h3", weight: 400, align: "center", children: format(itemDay_1, 'D', { l }) }) })] }), /*#__PURE__*/_jsxs(Line, { className: classes.dayBody, flex: true, fullWidth: true, onClick: event_2 => onTimeClickMethod(itemDay_1, event_2), children: [timesUI(itemDay_1), hours.map((itemGuide_0, indexGuide_0) => /*#__PURE__*/_jsx(Line, { className: classes.guide, fullWidth: true, style: { top: `${100 / 24 * indexGuide_0}%` } }, indexGuide_0)), format(now, formats.date) === format(itemDay_1, formats.date) && /*#__PURE__*/_jsx(Line, { className: classes.guidelineHour, fullWidth: true, style: { top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%` } })] })] }, index_3); })] }) })); }; CalendarWeek.displayName = 'onesy-CalendarWeek'; export default CalendarWeek;