From 57a17d7e9248a5cd5ed63ef99c79bfcd54318c3f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 18 Oct 2022 22:02:09 +0200 Subject: [PATCH 1/6] Created component for DateTimeInputs --- src/short-urls/ShortUrlForm.tsx | 6 +++--- src/utils/DateInput.tsx | 4 ++-- src/utils/dates/DateTimeInput.tsx | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/utils/dates/DateTimeInput.tsx diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index 5385d784..8c3f6635 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -3,7 +3,7 @@ import { InputType } from 'reactstrap/types/lib/Input'; import { Button, FormGroup, Input, Row } from 'reactstrap'; import { cond, isEmpty, pipe, replace, trim, T } from 'ramda'; import { parseISO } from 'date-fns'; -import { DateInput, DateInputProps } from '../utils/DateInput'; +import { DateTimeInput, DateTimeInputProps } from '../utils/dates/DateTimeInput'; import { supportsCrawlableVisits, supportsForwardQuery } from '../utils/helpers/features'; import { SimpleCard } from '../utils/SimpleCard'; import { handleEventPreventingDefault, hasValue, OptionalString } from '../utils/utils'; @@ -83,8 +83,8 @@ export const ShortUrlForm = ( /> ); - const renderDateInput = (id: DateFields, placeholder: string, props: Partial = {}) => ( - = {}) => ( + { - const { className, isClearable, selected } = props; + const { className, isClearable, selected, dateFormat } = props; const showCalendarIcon = !isClearable || isNil(selected); const ref = useRef<{ input: HTMLInputElement }>(); @@ -23,7 +23,7 @@ export const DateInput = (props: DateInputProps) => { options: { padding: 24 }, // This prevents the arrow to be placed on the very edge, which looks ugly }, ]} - dateFormat="yyyy-MM-dd" + dateFormat={dateFormat ?? 'yyyy-MM-dd'} className={classNames('date-input-container__input form-control', className)} // @ts-expect-error The DatePicker type definition is wrong. It has a ref prop ref={ref} diff --git a/src/utils/dates/DateTimeInput.tsx b/src/utils/dates/DateTimeInput.tsx new file mode 100644 index 00000000..09b96ffb --- /dev/null +++ b/src/utils/dates/DateTimeInput.tsx @@ -0,0 +1,14 @@ +import { ReactDatePickerProps } from 'react-datepicker'; +import { FC } from 'react'; +import { DateInput } from '../DateInput'; + +export type DateTimeInputProps = Omit; + +export const DateTimeInput: FC = (props) => ( + +); From 3cb79c167e6a50260b099a5f34782ecb8dea4025 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 10:13:53 +0200 Subject: [PATCH 2/6] Fixed date picker time styles --- src/utils/DateInput.scss | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/utils/DateInput.scss b/src/utils/DateInput.scss index 8c0877b8..ef3af934 100644 --- a/src/utils/DateInput.scss +++ b/src/utils/DateInput.scss @@ -51,8 +51,9 @@ } } +.react-datepicker__time.react-datepicker__time, .react-datepicker.react-datepicker { - background-color: var(--primary-color); + background-color: var(--primary-color) !important; color: var(--text-color); border-color: var(--border-color); } @@ -66,7 +67,7 @@ .react-datepicker-time__header.react-datepicker-time__header, .react-datepicker-year-header.react-datepicker-year-header, .react-datepicker__day-name.react-datepicker__day-name, -.react-datepicker__day:not(:hover).react-datepicker__day:not(:hover), +.react-datepicker__day.react-datepicker__day:not(:hover):not(.react-datepicker__day--selected), .react-datepicker__time-name.react-datepicker__time-name { color: inherit; } @@ -84,6 +85,18 @@ color: white !important; } +.react-datepicker__time-list-item.react-datepicker__time-list-item:hover { + color: #232323; +} + +.react-datepicker__time-container.react-datepicker__time-container { + border-color: var(--border-color); +} + +.react-datepicker__time-list.react-datepicker__time-list { + +} + .react-datepicker-popper.react-datepicker-popper { z-index: 2; From 10d3deff37121b8e925c9841c33f21c37e49d6df Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 10:22:31 +0200 Subject: [PATCH 3/6] Formatted scrollbar in date picker for time component --- src/utils/DateInput.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/utils/DateInput.scss b/src/utils/DateInput.scss index ef3af934..bb795943 100644 --- a/src/utils/DateInput.scss +++ b/src/utils/DateInput.scss @@ -94,7 +94,20 @@ } .react-datepicker__time-list.react-datepicker__time-list { + /* Forefox scrollbar */ + scrollbar-color: rgba(0, 0, 0, 0.5) var(--secondary-color); + scrollbar-width: thin; + /* Chrome webkit scrollbar */ + &::-webkit-scrollbar { + width: 10px; + background-color: var(--secondary-color); + } + + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 0.5rem; + } } .react-datepicker-popper.react-datepicker-popper { From c3b60367f30105c6e785c0d25b333db51a525846 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 10:43:01 +0200 Subject: [PATCH 4/6] Added test covering custom formatting in DateInput --- src/utils/DateInput.tsx | 3 ++- src/utils/Time.tsx | 4 ++-- src/utils/dates/DateTimeInput.tsx | 5 +++-- src/utils/helpers/date.ts | 9 ++++++++- src/visits/charts/LineChartCard.tsx | 7 ++++--- test/utils/DateInput.test.tsx | 11 +++++++++++ 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/utils/DateInput.tsx b/src/utils/DateInput.tsx index 230fd38f..60d45959 100644 --- a/src/utils/DateInput.tsx +++ b/src/utils/DateInput.tsx @@ -4,6 +4,7 @@ import DatePicker, { ReactDatePickerProps } from 'react-datepicker'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons'; import classNames from 'classnames'; +import { STANDARD_DATE_FORMAT } from './helpers/date'; import './DateInput.scss'; export type DateInputProps = ReactDatePickerProps; @@ -23,7 +24,7 @@ export const DateInput = (props: DateInputProps) => { options: { padding: 24 }, // This prevents the arrow to be placed on the very edge, which looks ugly }, ]} - dateFormat={dateFormat ?? 'yyyy-MM-dd'} + dateFormat={dateFormat ?? STANDARD_DATE_FORMAT} className={classNames('date-input-container__input form-control', className)} // @ts-expect-error The DatePicker type definition is wrong. It has a ref prop ref={ref} diff --git a/src/utils/Time.tsx b/src/utils/Time.tsx index fc4c3571..9a395ac8 100644 --- a/src/utils/Time.tsx +++ b/src/utils/Time.tsx @@ -1,5 +1,5 @@ import { parseISO, format as formatDate, getUnixTime, formatDistance } from 'date-fns'; -import { isDateObject } from './helpers/date'; +import { isDateObject, STANDARD_DATE_AND_TIME_FORMAT } from './helpers/date'; export interface TimeProps { date: Date | string; @@ -7,7 +7,7 @@ export interface TimeProps { relative?: boolean; } -export const Time = ({ date, format = 'yyyy-MM-dd HH:mm', relative = false }: TimeProps) => { +export const Time = ({ date, format = STANDARD_DATE_AND_TIME_FORMAT, relative = false }: TimeProps) => { const dateObject = isDateObject(date) ? date : parseISO(date); return ( diff --git a/src/utils/dates/DateTimeInput.tsx b/src/utils/dates/DateTimeInput.tsx index 09b96ffb..75e16692 100644 --- a/src/utils/dates/DateTimeInput.tsx +++ b/src/utils/dates/DateTimeInput.tsx @@ -1,14 +1,15 @@ import { ReactDatePickerProps } from 'react-datepicker'; import { FC } from 'react'; import { DateInput } from '../DateInput'; +import { STANDARD_DATE_AND_TIME_FORMAT } from '../helpers/date'; export type DateTimeInputProps = Omit; export const DateTimeInput: FC = (props) => ( ); diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts index 75c8b766..9e0a73b5 100644 --- a/src/utils/helpers/date.ts +++ b/src/utils/helpers/date.ts @@ -1,6 +1,10 @@ import { format, formatISO, isBefore, isEqual, isWithinInterval, parse, parseISO as stdParseISO } from 'date-fns'; import { OptionalString } from '../utils'; +export const STANDARD_DATE_FORMAT = 'yyyy-MM-dd'; + +export const STANDARD_DATE_AND_TIME_FORMAT = 'yyyy-MM-dd HH:mm'; + export type DateOrString = Date | string; type NullableDate = DateOrString | null; @@ -15,7 +19,10 @@ const formatDateFromFormat = (date?: NullableDate, theFormat?: string): Optional return theFormat ? format(date, theFormat) : formatISO(date); }; -export const formatDate = (theFormat = 'yyyy-MM-dd') => (date?: NullableDate) => formatDateFromFormat(date, theFormat); +export const formatDate = (theFormat = STANDARD_DATE_FORMAT) => (date?: NullableDate) => formatDateFromFormat( + date, + theFormat, +); export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date, undefined); diff --git a/src/visits/charts/LineChartCard.tsx b/src/visits/charts/LineChartCard.tsx index a6e4b4ba..8554ff71 100644 --- a/src/visits/charts/LineChartCard.tsx +++ b/src/visits/charts/LineChartCard.tsx @@ -31,6 +31,7 @@ import { prettify } from '../../utils/helpers/numbers'; import { pointerOnHover, renderChartLabel } from '../../utils/helpers/charts'; import { HIGHLIGHTED_COLOR, MAIN_COLOR } from '../../utils/theme'; import './LineChartCard.scss'; +import { STANDARD_DATE_FORMAT } from '../../utils/helpers/date'; interface LineChartCardProps { title: string; @@ -65,10 +66,10 @@ const STEP_TO_DIFF_FUNC_MAP: Record n const STEP_TO_DATE_FORMAT: Record string> = { hourly: (date) => format(date, 'yyyy-MM-dd HH:00'), - daily: (date) => format(date, 'yyyy-MM-dd'), + daily: (date) => format(date, STANDARD_DATE_FORMAT), weekly(date) { - const firstWeekDay = format(startOfISOWeek(date), 'yyyy-MM-dd'); - const lastWeekDay = format(endOfISOWeek(date), 'yyyy-MM-dd'); + const firstWeekDay = format(startOfISOWeek(date), STANDARD_DATE_FORMAT); + const lastWeekDay = format(endOfISOWeek(date), STANDARD_DATE_FORMAT); return `${firstWeekDay} - ${lastWeekDay}`; }, diff --git a/test/utils/DateInput.test.tsx b/test/utils/DateInput.test.tsx index e036433c..dc809afc 100644 --- a/test/utils/DateInput.test.tsx +++ b/test/utils/DateInput.test.tsx @@ -1,5 +1,6 @@ import { screen, waitFor } from '@testing-library/react'; import { Mock } from 'ts-mockery'; +import { parseISO } from 'date-fns'; import { DateInput, DateInputProps } from '../../src/utils/DateInput'; import { renderWithEvents } from '../__helpers__/setUpTest'; @@ -30,4 +31,14 @@ describe('', () => { await user.click(screen.getByPlaceholderText('foo')); await waitFor(() => expect(container.querySelector('.react-datepicker')).toBeInTheDocument()); }); + + it.each([ + [undefined, '2022-01-01'], + ['yyyy-MM-dd', '2022-01-01'], + ['yyyy-MM-dd HH:mm', '2022-01-01 15:18'], + ['HH:mm:ss', '15:18:36'], + ])('shows date in expected format', (dateFormat, expectedValue) => { + setUp({ placeholderText: 'foo', selected: parseISO('2022-01-01T15:18:36'), dateFormat }); + expect(screen.getByPlaceholderText('foo')).toHaveValue(expectedValue); + }); }); From 6df12ce194e7495071e946123c85cac7985e84b6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Oct 2022 10:49:35 +0200 Subject: [PATCH 5/6] Moved date-time related utils to the proper folder --- src/short-urls/helpers/ShortUrlsRow.tsx | 2 +- src/utils/{ => dates}/DateInput.scss | 4 ++-- src/utils/{ => dates}/DateInput.tsx | 2 +- src/utils/dates/DateRangeRow.tsx | 2 +- src/utils/dates/DateTimeInput.tsx | 2 +- src/utils/{ => dates}/Time.tsx | 2 +- src/visits/ShortUrlVisitsHeader.tsx | 2 +- src/visits/VisitsTable.tsx | 2 +- test/utils/{ => dates}/DateInput.test.tsx | 4 ++-- test/utils/{ => dates}/Time.test.tsx | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) rename src/utils/{ => dates}/DateInput.scss (98%) rename src/utils/{ => dates}/DateInput.tsx (96%) rename src/utils/{ => dates}/Time.tsx (87%) rename test/utils/{ => dates}/DateInput.test.tsx (92%) rename test/utils/{ => dates}/Time.test.tsx (87%) diff --git a/src/short-urls/helpers/ShortUrlsRow.tsx b/src/short-urls/helpers/ShortUrlsRow.tsx index eed961af..de75968e 100644 --- a/src/short-urls/helpers/ShortUrlsRow.tsx +++ b/src/short-urls/helpers/ShortUrlsRow.tsx @@ -7,7 +7,7 @@ import { Tag } from '../../tags/helpers/Tag'; import { SelectedServer } from '../../servers/data'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { ShortUrl } from '../data'; -import { Time } from '../../utils/Time'; +import { Time } from '../../utils/dates/Time'; import { ShortUrlVisitsCount } from './ShortUrlVisitsCount'; import { ShortUrlsRowMenuProps } from './ShortUrlsRowMenu'; import './ShortUrlsRow.scss'; diff --git a/src/utils/DateInput.scss b/src/utils/dates/DateInput.scss similarity index 98% rename from src/utils/DateInput.scss rename to src/utils/dates/DateInput.scss index bb795943..f68453dc 100644 --- a/src/utils/DateInput.scss +++ b/src/utils/dates/DateInput.scss @@ -1,5 +1,5 @@ -@import './mixins/vertical-align'; -@import './base'; +@import '../mixins/vertical-align'; +@import '../base'; .date-input-container { position: relative; diff --git a/src/utils/DateInput.tsx b/src/utils/dates/DateInput.tsx similarity index 96% rename from src/utils/DateInput.tsx rename to src/utils/dates/DateInput.tsx index 60d45959..15a95851 100644 --- a/src/utils/DateInput.tsx +++ b/src/utils/dates/DateInput.tsx @@ -4,7 +4,7 @@ import DatePicker, { ReactDatePickerProps } from 'react-datepicker'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons'; import classNames from 'classnames'; -import { STANDARD_DATE_FORMAT } from './helpers/date'; +import { STANDARD_DATE_FORMAT } from '../helpers/date'; import './DateInput.scss'; export type DateInputProps = ReactDatePickerProps; diff --git a/src/utils/dates/DateRangeRow.tsx b/src/utils/dates/DateRangeRow.tsx index 3712fb86..d2cf1b8d 100644 --- a/src/utils/dates/DateRangeRow.tsx +++ b/src/utils/dates/DateRangeRow.tsx @@ -1,5 +1,5 @@ import { endOfDay } from 'date-fns'; -import { DateInput } from '../DateInput'; +import { DateInput } from './DateInput'; import { DateRange } from './types'; interface DateRangeRowProps extends DateRange { diff --git a/src/utils/dates/DateTimeInput.tsx b/src/utils/dates/DateTimeInput.tsx index 75e16692..ddc45acb 100644 --- a/src/utils/dates/DateTimeInput.tsx +++ b/src/utils/dates/DateTimeInput.tsx @@ -1,6 +1,6 @@ import { ReactDatePickerProps } from 'react-datepicker'; import { FC } from 'react'; -import { DateInput } from '../DateInput'; +import { DateInput } from './DateInput'; import { STANDARD_DATE_AND_TIME_FORMAT } from '../helpers/date'; export type DateTimeInputProps = Omit; diff --git a/src/utils/Time.tsx b/src/utils/dates/Time.tsx similarity index 87% rename from src/utils/Time.tsx rename to src/utils/dates/Time.tsx index 9a395ac8..a06381df 100644 --- a/src/utils/Time.tsx +++ b/src/utils/dates/Time.tsx @@ -1,5 +1,5 @@ import { parseISO, format as formatDate, getUnixTime, formatDistance } from 'date-fns'; -import { isDateObject, STANDARD_DATE_AND_TIME_FORMAT } from './helpers/date'; +import { isDateObject, STANDARD_DATE_AND_TIME_FORMAT } from '../helpers/date'; export interface TimeProps { date: Date | string; diff --git a/src/visits/ShortUrlVisitsHeader.tsx b/src/visits/ShortUrlVisitsHeader.tsx index 96046d9f..bac9587f 100644 --- a/src/visits/ShortUrlVisitsHeader.tsx +++ b/src/visits/ShortUrlVisitsHeader.tsx @@ -1,7 +1,7 @@ import { UncontrolledTooltip } from 'reactstrap'; import { ExternalLink } from 'react-external-link'; import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail'; -import { Time } from '../utils/Time'; +import { Time } from '../utils/dates/Time'; import { ShortUrlVisits } from './reducers/shortUrlVisits'; import { VisitsHeader } from './VisitsHeader'; import './ShortUrlVisitsHeader.scss'; diff --git a/src/visits/VisitsTable.tsx b/src/visits/VisitsTable.tsx index 39e30c48..16181b49 100644 --- a/src/visits/VisitsTable.tsx +++ b/src/visits/VisitsTable.tsx @@ -10,7 +10,7 @@ import { determineOrderDir, Order, sortList } from '../utils/helpers/ordering'; import { prettify } from '../utils/helpers/numbers'; import { supportsBotVisits } from '../utils/helpers/features'; import { SelectedServer } from '../servers/data'; -import { Time } from '../utils/Time'; +import { Time } from '../utils/dates/Time'; import { TableOrderIcon } from '../utils/table/TableOrderIcon'; import { MediaMatcher } from '../utils/types'; import { NormalizedOrphanVisit, NormalizedVisit } from './types'; diff --git a/test/utils/DateInput.test.tsx b/test/utils/dates/DateInput.test.tsx similarity index 92% rename from test/utils/DateInput.test.tsx rename to test/utils/dates/DateInput.test.tsx index dc809afc..525b0652 100644 --- a/test/utils/DateInput.test.tsx +++ b/test/utils/dates/DateInput.test.tsx @@ -1,8 +1,8 @@ import { screen, waitFor } from '@testing-library/react'; import { Mock } from 'ts-mockery'; import { parseISO } from 'date-fns'; -import { DateInput, DateInputProps } from '../../src/utils/DateInput'; -import { renderWithEvents } from '../__helpers__/setUpTest'; +import { DateInput, DateInputProps } from '../../../src/utils/dates/DateInput'; +import { renderWithEvents } from '../../__helpers__/setUpTest'; describe('', () => { const setUp = (props: Partial = {}) => renderWithEvents( diff --git a/test/utils/Time.test.tsx b/test/utils/dates/Time.test.tsx similarity index 87% rename from test/utils/Time.test.tsx rename to test/utils/dates/Time.test.tsx index cb1f9c8f..38b38a47 100644 --- a/test/utils/Time.test.tsx +++ b/test/utils/dates/Time.test.tsx @@ -1,6 +1,6 @@ import { render } from '@testing-library/react'; -import { TimeProps, Time } from '../../src/utils/Time'; -import { parseDate } from '../../src/utils/helpers/date'; +import { TimeProps, Time } from '../../../src/utils/dates/Time'; +import { parseDate } from '../../../src/utils/helpers/date'; describe('