diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 07c6d67e2230..e8039dfa2337 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -26,7 +26,7 @@ const config: Config.InitialOptions = { '^.+\\.(js|jsx)$': 'babel-jest', }, transformIgnorePatterns: [ - 'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)', + 'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)', ], setupFilesAfterEnv: ['jest.setup.ts'], testPathIgnorePatterns: ['/node_modules/', '/public/'], diff --git a/frontend/package.json b/frontend/package.json index dd5a9c16e38e..3f61dd568cf4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,11 +43,14 @@ "@radix-ui/react-tooltip": "1.0.7", "@sentry/react": "8.41.0", "@sentry/webpack-plugin": "2.22.6", + "@signozhq/calendar": "0.0.0", "@signozhq/design-tokens": "1.1.4", + "@signozhq/input": "0.0.2", + "@signozhq/popover": "0.0.0", "@tanstack/react-table": "8.20.6", "@tanstack/react-virtual": "3.11.2", - "@uiw/codemirror-theme-github": "4.24.1", "@uiw/codemirror-theme-copilot": "4.23.11", + "@uiw/codemirror-theme-github": "4.24.1", "@uiw/react-codemirror": "4.23.10", "@uiw/react-md-editor": "3.23.5", "@visx/group": "3.3.0", diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss index 022a193761ca..376e24d0b05a 100644 --- a/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss @@ -1,6 +1,16 @@ .custom-time-picker { display: flex; flex-direction: column; + + .timeSelection-input { + &:hover { + border-color: #1d212d !important; + } + } + + .time-input-suffix { + display: flex; + } } .time-options-container { @@ -135,6 +145,7 @@ align-items: center; color: var(--bg-vanilla-400); gap: 6px; + .timezone { display: flex; align-items: center; @@ -163,6 +174,27 @@ cursor: pointer; } +.time-input-suffix-icon-badge { + display: flex; + align-items: center; + justify-content: center; + padding: 0 4px; + border-radius: 2px; + background: rgba(171, 189, 255, 0.04); + color: var(--bg-vanilla-100); + font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.06px; + cursor: pointer; + height: 20px; + width: 20px; + + &:hover { + background: rgba(171, 189, 255, 0.08); + } +} + .lightMode { .date-time-popover__footer { border-color: var(--bg-vanilla-400); @@ -180,8 +212,26 @@ } } } + + .custom-time-picker { + .timeSelection-input { + &:hover { + border-color: var(--bg-vanilla-300) !important; + } + } + } + .timezone-badge { color: var(--bg-ink-100); background: rgb(179 179 179 / 15%); } + + .time-input-suffix-icon-badge { + color: var(--bg-ink-100); + background: rgb(179 179 179 / 15%); + + &:hover { + background: rgb(179 179 179 / 20%); + } + } } diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx index db21d6e107ba..e9da51d14679 100644 --- a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx @@ -5,13 +5,12 @@ import './CustomTimePicker.styles.scss'; import { Input, Popover, Tooltip, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; import cx from 'classnames'; +import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; import { - CustomTimeType, FixedDurationSuggestionOptions, Options, RelativeDurationSuggestionOptions, - Time, } from 'container/TopNav/DateTimeSelectionV2/config'; import dayjs from 'dayjs'; import { isValidTimeFormat } from 'lib/getMinMax'; @@ -28,7 +27,10 @@ import { useMemo, useState, } from 'react'; +import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { popupContainer } from 'utils/selectPopupContainer'; import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent'; @@ -58,10 +60,6 @@ interface CustomTimePickerProps { setCustomDTPickerVisible?: Dispatch>; onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void; handleGoLive?: () => void; - onTimeChange?: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; } function CustomTimePicker({ @@ -79,13 +77,16 @@ function CustomTimePicker({ setCustomDTPickerVisible, onCustomDateHandler, handleGoLive, - onTimeChange, }: CustomTimePickerProps): JSX.Element { const [ selectedTimePlaceholderValue, setSelectedTimePlaceholderValue, ] = useState('Select / Enter Time Range'); + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + const [inputValue, setInputValue] = useState(''); const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>(''); const [inputErrorMessage, setInputErrorMessage] = useState( @@ -256,6 +257,11 @@ function CustomTimePicker({ }; const handleSelect = (label: string, value: string): void => { + if (label === 'Custom') { + setCustomDTPickerVisible?.(true); + return; + } + onSelect(value); setSelectedTimePlaceholderValue(label); setInputStatus(''); @@ -318,84 +324,105 @@ function CustomTimePicker({ ); }; + const getTooltipTitle = (): string => { + if (selectedTime === 'custom' && inputValue === '' && !open) { + return `${dayjs(minTime / 1000_000) + .tz(timezone.value) + .format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs( + maxTime / 1000_000, + ) + .tz(timezone.value) + .format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)}`; + } + + return ''; + }; + return (
- - ) : ( - content - ) - } - arrow={false} - trigger="click" - open={open} - onOpenChange={handleOpenChange} - style={{ - padding: 0, - }} - > - + + ) : ( - - - + content ) } - suffix={ - <> - {!!isTimezoneOverridden && activeTimezoneOffset && ( -
- {activeTimezoneOffset} -
- )} - handleViewChange('datetime')} - /> - - } - /> -
- + arrow={false} + trigger="click" + open={open} + onOpenChange={handleOpenChange} + style={{ + padding: 0, + }} + > + + {inputValue && inputStatus === 'success' ? ( + + ) : ( + + + + )} +
+ } + suffix={ +
+ {!!isTimezoneOverridden && activeTimezoneOffset && ( +
+ {activeTimezoneOffset} +
+ )} + { + e.stopPropagation(); + handleViewChange('datetime'); + }} + /> +
+ } + /> + + {inputStatus === 'error' && inputErrorMessage && ( {inputErrorMessage} @@ -414,5 +441,4 @@ CustomTimePicker.defaultProps = { onCustomDateHandler: noop, handleGoLive: noop, onCustomTimeStatusUpdate: noop, - onTimeChange: undefined, }; diff --git a/frontend/src/components/CustomTimePicker/CustomTimePickerPopoverContent.tsx b/frontend/src/components/CustomTimePicker/CustomTimePickerPopoverContent.tsx index 6cfe2c8aac59..d28d3f1b8bad 100644 --- a/frontend/src/components/CustomTimePicker/CustomTimePickerPopoverContent.tsx +++ b/frontend/src/components/CustomTimePicker/CustomTimePickerPopoverContent.tsx @@ -4,21 +4,22 @@ import { Color } from '@signozhq/design-tokens'; import { Button } from 'antd'; import logEvent from 'api/common/logEvent'; import cx from 'classnames'; +import DatePickerV2 from 'components/DatePickerV2/DatePickerV2'; +import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; import ROUTES from 'constants/routes'; import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; import { - CustomTimeType, LexicalContext, Option, RelativeDurationSuggestionOptions, - Time, } from 'container/TopNav/DateTimeSelectionV2/config'; +import dayjs from 'dayjs'; import { Clock, PenLine } from 'lucide-react'; import { useTimezone } from 'providers/Timezone'; -import { Dispatch, SetStateAction, useMemo } from 'react'; +import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; +import { getCustomTimeRanges } from 'utils/customTimeRangeUtils'; -import RangePickerModal from './RangePickerModal'; import TimezonePicker from './TimezonePicker'; interface CustomTimePickerPopoverContentProps { @@ -37,10 +38,14 @@ interface CustomTimePickerPopoverContentProps { setActiveView: Dispatch>; isOpenedFromFooter: boolean; setIsOpenedFromFooter: Dispatch>; - onTimeChange?: ( - interval: Time | CustomTimeType, - dateTimeRange?: [number, number], - ) => void; +} + +interface RecentlyUsedDateTimeRange { + label: string; + value: number; + timestamp: number; + from: string; + to: string; } // eslint-disable-next-line sonarjs/cognitive-complexity @@ -57,16 +62,42 @@ function CustomTimePickerPopoverContent({ setActiveView, isOpenedFromFooter, setIsOpenedFromFooter, - onTimeChange, }: CustomTimePickerPopoverContentProps): JSX.Element { const { pathname } = useLocation(); const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [ pathname, ]); + const { timezone } = useTimezone(); const activeTimezoneOffset = timezone.offset; + const [recentlyUsedTimeRanges, setRecentlyUsedTimeRanges] = useState< + RecentlyUsedDateTimeRange[] + >([]); + + useEffect(() => { + if (!customDateTimeVisible) { + const customTimeRanges = getCustomTimeRanges(); + + const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] = customTimeRanges.map( + (range) => ({ + label: `${dayjs(range.from) + .tz(timezone.value) + .format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs(range.to) + .tz(timezone.value) + .format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)}`, + from: range.from, + to: range.to, + value: range.timestamp, + timestamp: range.timestamp, + }), + ); + + setRecentlyUsedTimeRanges(formattedCustomTimeRanges); + } + }, [customDateTimeVisible, timezone.value]); + function getTimeChips(options: Option[]): JSX.Element { return (
@@ -112,50 +143,77 @@ function CustomTimePickerPopoverContent({ return ( <>
-
- {isLogsExplorerPage && ( - - )} - {options.map((option) => ( - - ))} -
+ {!customDateTimeVisible && ( +
+ {isLogsExplorerPage && ( + + )} + {options.map((option) => ( + + ))} +
+ )} +
- {selectedTime === 'custom' || customDateTimeVisible ? ( - ) : ( -
-
RELATIVE TIMES
-
{getTimeChips(RelativeDurationSuggestionOptions)}
+
+
+
RELATIVE TIMES
+
{getTimeChips(RelativeDurationSuggestionOptions)}
+
+ +
+
RECENTLY USED
+
+ {recentlyUsedTimeRanges.map((range: RecentlyUsedDateTimeRange) => ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + onCustomDateHandler([dayjs(range.from), dayjs(range.to)]); + setIsOpen(false); + } + }} + key={range.value} + onClick={(): void => { + onCustomDateHandler([dayjs(range.from), dayjs(range.to)]); + setIsOpen(false); + }} + > + {range.label} +
+ ))} +
+
)}
@@ -189,8 +247,4 @@ function CustomTimePickerPopoverContent({ ); } -CustomTimePickerPopoverContent.defaultProps = { - onTimeChange: undefined, -}; - export default CustomTimePickerPopoverContent; diff --git a/frontend/src/components/DatePickerV2/DatePickerV2.styles.scss b/frontend/src/components/DatePickerV2/DatePickerV2.styles.scss new file mode 100644 index 000000000000..40f86cf29b83 --- /dev/null +++ b/frontend/src/components/DatePickerV2/DatePickerV2.styles.scss @@ -0,0 +1,114 @@ +.date-picker-v2-container { + display: flex; + flex-direction: row; +} + +.custom-date-time-picker-v2 { + padding: 12px; + + .periscope-calendar { + border-radius: 4px; + border: none !important; + background: none !important; + padding: 8px 0 !important; + } + + .periscope-calendar-day { + background: none !important; + + &.periscope-calendar-today { + &.text-accent-foreground { + color: var(--bg-vanilla-100) !important; + } + } + + button { + &:hover { + background-color: var(--bg-robin-500) !important; + color: var(--bg-vanilla-100) !important; + } + } + } + + .custom-time-selector { + display: flex; + flex-direction: row; + gap: 16px; + align-items: center; + justify-content: space-between; + + .time-input { + border-radius: 4px; + border: none !important; + background: none !important; + padding: 8px 4px !important; + color: var(--bg-vanilla-100) !important; + + &::-webkit-calendar-picker-indicator { + display: none !important; + -webkit-appearance: none; + appearance: none; + } + + &:focus { + border: none !important; + outline: none !important; + box-shadow: none !important; + } + + &:focus-visible { + border: none !important; + outline: none !important; + box-shadow: none !important; + } + } + } + + .custom-date-time-picker-footer { + display: flex; + flex-direction: row; + gap: 8px; + align-items: center; + justify-content: flex-end; + margin-top: 16px; + + .next-btn { + width: 80px; + } + + .clear-btn { + width: 80px; + } + } +} + +.invalid-date-range-tooltip { + .ant-tooltip-inner { + color: var(--bg-sakura-500) !important; + } +} + +.lightMode { + .custom-date-time-picker-v2 { + .periscope-calendar-day { + &.periscope-calendar-today { + &.text-accent-foreground { + color: var(--bg-ink-500) !important; + } + } + + button { + &:hover { + background-color: var(--bg-robin-500) !important; + color: var(--bg-ink-500) !important; + } + } + } + + .custom-time-selector { + .time-input { + color: var(--bg-ink-500) !important; + } + } + } +} diff --git a/frontend/src/components/DatePickerV2/DatePickerV2.tsx b/frontend/src/components/DatePickerV2/DatePickerV2.tsx new file mode 100644 index 000000000000..32f55573dbde --- /dev/null +++ b/frontend/src/components/DatePickerV2/DatePickerV2.tsx @@ -0,0 +1,311 @@ +import './DatePickerV2.styles.scss'; + +import { Calendar } from '@signozhq/calendar'; +import { Input } from '@signozhq/input'; +import { Button, Tooltip } from 'antd'; +import cx from 'classnames'; +import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; +import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config'; +import dayjs, { Dayjs } from 'dayjs'; +import { CornerUpLeft, MoveRight } from 'lucide-react'; +import { useTimezone } from 'providers/Timezone'; +import { useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { addCustomTimeRange } from 'utils/customTimeRangeUtils'; + +function DatePickerV2({ + onSetCustomDTPickerVisible, + setIsOpen, + onCustomDateHandler, +}: { + onSetCustomDTPickerVisible: (visible: boolean) => void; + setIsOpen: (isOpen: boolean) => void; + onCustomDateHandler: ( + dateTimeRange: DateTimeRangeType, + lexicalContext?: LexicalContext, + ) => void; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const timeInputRef = useRef(null); + + const { timezone } = useTimezone(); + + const [selectedDateTimeFor, setSelectedDateTimeFor] = useState<'to' | 'from'>( + 'from', + ); + + const [selectedFromDateTime, setSelectedFromDateTime] = useState( + dayjs(minTime / 1000_000).tz(timezone.value), + ); + + const [selectedToDateTime, setSelectedToDateTime] = useState( + dayjs(maxTime / 1000_000).tz(timezone.value), + ); + + const handleNext = (): void => { + if (selectedDateTimeFor === 'to') { + onCustomDateHandler([selectedFromDateTime, selectedToDateTime]); + + addCustomTimeRange([selectedFromDateTime, selectedToDateTime]); + + setIsOpen(false); + onSetCustomDTPickerVisible(false); + setSelectedDateTimeFor('from'); + } else { + setSelectedDateTimeFor('to'); + } + }; + + const handleDateChange = (date: Date | undefined): void => { + if (!date) { + return; + } + + if (selectedDateTimeFor === 'from') { + const prevFromDateTime = selectedFromDateTime; + + const newDate = dayjs(date); + + const updatedFromDateTime = prevFromDateTime + ? prevFromDateTime + .year(newDate.year()) + .month(newDate.month()) + .date(newDate.date()) + : dayjs(date).tz(timezone.value); + + setSelectedFromDateTime(updatedFromDateTime); + } else { + // eslint-disable-next-line sonarjs/no-identical-functions + setSelectedToDateTime((prev) => { + const newDate = dayjs(date); + + // Update only the date part, keeping time from existing state + return prev + ? prev.year(newDate.year()).month(newDate.month()).date(newDate.date()) + : dayjs(date).tz(timezone.value); + }); + } + + // focus the time input + timeInputRef?.current?.focus(); + }; + + const handleTimeChange = (time: string): void => { + // time should have format HH:mm:ss + if (!/^\d{2}:\d{2}:\d{2}$/.test(time)) { + return; + } + + if (selectedDateTimeFor === 'from') { + setSelectedFromDateTime((prev) => { + if (prev) { + return prev + .set('hour', parseInt(time.split(':')[0], 10)) + .set('minute', parseInt(time.split(':')[1], 10)) + .set('second', parseInt(time.split(':')[2], 10)); + } + + return prev; + }); + } + if (selectedDateTimeFor === 'to') { + // eslint-disable-next-line sonarjs/no-identical-functions + setSelectedToDateTime((prev) => { + if (prev) { + return prev + .set('hour', parseInt(time.split(':')[0], 10)) + .set('minute', parseInt(time.split(':')[1], 10)) + .set('second', parseInt(time.split(':')[2], 10)); + } + + return prev; + }); + } + }; + + const getDefaultMonth = (): Date => { + let defaultDate = null; + + if (selectedDateTimeFor === 'from') { + defaultDate = selectedFromDateTime?.toDate(); + } else if (selectedDateTimeFor === 'to') { + defaultDate = selectedToDateTime?.toDate(); + } + + return defaultDate ?? new Date(); + }; + + const isValidRange = (): boolean => { + if (selectedDateTimeFor === 'to') { + return selectedToDateTime?.isAfter(selectedFromDateTime) ?? false; + } + + return true; + }; + + const handleBack = (): void => { + setSelectedDateTimeFor('from'); + }; + + const handleHideCustomDTPicker = (): void => { + onSetCustomDTPickerVisible(false); + }; + + const handleSelectDateTimeFor = (selectedDateTimeFor: 'to' | 'from'): void => { + setSelectedDateTimeFor(selectedDateTimeFor); + }; + + return ( +
+
+
{ + if (e.key === 'Enter') { + handleHideCustomDTPicker(); + } + }} + > + + Back +
+ +
+
{ + if (e.key === 'Enter') { + handleSelectDateTimeFor('from'); + } + }} + className={cx( + 'date-time-custom-option-from', + selectedDateTimeFor === 'from' && 'active', + )} + onClick={(): void => { + handleSelectDateTimeFor('from'); + }} + > +
FROM
+
+ {selectedFromDateTime?.format('YYYY-MM-DD HH:mm:ss')} +
+
+
{ + if (e.key === 'Enter') { + handleSelectDateTimeFor('to'); + } + }} + className={cx( + 'date-time-custom-option-to', + selectedDateTimeFor === 'to' && 'active', + )} + onClick={(): void => { + handleSelectDateTimeFor('to'); + }} + > +
TO
+
+ {selectedToDateTime?.format('YYYY-MM-DD HH:mm:ss')} +
+
+
+
+
+ { + if (selectedDateTimeFor === 'to') { + // disable dates after today and before selectedFromDateTime + const currentDay = dayjs(current); + return currentDay.isAfter(dayjs()) || false; + } + + if (selectedDateTimeFor === 'from') { + // disable dates after selectedToDateTime + + return dayjs(current).isAfter(dayjs()) || false; + } + + return false; + }} + className="rounded-md border" + navLayout="after" + /> + +
+ + + + +
+ handleTimeChange(e.target.value)} + step="1" + /> +
+
+ +
+ {selectedDateTimeFor === 'to' && ( + + )} + + + +
+
+
+ ); +} + +export default DatePickerV2; diff --git a/frontend/src/constants/dateTimeFormats.ts b/frontend/src/constants/dateTimeFormats.ts index 7a81f767dcf8..e9a67701322c 100644 --- a/frontend/src/constants/dateTimeFormats.ts +++ b/frontend/src/constants/dateTimeFormats.ts @@ -39,6 +39,8 @@ export const DATE_TIME_FORMATS = { MONTH_DATETIME_SECONDS: 'MMM DD YYYY HH:mm:ss', MONTH_DATETIME_FULL: 'MMMM DD, YYYY HH:mm', MONTH_DATETIME_FULL_SECONDS: 'MMM DD, YYYY, HH:mm:ss', + DD_MMM_YYYY_HH_MM: 'DD MMM YYYY, HH:mm', + DD_MMM_YYYY_HH_MM_SS: 'DD MMM YYYY, HH:mm:ss', // Ordinal formats (1st, 2nd, 3rd, etc) ORDINAL_DATE: 'Do MMM YYYY', diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 030b2d62b120..5bdf89f26563 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -32,4 +32,5 @@ export enum LOCALSTORAGE { BANNER_DISMISSED = 'BANNER_DISMISSED', QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT', FUNNEL_STEPS = 'FUNNEL_STEPS', + LAST_USED_CUSTOM_TIME_RANGES = 'LAST_USED_CUSTOM_TIME_RANGES', } diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss index 0cdc9bafd57b..a2ff345dad2c 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss @@ -54,7 +54,7 @@ border: none; &.active-tab { - background-color: #1d212d; + background-color: var(--bg-slate-400); } } } @@ -146,20 +146,20 @@ display: flex; width: 224px; flex-direction: column; - border-right: 1px solid #1d212d; + border-right: 1px solid var(--bg-slate-400); .data-time-live { - border-bottom: 1px solid #1d212d; + border-bottom: 1px solid var(--bg-slate-400); text-align: start; padding: 13.5px 14px; height: 44px; - color: var(--bg-vanilla-400, #c0c1c3); - font-size: 14px; + color: var(--bg-vanilla-400); + font-size: 13px; font-style: normal; font-weight: 400; line-height: normal; letter-spacing: 0.14px; - border-bottom: 1px solid var(--bg-slate-400, #1d212d); + border-bottom: 1px solid var(--bg-slate-400); } .active { @@ -176,8 +176,8 @@ text-align: start; padding: 8px 13px; height: 37px; - color: var(--bg-vanilla-400, #c0c1c3); - font-size: 14px; + color: var(--bg-vanilla-400); + font-size: 13px; font-style: normal; font-weight: 400; line-height: normal; @@ -190,6 +190,80 @@ } } + .date-time-custom-options-container { + display: flex; + width: 224px; + flex-direction: column; + border-right: 1px solid var(--bg-slate-400); + + .back-btn { + display: flex; + align-items: center; + gap: 16px; + padding: 8px; + cursor: pointer; + + &:hover { + background-color: rgba(171, 189, 255, 0.04); + } + } + + .date-time-custom-options { + .date-time-custom-option-from, + .date-time-custom-option-to { + display: flex; + padding: 12px 14px; + flex-direction: column; + align-items: flex-start; + gap: 10px; + cursor: pointer; + + &.disabled { + cursor: not-allowed; + } + + .date-time-custom-option-from-title, + .date-time-custom-option-to-title { + color: var(--bg-slate-50); + font-variant-numeric: slashed-zero; + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 0.12px; + text-transform: uppercase; + } + + .date-time-custom-option-from-value, + .date-time-custom-option-to-value { + color: var(--bg-vanilla-400); + font-variant-numeric: slashed-zero; + font-family: Inter; + font-size: 13px; + font-style: normal; + font-weight: 300; + line-height: normal; + letter-spacing: 0.14px; + } + + &.active { + background: rgba(171, 189, 255, 0.04); + + .date-time-custom-option-from-title, + .date-time-custom-option-to-title { + color: var(--bg-vanilla-400); + } + + .date-time-custom-option-from-value, + .date-time-custom-option-to-value { + color: var(--bg-vanilla-100); + } + } + } + } + } + .relative-date-time { display: flex; flex-direction: column; @@ -197,12 +271,11 @@ padding: 13px 14px; &.date-picker { - width: 480px; - height: 430px; + padding: 0px; } &.relative-times { - width: 320px; + width: 360px; } .relative-date-time-section { @@ -210,9 +283,45 @@ gap: 6px; flex-flow: wrap; } + + .time-selector-container { + display: flex; + flex-direction: column; + gap: 32px; + } + + .recently-used-container { + display: flex; + flex-direction: column; + gap: 8px; + + .recently-used-range { + display: flex; + flex-direction: column; + gap: 8px; + + .recently-used-range-item { + color: var(--bg-vanilla-400); + background-color: var(--bg-ink-200); + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04em; + text-align: left; + border-radius: 2px; + padding: 8px; + cursor: pointer; + + &:hover { + background-color: var(--bg-ink-100); + color: var(--bg-vanilla-100); + } + } + } + } + .time-heading { text-align: left; - color: var(--bg-slate-200); + color: var(--bg-vanilla-300); font-size: 11px; font-style: normal; font-weight: 500; @@ -224,8 +333,8 @@ .time-btns { color: var(--bg-vanilla-400); background-color: #23262e; - font-size: 14px; - line-height: 17px; + font-size: 12px; + line-height: 16px; letter-spacing: 0.04em; text-align: left; border-radius: 2px; @@ -288,6 +397,54 @@ } } + .date-time-custom-options-container { + display: flex; + width: 224px; + flex-direction: column; + border-right: 1px solid var(--bg-vanilla-400); + + .back-btn { + display: flex; + align-items: center; + gap: 16px; + padding: 8px; + cursor: pointer; + + &:hover { + background-color: var(--bg-vanilla-300); + } + } + + .date-time-custom-options { + .date-time-custom-option-from, + .date-time-custom-option-to { + .date-time-custom-option-from-title, + .date-time-custom-option-to-title { + color: var(--bg-slate-50); + } + + .date-time-custom-option-from-value, + .date-time-custom-option-to-value { + color: var(--bg-slate-400); + } + + &.active { + background: var(--bg-vanilla-300); + + .date-time-custom-option-from-title, + .date-time-custom-option-to-title { + color: var(--bg-slate-400); + } + + .date-time-custom-option-from-value, + .date-time-custom-option-to-value { + color: var(--bg-slate-400); + } + } + } + } + } + .relative-date-time { .time-heading { color: var(--bg-vanilla-400); @@ -297,6 +454,20 @@ background-color: var(--bg-vanilla-300); color: var(--bg-slate-400); } + + .recently-used-container { + .recently-used-range { + .recently-used-range-item { + color: var(--bg-slate-400); + background-color: var(--bg-vanilla-300); + + &:hover { + background-color: var(--bg-vanilla-300); + color: var(--bg-slate-400); + } + } + } + } } } diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx index ef1ff875f87b..1e1aa2897b62 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx @@ -248,7 +248,7 @@ function DateTimeSelection({ timeInterval: Time | CustomTimeType = '15m', ): string | Time => { if (startTime && endTime && timeInterval === 'custom') { - const format = DATE_TIME_FORMATS.UK_DATETIME; + const format = DATE_TIME_FORMATS.UK_DATETIME_SECONDS; const startString = startTime.format(format); const endString = endTime.format(format); @@ -803,6 +803,17 @@ function DateTimeSelection({ const { timezone } = useTimezone(); + const getSelectedValue = (): string => + getInputLabel( + dayjs(isModalTimeSelection ? modalStartTime : minTime / 1000000).tz( + timezone.value, + ), + dayjs(isModalTimeSelection ? modalEndTime : maxTime / 1000000).tz( + timezone.value, + ), + isModalTimeSelection ? modalSelectedInterval : selectedTime, + ); + return (
{showResetButton && selectedTime !== defaultRelativeTime && ( @@ -859,15 +870,7 @@ function DateTimeSelection({ onCustomTimeStatusUpdate={(isValid: boolean): void => { setIsValidteRelativeTime(isValid); }} - selectedValue={getInputLabel( - dayjs(isModalTimeSelection ? modalStartTime : minTime / 1000000).tz( - timezone.value, - ), - dayjs(isModalTimeSelection ? modalEndTime : maxTime / 1000000).tz( - timezone.value, - ), - isModalTimeSelection ? modalSelectedInterval : selectedTime, - )} + selectedValue={getSelectedValue()} data-testid="dropDown" items={options} newPopover @@ -875,7 +878,6 @@ function DateTimeSelection({ onCustomDateHandler={onCustomDateHandler} customDateTimeVisible={customDateTimeVisible} setCustomDTPickerVisible={setCustomDTPickerVisible} - onTimeChange={onTimeChange} /> {showAutoRefresh && selectedTime !== 'custom' && ( diff --git a/frontend/src/hooks/useCustomTimeRanges.ts b/frontend/src/hooks/useCustomTimeRanges.ts new file mode 100644 index 000000000000..5cd5cad74228 --- /dev/null +++ b/frontend/src/hooks/useCustomTimeRanges.ts @@ -0,0 +1,112 @@ +import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; +import { useCallback, useEffect, useState } from 'react'; +import { + addCustomTimeRange as addRange, + clearCustomTimeRanges as clearRanges, + CustomTimeRange, + getCustomTimeRanges as getRanges, + removeCustomTimeRange as removeRange, +} from 'utils/customTimeRangeUtils'; + +/** + * Custom hook for managing the last 3 used custom time ranges + * @returns Object containing custom time ranges and management functions + */ +export const useCustomTimeRanges = (): { + customTimeRanges: CustomTimeRange[]; + addCustomTimeRange: (dateTimeRange: DateTimeRangeType) => CustomTimeRange[]; + removeCustomTimeRange: (timestamp: number) => CustomTimeRange[]; + clearCustomTimeRanges: () => void; + isCustomTimeRangeExists: (dateTimeRange: DateTimeRangeType) => boolean; + refreshCustomTimeRanges: () => void; +} => { + const [customTimeRanges, setCustomTimeRanges] = useState( + [], + ); + + // Always get the latest data from localStorage + const getLatestRanges = useCallback((): CustomTimeRange[] => getRanges(), []); + + // Load initial data from localStorage + useEffect(() => { + const ranges = getLatestRanges(); + setCustomTimeRanges(ranges); + }, [getLatestRanges]); + + /** + * Adds a new custom time range to the stored list + * @param dateTimeRange - The DateTimeRangeType from the time picker + * @returns The updated list of custom time ranges + */ + const addCustomTimeRange = useCallback( + (dateTimeRange: DateTimeRangeType): CustomTimeRange[] => { + const updatedRanges = addRange(dateTimeRange); + setCustomTimeRanges(updatedRanges); + return updatedRanges; + }, + [], + ); + + /** + * Removes a specific custom time range by its timestamp + * @param timestamp - The timestamp of the range to remove + * @returns The updated list of custom time ranges + */ + const removeCustomTimeRange = useCallback( + (timestamp: number): CustomTimeRange[] => { + const updatedRanges = removeRange(timestamp); + setCustomTimeRanges(updatedRanges); + return updatedRanges; + }, + [], + ); + + /** + * Clears all stored custom time ranges + */ + const clearCustomTimeRanges = useCallback((): void => { + clearRanges(); + setCustomTimeRanges([]); + }, []); + + /** + * Checks if a time range already exists in the stored list + * @param dateTimeRange - The DateTimeRangeType to check + * @returns True if the range already exists + */ + const isCustomTimeRangeExists = useCallback( + (dateTimeRange: DateTimeRangeType): boolean => { + if (!dateTimeRange || !dateTimeRange[0] || !dateTimeRange[1]) { + return false; + } + + const [startTime, endTime] = dateTimeRange; + // Always fetch latest data from localStorage + const latestRanges = getLatestRanges(); + return latestRanges.some( + (range) => + range.from === startTime.toISOString() && + range.to === endTime.toISOString(), + ); + }, + [getLatestRanges], + ); + + /** + * Refreshes the custom time ranges from localStorage + * Useful when you want to sync with changes made outside the hook + */ + const refreshCustomTimeRanges = useCallback((): void => { + const ranges = getLatestRanges(); + setCustomTimeRanges(ranges); + }, [getLatestRanges]); + + return { + customTimeRanges, + addCustomTimeRange, + removeCustomTimeRange, + clearCustomTimeRanges, + isCustomTimeRangeExists, + refreshCustomTimeRanges, + }; +}; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index d655e16551f4..b192ffd1cc0c 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -755,3 +755,7 @@ notifications - 2050 .service-map-container { padding: 0px 8px; } + +.cursor-pointer { + cursor: pointer; +} diff --git a/frontend/src/utils/customTimeRangeUtils.ts b/frontend/src/utils/customTimeRangeUtils.ts new file mode 100644 index 000000000000..0b3f570fe751 --- /dev/null +++ b/frontend/src/utils/customTimeRangeUtils.ts @@ -0,0 +1,175 @@ +import { LOCALSTORAGE } from 'constants/localStorage'; +import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; +import dayjs from 'dayjs'; + +export interface CustomTimeRange { + from: string; // ISO string format + to: string; // ISO string format + timestamp: number; // When this range was created/used +} + +const MAX_STORED_RANGES = 3; + +/** + * Retrieves the list of stored custom time ranges + * @returns Array of CustomTimeRange objects + */ +export const getCustomTimeRanges = (): CustomTimeRange[] => { + try { + const stored = localStorage.getItem( + LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES, + ); + if (!stored) return []; + + const parsed = JSON.parse(stored); + + // Validate stored data + if (Array.isArray(parsed)) { + return parsed.filter((item) => item && item.from && item.to); + } + + return []; + } catch (error) { + console.warn( + 'Failed to retrieve custom time ranges from localStorage:', + error, + ); + return []; + } +}; + +/** + * Adds a new custom time range to the stored list, maintaining only the last 3 used ranges + * @param dateTimeRange - The DateTimeRangeType from the time picker + * @returns The updated list of custom time ranges + */ +export const addCustomTimeRange = ( + dateTimeRange: DateTimeRangeType, +): CustomTimeRange[] => { + if (!dateTimeRange || !dateTimeRange[0] || !dateTimeRange[1]) { + return getCustomTimeRanges(); + } + + const [startTime, endTime] = dateTimeRange; + const now = dayjs(); + + const newRange: CustomTimeRange = { + from: startTime.toISOString(), + to: endTime.toISOString(), + timestamp: now.unix(), + }; + + // Get existing ranges + const existingRanges = getCustomTimeRanges(); + + // Remove duplicate ranges (same start and end time) + const filteredRanges = existingRanges.filter( + (range) => + range.from !== startTime.toISOString() || range.to !== endTime.toISOString(), + ); + + // Add new range at the beginning (most recent) + const updatedRanges = [newRange, ...filteredRanges].slice( + 0, + MAX_STORED_RANGES, + ); + + // Store in localStorage + try { + localStorage.setItem( + LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES, + JSON.stringify(updatedRanges), + ); + } catch (error) { + console.warn('Failed to save custom time range to localStorage:', error); + } + + return updatedRanges; +}; + +/** + * Clears all stored custom time ranges + */ +export const clearCustomTimeRanges = (): void => { + try { + localStorage.removeItem(LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES); + } catch (error) { + console.warn('Failed to clear custom time ranges from localStorage:', error); + } +}; + +/** + * Removes a specific custom time range by its timestamp + * @param timestamp - The timestamp of the range to remove + * @returns The updated list of custom time ranges + */ +export const removeCustomTimeRange = (timestamp: number): CustomTimeRange[] => { + const existingRanges = getCustomTimeRanges(); + const updatedRanges = existingRanges.filter( + (range) => range.timestamp !== timestamp, + ); + + try { + localStorage.setItem( + LOCALSTORAGE.LAST_USED_CUSTOM_TIME_RANGES, + JSON.stringify(updatedRanges), + ); + } catch (error) { + console.warn('Failed to update custom time ranges in localStorage:', error); + } + + return updatedRanges; +}; + +/** + * Checks if a time range already exists in the stored list + * @param dateTimeRange - The DateTimeRangeType to check + * @returns True if the range already exists + */ +export const isCustomTimeRangeExists = ( + dateTimeRange: DateTimeRangeType, +): boolean => { + if (!dateTimeRange || !dateTimeRange[0] || !dateTimeRange[1]) { + return false; + } + + const [startTime, endTime] = dateTimeRange; + const existingRanges = getCustomTimeRanges(); + + return existingRanges.some( + (range) => + range.from === startTime.toISOString() && range.to === endTime.toISOString(), + ); +}; + +/** + * Converts a CustomTimeRange to DateTimeRangeType + * @param customRange - The CustomTimeRange to convert + * @returns DateTimeRangeType + */ +export const convertCustomRangeToDateTimeRange = ( + customRange: CustomTimeRange, +): DateTimeRangeType => [dayjs(customRange.from), dayjs(customRange.to)]; + +/** + * Converts a DateTimeRangeType to CustomTimeRange format + * @param dateTimeRange - The DateTimeRangeType to convert + * @param label - Optional label, will be auto-generated if not provided + * @returns CustomTimeRange + */ +export const convertDateTimeRangeToCustomRange = ( + dateTimeRange: DateTimeRangeType, +): CustomTimeRange | null => { + if (!dateTimeRange || !dateTimeRange[0] || !dateTimeRange[1]) { + return null; + } + + const [startTime, endTime] = dateTimeRange; + + return { + from: startTime.toISOString(), + to: endTime.toISOString(), + + timestamp: Date.now(), + }; +}; diff --git a/frontend/src/utils/timeUtils.ts b/frontend/src/utils/timeUtils.ts index 1394f839f380..bbc9a3cad689 100644 --- a/frontend/src/utils/timeUtils.ts +++ b/frontend/src/utils/timeUtils.ts @@ -128,7 +128,6 @@ export const secondsToMilliseconds = (seconds: number): number => seconds * 1_000; export const epochToTimeString = (epochMs: number): string => { - console.log({ epochMs }); const date = new Date(epochMs); const options: Intl.DateTimeFormatOptions = { hour: '2-digit', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9fb309f85d3c..b7755b736f6e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2578,6 +2578,11 @@ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31" integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== +"@date-fns/tz@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@date-fns/tz/-/tz-1.4.1.tgz#2d905f282304630e07bef6d02d2e7dbf3f0cc4e4" + integrity sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA== + "@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -3543,6 +3548,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/primitive@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.3.tgz#e2dbc13bdc5e4168f4334f75832d7bdd3e2de5ba" + integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg== + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -3551,6 +3561,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-arrow@1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09" + integrity sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w== + dependencies: + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-collection@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" @@ -3569,6 +3586,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-compose-refs@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30" + integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== + "@radix-ui/react-context@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" @@ -3576,6 +3598,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-context@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36" + integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== + "@radix-ui/react-direction@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" @@ -3595,6 +3622,36 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" +"@radix-ui/react-dismissable-layer@1.1.11": + version "1.1.11" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz#e33ab6f6bdaa00f8f7327c408d9f631376b88b37" + integrity sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg== + dependencies: + "@radix-ui/primitive" "1.1.3" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-escape-keydown" "1.1.1" + +"@radix-ui/react-focus-guards@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz#2a5669e464ad5fde9f86d22f7fdc17781a4dfa7f" + integrity sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw== + +"@radix-ui/react-focus-scope@1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz#dfe76fc103537d80bf42723a183773fd07bfb58d" + integrity sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + +"@radix-ui/react-icons@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz#09be63d178262181aeca5fb7f7bc944b10a7f441" + integrity sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g== + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3603,6 +3660,34 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-id@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7" + integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-popover@^1.1.15": + version "1.1.15" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz#9c852f93990a687ebdc949b2c3de1f37cdc4c5d5" + integrity sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA== + dependencies: + "@radix-ui/primitive" "1.1.3" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-dismissable-layer" "1.1.11" + "@radix-ui/react-focus-guards" "1.1.3" + "@radix-ui/react-focus-scope" "1.1.7" + "@radix-ui/react-id" "1.1.1" + "@radix-ui/react-popper" "1.2.8" + "@radix-ui/react-portal" "1.1.9" + "@radix-ui/react-presence" "1.1.5" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-slot" "1.2.3" + "@radix-ui/react-use-controllable-state" "1.2.2" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + "@radix-ui/react-popper@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" @@ -3620,6 +3705,22 @@ "@radix-ui/react-use-size" "1.0.1" "@radix-ui/rect" "1.0.1" +"@radix-ui/react-popper@1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz#a79f39cdd2b09ab9fb50bf95250918422c4d9602" + integrity sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-use-rect" "1.1.1" + "@radix-ui/react-use-size" "1.1.1" + "@radix-ui/rect" "1.1.1" + "@radix-ui/react-portal@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" @@ -3628,6 +3729,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-portal@1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472" + integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ== + dependencies: + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3637,6 +3746,14 @@ "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-presence@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz#5d8f28ac316c32f078afce2996839250c10693db" + integrity sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-primitive@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" @@ -3645,6 +3762,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-primitive@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc" + integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ== + dependencies: + "@radix-ui/react-slot" "1.2.3" + "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" @@ -3669,6 +3793,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-slot@1.2.3", "@radix-ui/react-slot@^1.1.0", "@radix-ui/react-slot@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1" + integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-tabs@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2" @@ -3710,6 +3841,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-callback-ref@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40" + integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg== + "@radix-ui/react-use-controllable-state@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" @@ -3718,6 +3854,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-controllable-state@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz#905793405de57d61a439f4afebbb17d0645f3190" + integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== + dependencies: + "@radix-ui/react-use-effect-event" "0.0.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-use-effect-event@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz#090cf30d00a4c7632a15548512e9152217593907" + integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-use-escape-keydown@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" @@ -3726,6 +3877,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz#b3fed9bbea366a118f40427ac40500aa1423cc29" + integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -3733,6 +3891,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-layout-effect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e" + integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== + "@radix-ui/react-use-rect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" @@ -3741,6 +3904,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/rect" "1.0.1" +"@radix-ui/react-use-rect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152" + integrity sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w== + dependencies: + "@radix-ui/rect" "1.1.1" + "@radix-ui/react-use-size@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" @@ -3749,6 +3919,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-use-size@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz#6de276ffbc389a537ffe4316f5b0f24129405b37" + integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-visually-hidden@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" @@ -3764,6 +3941,11 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/rect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.1.tgz#78244efe12930c56fd255d7923865857c41ac8cb" + integrity sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw== + "@rc-component/color-picker@~1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@rc-component/color-picker/-/color-picker-1.4.1.tgz#dcab0b660e9c4ed63a7582db68ed4a77c862cb93" @@ -4009,11 +4191,81 @@ unplugin "1.0.1" uuid "^9.0.0" +"@signozhq/button@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@signozhq/button/-/button-0.0.1.tgz#7d3204454b0361bd3fdf91fa6604af01a481a9db" + integrity sha512-k5WFpckNXzwcTS82jU+65M3V1KdriopBObB1ls7W2OU0RKof6Gf+/9uqDXnuu+Y4Cxn2cPo8+6MfiQbS02LHeg== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-slot" "^1.1.0" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + lucide-react "^0.445.0" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + +"@signozhq/button@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@signozhq/button/-/button-0.0.2.tgz#c13edef1e735134b784a41f874b60a14bc16993f" + integrity sha512-434/gbTykC00LrnzFPp7c33QPWZkf9n+8+SToLZFTB0rzcaS/xoB4b7QKhvk+8xLCj4zpw6BxfeRAL+gSoOUJw== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-slot" "^1.1.0" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + lucide-react "^0.445.0" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + +"@signozhq/calendar@0.0.0": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@signozhq/calendar/-/calendar-0.0.0.tgz#93b2cec2586efee814df934f88a2193cec95bae9" + integrity sha512-lm7tzPEhaHNjrksvi2GPGH4suEe6x2DQJ2dpku+JmKyLGB5rg9saSAosvrZVKhXLoZuSSjlBSkz+oHYEKIdHfA== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-slot" "^1.2.3" + "@signozhq/button" "0.0.1" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + date-fns "^4.1.0" + lucide-react "^0.445.0" + react-day-picker "^9.8.1" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + "@signozhq/design-tokens@1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-1.1.4.tgz#5d5de5bd9d19b6a3631383db015cc4b70c3f7661" integrity sha512-ICZz5szxTq8NcKAsk6LP+nSybPyEcyy8eu2zfxlPQCnJ1YjJP1PglaKLlF0N6+D60gAd3yC5he06BqR8/HxjNg== +"@signozhq/input@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@signozhq/input/-/input-0.0.2.tgz#b2fea8c0979a53984ebcd5e3c3c50b38082eb1b1" + integrity sha512-Iti9GkvexSsULX1pQsN6FT6Gw96YWilts72wITZd5fzgZq1yKqaDtQl98/QNuyoS3I3WEh+hVF4EIeCCe7oRsQ== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-slot" "^1.1.0" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + lucide-react "^0.445.0" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + +"@signozhq/popover@0.0.0": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@signozhq/popover/-/popover-0.0.0.tgz#675baf1c18ca0180369b4df0700c24e2c55ad758" + integrity sha512-XW0MhzxWzZNQWjVeb+BFjiOIbBbYCT+9MCUOIW8kiL0axFaaimnk0QPi1rk09u136MMGByI6fYuCJ5Qa07l1dA== + dependencies: + "@radix-ui/react-icons" "^1.3.0" + "@radix-ui/react-popover" "^1.1.15" + "@radix-ui/react-slot" "^1.1.0" + "@signozhq/button" "^0.0.2" + class-variance-authority "^0.7.0" + clsx "^2.1.1" + lucide-react "^0.445.0" + tailwind-merge "^2.5.2" + tailwindcss-animate "^1.0.7" + "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz" @@ -5787,6 +6039,13 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-hidden@^1.2.4: + version "1.2.6" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a" + integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== + dependencies: + tslib "^2.0.0" + aria-query@^5.0.0, aria-query@^5.1.3: version "5.1.3" resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz" @@ -6870,6 +7129,13 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-variance-authority@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787" + integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== + dependencies: + clsx "^2.1.1" + classnames@2.3.2, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz" @@ -6964,6 +7230,11 @@ clsx@^1.1.1: resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -7787,11 +8058,21 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns-jalali@^4.1.0-0: + version "4.1.0-0" + resolved "https://registry.yarnpkg.com/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz#9c7fb286004fab267a300d3e9f1ada9f10b4b6b0" + integrity sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg== + date-fns@3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== +date-fns@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + dayjs@^1.10.7, dayjs@^1.11.1: version "1.11.7" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz" @@ -7993,6 +8274,11 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -9418,6 +9704,11 @@ get-intrinsic@^1.2.6: hasown "^2.0.2" math-intrinsics "^1.1.0" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" @@ -11932,6 +12223,11 @@ lucide-react@0.498.0: resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.498.0.tgz#3109eac93dfd0c1561db7a5cddfe4b9b20c14315" integrity sha512-k8IKbvMNV5Dj7CHRrKyIc1wAtmGdEF0r6SCaiGAt5cZ8KnjcEao8mfdydKkWspy65l40MdlcfdK0kT3QrxpnIg== +lucide-react@^0.445.0: + version "0.445.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.445.0.tgz#35c42341e98fbf0475b2a6cf74fd25ef7cbfcd62" + integrity sha512-YrLf3aAHvmd4dZ8ot+mMdNFrFpJD7YRwQ2pUcBhgqbmxtrMP4xDzIorcj+8y+6kpuXBF4JB0NOCTUWIYetJjgA== + lz-string@^1.4.4: version "1.5.0" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" @@ -14759,6 +15055,15 @@ react-beautiful-dnd@13.1.1: redux "^4.0.4" use-memo-one "^1.1.1" +react-day-picker@^9.8.1: + version "9.9.0" + resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-9.9.0.tgz#494da0f1470e8f0ca61c30100b17615687b68393" + integrity sha512-NtkJbuX6cl/VaGNb3sVVhmMA6LSMnL5G3xNL+61IyoZj0mUZFWTg4hmj7PHjIQ8MXN9dHWhUHFoJWG6y60DKSg== + dependencies: + "@date-fns/tz" "^1.4.1" + date-fns "^4.1.0" + date-fns-jalali "^4.1.0-0" + react-dnd-html5-backend@16.0.1: version "16.0.1" resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" @@ -14962,6 +15267,25 @@ react-redux@^7.2.0, react-redux@^7.2.2: prop-types "^15.7.2" react-is "^17.0.2" +react-remove-scroll-bar@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== + dependencies: + react-style-singleton "^2.2.2" + tslib "^2.0.0" + +react-remove-scroll@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz#d2101d414f6d81d7d3bf033f3c1cb4785789f753" + integrity sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.3" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.3" + react-resizable@3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz" @@ -15022,6 +15346,14 @@ react-router@6.27.0: dependencies: "@remix-run/router" "1.20.0" +react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== + dependencies: + get-nonce "^1.0.0" + tslib "^2.0.0" + react-syntax-highlighter@15.5.0: version "15.5.0" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" @@ -16608,6 +16940,16 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" +tailwind-merge@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.6.0.tgz#ac5fb7e227910c038d458f396b7400d93a3142d5" + integrity sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA== + +tailwindcss-animate@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4" + integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== + tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" @@ -17346,6 +17688,13 @@ url-set-query@^1.0.0: resolved "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz" integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== +use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + use-isomorphic-layout-effect@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" @@ -17356,6 +17705,14 @@ use-memo-one@^1.1.1: resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== +use-sidecar@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + use-sync-external-store@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"