import React from 'react'; import { scaleTime } from 'd3-scale'; import { Slider, Rail, Handles, Tracks, Ticks, SliderModeFunction } from 'react-compound-slider'; import { format, addHours, startOfToday, endOfToday, differenceInMilliseconds, isBefore, isAfter, set, addMinutes } from 'date-fns'; import SliderRail from './components/SliderRail'; import Track from './components/Track'; import Tick from './components/Tick'; import Handle from './components/Handle'; import './styles/index.scss'; const getTimelineConfig = (timelineStart, timelineLength) => date => { const percent = (differenceInMilliseconds(date, timelineStart) / timelineLength) * 100; const value = Number(format(date, 'T')); return { percent, value }; }; const getFormattedBlockedIntervals = (blockedDates: any[] = [], [startTime, endTime]) => { if (!blockedDates.length) { return null; } const timelineLength = differenceInMilliseconds(endTime, startTime); const getConfig = getTimelineConfig(startTime, timelineLength); const formattedBlockedDates = blockedDates.map((interval, index) => { let { start, end } = interval; if (isBefore(start, startTime)) { start = startTime; } if (isAfter(end, endTime)) { end = endTime; } const source = getConfig(start); const target = getConfig(end); return { id: `blocked-track-${index}`, source, target }; }); return formattedBlockedDates; }; const getNowConfig = ([startTime, endTime]) => { const timelineLength = differenceInMilliseconds(endTime, startTime); const getConfig = getTimelineConfig(startTime, timelineLength); const source = getConfig(new Date()); const target = getConfig(addMinutes(new Date(), 1)); return { id: 'now-track', source, target }; }; function TimeRange(props: TimeRangeProps) { const getDisabledIntervals = () => { return getFormattedBlockedIntervals(props.disabledIntervals, props.timelineInterval); }; const getNow = () => { return getNowConfig(props.timelineInterval); }; const onChange = (newTime: ReadonlyArray) => { const formattedNewTime: [Date, Date] = [new Date(newTime[0]), new Date(newTime[1])]; props.onChangeCallback(formattedNewTime); }; const checkIsSelectedIntervalNotValid = ([start, end], source, target) => { const { value: startInterval } = source; const { value: endInterval } = target; if ((startInterval > start && endInterval <= end) || (startInterval >= start && endInterval < end)) { return true; } if (start >= startInterval && end <= endInterval) { return true; } const isStartInBlockedInterval = start > startInterval && start < endInterval && end >= endInterval; const isEndInBlockedInterval = end < endInterval && end > startInterval && start <= startInterval; return isStartInBlockedInterval || isEndInBlockedInterval; }; const onUpdate = newTime => { const { onUpdateCallback } = props; const disabledIntervals = getDisabledIntervals(); if (disabledIntervals?.length) { const isValuesNotValid = disabledIntervals.some(({ source, target }) => checkIsSelectedIntervalNotValid(newTime, source, target) ); onUpdateCallback({ error: isValuesNotValid }); return; } onUpdateCallback({ error: false }); }; const getDateTicks = () => { const { timelineInterval, ticksNumber } = props; return scaleTime() .domain(timelineInterval) .ticks(ticksNumber) .map(t => +t); }; const { sliderRailClassName, timelineInterval, selectedInterval, containerClassName, error, step, showNow, formatTick, mode } = props; const domain = timelineInterval.map(t => Number(t)); return (
+t) || []} rootStyle={{ position: 'relative', width: '100%' }} > {({ getRailProps }) => } {({ handles, getHandleProps }) => ( <> {handles.map(handle => ( ))} )} {({ tracks, getTrackProps }) => ( <> {tracks?.map(({ id, source, target }) => ( ))} )} {getDisabledIntervals()?.length && ( {({ getTrackProps }) => ( <> {getDisabledIntervals()?.map(({ id, source, target }) => ( ))} )} )} {showNow && ( {({ getTrackProps }) => ( )} )} {({ ticks }) => ( <> {ticks.map(tick => ( ))} )}
); } export interface TimeRangeProps { onChangeCallback: (range: [Date, Date]) => void; onUpdateCallback: ({ error: boolean }) => void; ticksNumber?: number; selectedInterval?: [Date, Date]; timelineInterval: [Date, Date]; disabledIntervals?: [Date, Date]; containerClassName?: string; sliderRailClassName?: string; step: number; formatTick?: (ms: number) => string; error?: boolean; showNow?: boolean; mode?: number | SliderModeFunction; } TimeRange.defaultProps = { selectedInterval: [ set(new Date(), { minutes: 0, seconds: 0, milliseconds: 0 }), set(addHours(new Date(), 1), { minutes: 0, seconds: 0, milliseconds: 0 }) ], timelineInterval: [startOfToday(), endOfToday()], formatTick: ms => format(new Date(ms), 'HH:mm'), disabledIntervals: [], step: 1000 * 60 * 30, ticksNumber: 48, error: false, mode: 3 }; export default TimeRange;