UNPKG

@amaui/ui-react

Version:
640 lines (639 loc) 24.6 kB
import _extends from "@babel/runtime/helpers/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["name", "date", "dateDefault", "times", "events", "meta", "views", "onUpdate", "onRemove", "onChangeDate", "startHeader", "endHeader", "startLeft", "endLeft", "startRight", "endRight", "startLeftModal", "endLeftModal", "startRightModal", "endRightModal", "Component", "IconEdit", "IconPrevious", "IconNext", "IconRemove", "IconClose", "WeekProps", "DayProps", "IconProps", "IconButtonProps", "className", "children"]; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import React from 'react'; import { capitalize, cleanValue, is, textToInnerHTML } from '@amaui/utils'; import { style as styleMethod, classNames, useAmauiTheme, colors } from '@amaui/style-react'; import { AmauiDate, add, endOf, format, remove, set, startOf } from '@amaui/date'; import IconMaterialEdit from '@amaui/icons-material-rounded-react/IconMaterialEditW100'; import IconMaterialKeyboardArrowDown from '@amaui/icons-material-rounded-react/IconMaterialKeyboardArrowDownW100'; import IconMaterialArrowForwardIos from '@amaui/icons-material-rounded-react/IconMaterialArrowForwardIosW100'; import IconMaterialArrowBackIosNew from '@amaui/icons-material-rounded-react/IconMaterialArrowBackIosNewW100'; import IconMaterialDelete from '@amaui/icons-material-rounded-react/IconMaterialDeleteW100'; import CalendarWeekElement from '../CalendarWeek'; import SelectElement from '../Select'; import ButtonElement from '../Button'; import LineElement from '../Line'; import ModalElement from '../Modal'; import ModalHeaderElement from '../ModalHeader'; import ModalMainElement from '../ModalMain'; import TypeElement from '../Type'; import TooltipElement from '../Tooltip'; import IconButtonElement from '../IconButton'; import LabelElement from '../Label'; import SlideElement from '../Slide'; import SwitchElement from '../Switch'; import { formats, staticClassName } from '../utils'; const useStyle = styleMethod(theme => ({ root: { padding: '16px', paddingBottom: '24px', color: theme.methods.palette.color.value('primary', 10), background: theme.palette.background.default.primary, '& .amaui-Label-text': { whiteSpace: 'nowrap' } }, calendar: { padding: '12px 8px', background: theme.palette.background.default.primary }, aside: { width: 'auto', maxWidth: '100%' }, weekDay: { width: '47px', height: '47px', borderRadius: '50%' }, today: { background: theme.palette.color.primary[40], color: '#fff' }, palettePreview: { width: '17px', height: '17px', boxShadow: theme.palette.light ? '0px 1px 1px 0px rgba(0, 0, 0, 0.07), 0px 2px 1px -1px rgba(0, 0, 0, 0.04), 0px 1px 3px 0px rgba(0, 0, 0, 0.11)' : '0px 1px 1px 0px rgba(255, 255, 255, 0.21), 0px 2px 1px -1px rgba(255, 255, 255, 0.12), 0px 1px 3px 0px rgba(255, 255, 255, 0.40)', borderRadius: '50%', cursor: 'default', flex: '0 0 auto', transition: theme.methods.transitions.make('transform'), '& > *': { width: '100% !important', height: 'calc(100% + 12px) !important' }, '&:active': { transform: 'scale(0.94)' } }, dayWeekSimple: { maxWidth: 184 }, simpleTimes: { '&.amaui-Line-direction-row': { '& > *': { flex: '1 1 auto' } }, '&.amaui-Line-direction-column': { '& > *': { width: '100%' } } }, legend: { padding: '2px', alignSelf: 'center', maxWidth: '100%', overflow: 'auto hidden' }, itemLegend: { cursor: 'pointer', userSelect: 'none', opacity: 0.5, transition: theme.methods.transitions.make(['opacity', 'transform']), '&:active': { transform: 'scale(0.94)' } }, itemLegendActive: { opacity: 1 }, overflowX: { padding: '2px 0', overflow: 'auto hidden' } }), { name: 'amaui-CalendarAvailability' }); const CalendarAvailability = /*#__PURE__*/React.forwardRef((props_, ref) => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiCalendarAvailability?.props?.default), props_), [props_]); const Line = React.useMemo(() => theme?.elements?.Line || LineElement, [theme]); const Type = React.useMemo(() => theme?.elements?.Type || TypeElement, [theme]); const Tooltip = React.useMemo(() => theme?.elements?.Tooltip || TooltipElement, [theme]); const IconButton = React.useMemo(() => theme?.elements?.IconButton || IconButtonElement, [theme]); const Label = React.useMemo(() => theme?.elements?.Label || LabelElement, [theme]); const Switch = React.useMemo(() => theme?.elements?.Switch || SwitchElement, [theme]); const Modal = React.useMemo(() => theme?.elements?.Modal || ModalElement, [theme]); const ModalHeader = React.useMemo(() => theme?.elements?.ModalHeader || ModalHeaderElement, [theme]); const ModalMain = React.useMemo(() => theme?.elements?.ModalMain || ModalMainElement, [theme]); const Slide = React.useMemo(() => theme?.elements?.Slide || SlideElement, [theme]); const Button = React.useMemo(() => theme?.elements?.Button || ButtonElement, [theme]); const Select = React.useMemo(() => theme?.elements?.Select || SelectElement, [theme]); const CalendarWeek = React.useMemo(() => theme?.elements?.CalendarWeek || CalendarWeekElement, [theme]); const { name, date: date_, dateDefault, times: timesProps, events, meta, views: viewsProps = ['week', 'day', 'simple'], onUpdate, onRemove, onChangeDate: onChangeDateProps, startHeader, endHeader, startLeft, endLeft, startRight, endRight, startLeftModal, endLeftModal, startRightModal, endRightModal, Component = Line, IconEdit = IconMaterialEdit, IconPrevious = IconMaterialArrowBackIosNew, IconNext = IconMaterialArrowForwardIos, IconRemove = IconMaterialDelete, IconClose = IconMaterialKeyboardArrowDown, WeekProps, DayProps, IconProps, IconButtonProps, className, children } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [now, setNow] = React.useState(new AmauiDate()); const [date, setDate] = React.useState(dateDefault || date_ || new AmauiDate()); const [view, setView] = React.useState('week'); const [displayTime, setDisplayTime] = React.useState(true); const [modal, setModal] = React.useState(); const [statuses, setStatuses] = React.useState({}); const refs = { date: React.useRef(date), displayTime: React.useRef(displayTime), interval: React.useRef(undefined), calendar: 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 onStatusToggle = React.useCallback(function () { let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'working'; setStatuses(previous => _objectSpread(_objectSpread({}, previous), {}, { [value]: previous[value] === undefined ? false : !previous[value] })); }, []); const rangeShade = theme.palette.light ? 70 : 40; React.useEffect(() => { // 1 minute refs.interval.current = setInterval(() => { setNow(new AmauiDate()); }, 60 * 1e3); return () => { clearInterval(refs.interval.current); }; }, []); // Date React.useEffect(() => { if (date_ !== undefined && date_ !== date) setDate(date_); }, [date_]); const onOpen = React.useCallback(item => { setModal(_objectSpread(_objectSpread({}, item), {}, { open: true })); }, []); const onClose = React.useCallback(() => { setModal(item => _objectSpread(_objectSpread({}, item), {}, { open: false })); }, []); const onChangeDisplayTime = React.useCallback(valueNew => { setDisplayTime(valueNew); }, []); const onChangeView = React.useCallback(valueNew => { setView(valueNew); }, []); const optionsStatus = React.useMemo(() => { return [{ name: 'Working', value: 'working' }, { name: 'Not working', value: 'not-working' }, { name: 'On a break', value: 'break' }, { name: 'Scheduled', value: 'pending' }, { name: 'Rescheduled', value: 'rescheduled' }, { name: 'Cancelled', value: 'canceled' }, { name: 'Other', value: 'other' }]; }, []); const onToday = React.useCallback(() => { const valueNew = new AmauiDate(); setDate(valueNew); if (is('function', onChangeDateProps)) onChangeDateProps(valueNew); }, [onChangeDateProps]); const onPrevious = React.useCallback(() => { let valueNew = new AmauiDate(); setDate(previous => { valueNew = remove(1, ['week', 'simple'].includes(view) ? 'week' : view, previous); return valueNew; }); if (is('function', onChangeDateProps)) onChangeDateProps(valueNew); }, [view, onChangeDateProps]); const onNext = React.useCallback(() => { let valueNew = new AmauiDate(); setDate(previous => { valueNew = add(1, ['week', 'simple'].includes(view) ? 'week' : view, previous); return valueNew; }); if (is('function', onChangeDateProps)) onChangeDateProps(valueNew); }, [view, onChangeDateProps]); const getDates = React.useCallback(available => { const values = available.dates?.values || []; return values.map(item => { if (item.entire) { if (item.from) { let from = new AmauiDate(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; }); }, [times]); const getDatesWeek = React.useCallback(available => { const weekFrom = startOf(date, 'week'); const weekTo = endOf(date, 'week'); return getDates(available).filter(item => { const from = new AmauiDate(item.from); const to = new AmauiDate(item.to); return !(from.milliseconds >= weekTo.milliseconds || to.milliseconds <= weekFrom.milliseconds); }); }, [date, getDates]); const getColor = React.useCallback(item => { let palette = theme.palette.color.neutral; if (item?.status === 'working') palette = theme.palette.color.success; if (item?.status === 'not-working') palette = theme.palette.color.info; if (item?.status === 'break') palette = theme.palette.color.warning; if (item?.status === 'pending') palette = theme.methods.color(colors.yellow[50]); if (item?.status === 'rescheduled') palette = theme.methods.color(colors.purple[50]); if (item?.status === 'canceled') palette = theme.palette.color.error; if (item?.status === 'other') palette = theme.palette.color.neutral; return palette[rangeShade]; }, [rangeShade, colors, theme]); const itemToText = React.useCallback(item => { if (item === 'pending') return 'scheduled'; if (item === 'not-count-workout-session') return `don't count workout session`; return item; }, []); const viewOptions = React.useMemo(() => { return viewsProps?.map(item => ({ name: capitalize(item), value: item })); }, [viewsProps]); const days = React.useMemo(() => { const weekStartDate = set(4, 'hour', startOf(date, 'week')); return Array.from({ length: 7 }).map((_, index) => add(index, 'day', weekStartDate)); }, [date]); const simpleTimesUI = () => { return /*#__PURE__*/React.createElement(Line, { gap: 3, direction: { default: 'row', 1400: 'column' }, className: classes.simpleTimes, fullWidth: true }, days.map((itemDay, index) => { const values = times.filter(item => item.weekly.days[index + 1]?.active).flatMap(item => item.weekly.days[index + 1].values).filter(item => item && [undefined, true].includes(refs.statuses.current[item.status || 'working'])); values.sort((a, b) => b.from > a.from ? -1 : 1); return /*#__PURE__*/React.createElement(Line, { key: index, gap: 1.5, direction: "column" }, /*#__PURE__*/React.createElement(Line, { gap: 0, align: "center", fullWidth: true }, /*#__PURE__*/React.createElement(Type, { version: "h3", weight: 400 }, format(itemDay, 'dd')), /*#__PURE__*/React.createElement(Line, { align: "center", justify: "center", className: classNames([classes.weekDay, itemDay.year === now.year && itemDay.dayYear === now.dayYear && classes.today]) }, /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 200 }, format(itemDay, 'DD.MM.')))), /*#__PURE__*/React.createElement(Line, { gap: 2, fullWidth: true }, !!values.length ? values.map((itemValue, indexItem) => { const itemValueFrom = new AmauiDate(itemValue.from); const itemValueTo = new AmauiDate(itemValue.to); return /*#__PURE__*/React.createElement(Line, { key: indexItem, gap: 0.5, className: classes.dayWeekSimple }, /*#__PURE__*/React.createElement(Line, { gap: 1, direction: "row", align: "center" }, /*#__PURE__*/React.createElement(Line, { className: classes.palettePreview, style: { background: getColor(itemValue) } }), /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 300 }, format(itemValueFrom, 'hh:mm a'), " \u2014 ", format(itemValueTo, 'hh:mm a'))), itemValue.description && /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 200, whiteSpace: "pre-wrap", className: classNames([classes.timeDescription, !refs.displayTime.current && 'amaui-work-day-time']), dangerouslySetInnerHTML: { __html: textToInnerHTML(itemValue.description) } })); }) : /*#__PURE__*/React.createElement(Type, { version: "b2" }, "No information for this day"))); })); }; const simpleExceptionsUI = () => { const items = times.flatMap(item => getDatesWeek(item)); if (!items.length) return /*#__PURE__*/React.createElement(Type, { version: "b1" }, "No exceptions this week"); return /*#__PURE__*/React.createElement(React.Fragment, null, items.map((itemValue, index) => { const day = set(index + 1, 'dayWeek'); const itemValueFrom = new AmauiDate(itemValue.from); const itemValueTo = new AmauiDate(itemValue.to); return /*#__PURE__*/React.createElement(Line, { key: index, gap: 1, direction: "column" }, /*#__PURE__*/React.createElement(Line, { gap: 1, direction: "row", align: "center", fullWidth: true }, /*#__PURE__*/React.createElement(Line, { className: classes.palettePreview, style: { background: getColor(itemValue) } }), /*#__PURE__*/React.createElement(Line, { gap: 1, direction: "row", align: "center" }, /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 400 }, format(day, 'dd')), /*#__PURE__*/React.createElement(Type, { version: "b2" }, format(itemValueFrom, formats.entire), " \u2014 ", format(itemValueTo, formats.entire)))), itemValue.description && /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 200, whiteSpace: "pre-wrap", className: classNames([classes.timeDescription, !refs.displayTime.current && 'amaui-work-day-time']), dangerouslySetInnerHTML: { __html: textToInnerHTML(itemValue.description) } })); })); }; const formattedDate = React.useMemo(() => { if (view === 'day') return format(date, `MMMM DD, YYYY`); if (['week', 'simple'].includes(view)) return `${format(startOf(date, 'week'), `MMM DD, YYYY`)}${format(endOf(date, 'week'), `MMM DD, YYYY`)}`; }, [view, date]); const legend = React.useMemo(() => { return /*#__PURE__*/React.createElement(Line, { rowGap: 1.5, direction: "row", align: "center", className: classes.legend }, optionsStatus.map((item, index) => /*#__PURE__*/React.createElement(Line, { key: index, gap: 1, direction: "row", align: "center", onClick: () => onStatusToggle(item.value), flexNo: true, className: classNames([classes.itemLegend, [undefined, true].includes(refs.statuses.current[item.value || 'working']) && classes.itemLegendActive]) }, /*#__PURE__*/React.createElement(Line, { className: classes.palettePreview, style: { background: getColor({ status: item.value }) } }), /*#__PURE__*/React.createElement(Type, { version: "b2" }, item.name)))); }, [theme, statuses, optionsStatus]); return /*#__PURE__*/React.createElement(Component, _extends({ ref: ref, flex: true, fullWidth: true, className: classNames([staticClassName('CalendarAvailability', theme) && ['amaui-CalendarAvailability-root'], className, classes.root]) }, other), /*#__PURE__*/React.createElement(Line, { gap: 1, fullWidth: true, className: classNames([staticClassName('CalendarAvailability', theme) && ['amaui-CalendarAvailability-header']]) }, startHeader, /*#__PURE__*/React.createElement(Line, { gap: 2, direction: "row", wrap: "wrap", justify: "space-between", align: "center", fullWidth: true }, /*#__PURE__*/React.createElement(Line, { gap: 1.5, direction: "row", wrap: "wrap", align: "center", className: classes.aside }, startLeft, /*#__PURE__*/React.createElement(Button, { color: "inherit", version: "outlined", size: "small", onClick: onToday, selected: now.days === date.days }, "Today"), /*#__PURE__*/React.createElement(Line, { gap: 0, direction: "row", align: "center" }, /*#__PURE__*/React.createElement(Tooltip, { name: `Previous ${view}` }, /*#__PURE__*/React.createElement(IconButton, _extends({ onClick: onPrevious }, IconButtonProps), /*#__PURE__*/React.createElement(IconPrevious, { size: "regular" }))), /*#__PURE__*/React.createElement(Tooltip, { name: `Next ${view}` }, /*#__PURE__*/React.createElement(IconButton, _extends({ onClick: onNext }, IconButtonProps), /*#__PURE__*/React.createElement(IconNext, { size: "regular" })))), /*#__PURE__*/React.createElement(Type, { version: "h2", weight: 500, whiteSpace: "nowrap" }, formattedDate), endLeft), /*#__PURE__*/React.createElement(Line, { gap: 1.5, direction: "row", align: "center", flexNo: true, className: classNames([classes.aside, classes.overflowX]) }, startRight, ['week', 'day'].includes(view) && /*#__PURE__*/React.createElement(Label, { value: displayTime, onChange: onChangeDisplayTime }, /*#__PURE__*/React.createElement(Switch, null), "Display time"), /*#__PURE__*/React.createElement(Select, { name: "View", value: view, onChange: onChangeView, options: viewOptions, size: "small", MenuProps: { portal: true, size: 'regular' }, WrapperProps: { style: { width: 170, minWidth: 'unset' } }, style: { width: 170, minWidth: 'unset' } }), endRight)), endHeader), /*#__PURE__*/React.createElement(Line, { ref: refs.calendar, gap: 2, flex: true, fullWidth: true }, view === 'simple' && /*#__PURE__*/React.createElement(Line, { gap: 1, flex: true, fullWidth: true }, /*#__PURE__*/React.createElement(Line, { gap: 4, fullWidth: true, className: classNames([staticClassName('CalendarAvailability', theme) && ['amaui-CalendarAvailability-simple'], classes.calendar]) }, simpleTimesUI(), /*#__PURE__*/React.createElement(Line, { gap: 1.5 }, /*#__PURE__*/React.createElement(Type, { version: "t2" }, "Exceptions this week"), /*#__PURE__*/React.createElement(Line, { gap: 1 }, simpleExceptionsUI())))), ['week', 'day'].includes(view) && /*#__PURE__*/React.createElement(CalendarWeek, _extends({ onOpen: onOpen, date: date, displayTime: displayTime, statuses: statuses, times: times, events: events, day: view === 'day' }, WeekProps)), legend), times && /*#__PURE__*/React.createElement(Modal, { open: !!modal?.open, onClose: onClose, minWidth: "lg", TransitionComponent: Slide, size: "small" }, /*#__PURE__*/React.createElement(ModalHeader, { gap: 1, direction: "row", align: "center", justify: "space-between", fullWidth: true }, /*#__PURE__*/React.createElement(Line, { gap: 1, direction: "row", align: "center" }, startLeftModal, /*#__PURE__*/React.createElement(Line, { className: classes.palettePreview, style: { background: getColor(modal) } }), /*#__PURE__*/React.createElement(Type, { version: "b2", weight: 100 }, cleanValue(itemToText(modal?.status), { capitalize: true })), endLeftModal), /*#__PURE__*/React.createElement(Line, { gap: 1, direction: "row", align: "center" }, startRightModal, onUpdate && /*#__PURE__*/React.createElement(Tooltip, { name: "Update" }, /*#__PURE__*/React.createElement(IconButton, { onClick: () => { onUpdate(modal?.object); onClose(); } }, /*#__PURE__*/React.createElement(IconEdit, IconProps))), onRemove && /*#__PURE__*/React.createElement(Tooltip, { name: "Remove" }, /*#__PURE__*/React.createElement(IconButton, { onClick: () => { onRemove(modal?.object); onClose(); } }, /*#__PURE__*/React.createElement(IconRemove, IconProps))), /*#__PURE__*/React.createElement(Tooltip, { name: "Close" }, /*#__PURE__*/React.createElement(IconButton, { onClick: onClose }, /*#__PURE__*/React.createElement(IconClose, IconProps))), endRightModal)), /*#__PURE__*/React.createElement(ModalMain, { align: "flex-start", className: classes.modalMain }, /*#__PURE__*/React.createElement(Line, { gap: 1.5, fullWidth: true }, /*#__PURE__*/React.createElement(Type, { version: "l1", weight: 200 }, format(modal?.day, 'dd'), " ", format(new AmauiDate(modal?.from), modal?.weekly ? `hh:mm a` : formats.entire), " \u2014 ", format(new AmauiDate(modal?.to), modal?.weekly ? `hh:mm a` : formats.entire)), modal?.description && /*#__PURE__*/React.createElement(Type, { version: "b2", dangerouslySetInnerHTML: { __html: textToInnerHTML(modal.description) } }))))); }); CalendarAvailability.displayName = 'amaui-CalendarAvailability'; export default CalendarAvailability;