From dd5c4b33ca1859343bfb4366c9eb274a33bdc948 Mon Sep 17 00:00:00 2001 From: Nada Date: Mon, 10 Jun 2024 12:59:19 +0400 Subject: [PATCH 1/6] Fix: --- package-lock.json | 86 +++++++++++++++ package.json | 1 + src/components/DatePicker/DatePicker.scss | 0 src/components/DatePicker/DatePicker.tsx | 103 ++++++++++++++++++ src/components/DatePicker/index.ts | 1 + src/components/DatePicker/utils.ts | 13 +++ .../FlowTextField/FlowTextField.tsx | 81 ++++++++++++++ src/components/FlowTextField/index.ts | 0 src/components/index.ts | 1 + .../OrdersTableHeader/OrdersTableHeader.tsx | 2 + 10 files changed, 288 insertions(+) create mode 100644 src/components/DatePicker/DatePicker.scss create mode 100644 src/components/DatePicker/DatePicker.tsx create mode 100644 src/components/DatePicker/index.ts create mode 100644 src/components/DatePicker/utils.ts create mode 100644 src/components/FlowTextField/FlowTextField.tsx create mode 100644 src/components/FlowTextField/index.ts diff --git a/package-lock.json b/package-lock.json index e66f9f92..bf88ff3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "moment": "^2.30.1", "qrcode.react": "^3.1.0", "react": "^18.2.0", + "react-calendar": "^5.0.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.51.1", @@ -6057,6 +6058,14 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -10782,6 +10791,17 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-user-locale": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz", + "integrity": "sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ==", + "dependencies": { + "mem": "^8.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/git-raw-commits": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", @@ -14988,6 +15008,17 @@ "tmpl": "1.0.5" } }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -15060,6 +15091,29 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" }, + "node_modules/mem": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", + "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "dependencies": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/mem?sponsor=1" + } + }, + "node_modules/mem/node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/memory-fs": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", @@ -15640,6 +15694,14 @@ "node": ">= 0.8.0" } }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -16605,6 +16667,30 @@ "node": ">=0.10.0" } }, + "node_modules/react-calendar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-5.0.0.tgz", + "integrity": "sha512-bHcE5e5f+VUKLd4R19BGkcSQLpuwjKBVG0fKz74cwPW5xDfNsReHdDbfd4z3mdjuUuZzVtw4Q920mkwK5/ZOEg==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", diff --git a/package.json b/package.json index f18cf7e6..9c59cb3c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "moment": "^2.30.1", "qrcode.react": "^3.1.0", "react": "^18.2.0", + "react-calendar": "^5.0.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.51.1", diff --git a/src/components/DatePicker/DatePicker.scss b/src/components/DatePicker/DatePicker.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx new file mode 100644 index 00000000..760f7fe6 --- /dev/null +++ b/src/components/DatePicker/DatePicker.tsx @@ -0,0 +1,103 @@ +import { useEffect, useRef, useState } from 'react'; +import Calendar, { CalendarProps } from 'react-calendar'; +import { useOnClickOutside } from 'usehooks-ts'; +import { LegacyCalendar1pxIcon } from '@deriv/quill-icons'; +import FlowTextField, { TFlowFieldProps } from '../FlowTextField/FlowTextField'; +import { customFormatShortWeekday, unixToDateString } from './utils'; +import 'react-calendar/dist/Calendar.css'; +import './DatePicker.scss'; + +interface TDatePickerProps extends TFlowFieldProps { + displayFormat?: string; + maxDate?: Date; + minDate?: Date; + mobileAlignment?: 'above' | 'below'; + onDateChange: (formattedDate: string | null) => void; +} + +const DatePicker = ({ + defaultValue, + disabled, + displayFormat = 'YYYY-MM-DD', + label, + maxDate, + message, + minDate, + mobileAlignment = 'below', + name, + onDateChange, + validationSchema, +}: TDatePickerProps) => { + console.log('here'); + const [selectedDate, setSelectedDate] = useState(defaultValue ? new Date(defaultValue) : null); + const [isCalendarOpen, setIsCalendarOpen] = useState(false); + const datePickerRef = useRef(null); + const inputeDateRef = useRef(null); + + const toggleCalendar = () => { + setIsCalendarOpen(prevState => !prevState); + }; + + useOnClickOutside(datePickerRef, () => { + setIsCalendarOpen(false); + }); + + const handleDateChange: CalendarProps['onChange'] = value => { + const calendarSelectedDate = Array.isArray(value) ? value[0] : value; + setSelectedDate(calendarSelectedDate); + setIsCalendarOpen(false); + }; + + useEffect(() => { + if (selectedDate !== null) { + onDateChange(unixToDateString(selectedDate)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedDate]); + + return ( +
+ e.preventDefault()} + ref={inputeDateRef} + renderRightIcon={() => ( + + )} + showMessage + type='text' + validationSchema={validationSchema} + value={selectedDate !== null ? unixToDateString(selectedDate, displayFormat) : ''} + /> + {isCalendarOpen && ( +
+ +
+ )} +
+ ); +}; + +export default DatePicker; diff --git a/src/components/DatePicker/index.ts b/src/components/DatePicker/index.ts new file mode 100644 index 00000000..5a316c5b --- /dev/null +++ b/src/components/DatePicker/index.ts @@ -0,0 +1 @@ +export { default as DatePicker } from './DatePicker'; diff --git a/src/components/DatePicker/utils.ts b/src/components/DatePicker/utils.ts new file mode 100644 index 00000000..363ded47 --- /dev/null +++ b/src/components/DatePicker/utils.ts @@ -0,0 +1,13 @@ +import moment from 'moment'; + +export function customFormatShortWeekday(_locale: string | undefined, date: Date) { + const weekdays = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; + + return weekdays[date.getDay()]; +} + +export function unixToDateString(date: Date, format = 'YYYY-MM-DD') { + const formattedDate = moment(date).format(format); + + return formattedDate; +} diff --git a/src/components/FlowTextField/FlowTextField.tsx b/src/components/FlowTextField/FlowTextField.tsx new file mode 100644 index 00000000..d5639b16 --- /dev/null +++ b/src/components/FlowTextField/FlowTextField.tsx @@ -0,0 +1,81 @@ +import React, { forwardRef, Ref, useEffect, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import * as Yup from 'yup'; +import { Text } from '@deriv-com/ui'; +import WalletTextField, { WalletTextFieldProps } from '../Base/WalletTextField/WalletTextField'; +import { useFlow } from '../FlowProvider'; +import { TextField } from '../TextField'; + +export interface TFlowFieldProps extends WalletTextFieldProps { + isInvalid?: WalletTextFieldProps['isInvalid']; + name: string; + validationSchema?: Yup.AnySchema; +} + +/** + * This component is just a wrapper to the Field Formik component and WalletTextField + * Use this component when you are using the FlowProvider with a form and several inputs, + * and you want those input values to be tracked and validated + */ +const FlowTextField = forwardRef( + ( + { defaultValue, disabled, errorMessage, isInvalid, name, validationSchema, ...rest }: TFlowFieldProps, + ref: Ref + ) => { + const { control } = useForm(); + const [hasTouched, setHasTouched] = useState(false); + const { setFormValues } = useFlow(); + + const validateField = (value: unknown) => { + try { + if (validationSchema) { + validationSchema.validateSync(value); + } + } catch (err: unknown) { + return (err as Yup.ValidationError).message; + } + }; + + useEffect(() => { + const setFormValuesAndTouch = async () => { + if (defaultValue) { + await setFormValues(name, defaultValue, true); + console.log('asdfasdf'); + } + }; + + setFormValuesAndTouch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + { + return ( +
+ { + setHasTouched(true); + field.onBlur(e); + }} + ref={ref} + /> +
+ ); + }} + /> + ); + } +); + +FlowTextField.displayName = 'FlowTextField'; +export default FlowTextField; diff --git a/src/components/FlowTextField/index.ts b/src/components/FlowTextField/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/components/index.ts b/src/components/index.ts index 663ba46b..8f6c7614 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,6 +6,7 @@ export * from './Badge'; export * from './BuySellForm'; export * from './Checklist'; export * from './Clipboard'; +export * from './DatePicker'; export * from './DerivIframe'; export * from './FileDropzone'; export * from './FloatingRate'; diff --git a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx index da24ab78..12481213 100644 --- a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx +++ b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx @@ -1,3 +1,4 @@ +import { DatePicker } from '@/components'; import { ORDERS_STATUS } from '@/constants/orders'; import { useQueryString } from '@/hooks/custom-hooks'; import { getLocalizedTabs } from '@/utils/tabs'; @@ -30,6 +31,7 @@ const OrdersTableHeader = ({ activeTab }: TOrdersTableHeaderProps) => { + console.log(value)} /> ); }; From f6203653c789a8fa4c535103738a7ce193b8e9dc Mon Sep 17 00:00:00 2001 From: Nada Date: Tue, 11 Jun 2024 15:05:38 +0400 Subject: [PATCH 2/6] feat: date filter for past orders --- package-lock.json | 8 +- package.json | 2 +- src/components/DatePicker/DatePicker.scss | 147 ++++++++++++++++++ src/components/DatePicker/DatePicker.tsx | 67 ++++---- .../DateTextField/DateTextField.scss | 34 ++++ .../DateTextField/DateTextField.tsx | 31 ++++ src/components/DateTextField/index.ts | 1 + .../FlowTextField/FlowTextField.tsx | 81 ---------- src/components/FlowTextField/index.ts | 0 .../FullPageMobileWrapper.tsx | 9 +- .../OrdersDateSelection.scss | 19 +++ .../OrdersDateSelection.tsx | 76 +++++++++ .../components/OrdersDateSelection/index.ts | 1 + .../OrdersDateSelectionFullPage.scss | 21 +++ .../OrdersDateSelectionFullPage.tsx | 88 +++++++++++ .../OrdersDateSelectionFullPage/index.ts | 1 + src/pages/orders/components/index.ts | 1 + src/pages/orders/screens/Orders/Orders.tsx | 33 +++- .../OrdersTableHeader/OrdersTableHeader.scss | 9 ++ .../OrdersTableHeader/OrdersTableHeader.tsx | 18 ++- .../__tests__/OrdersTableHeader.spec.tsx | 24 ++- 21 files changed, 537 insertions(+), 134 deletions(-) create mode 100644 src/components/DateTextField/DateTextField.scss create mode 100644 src/components/DateTextField/DateTextField.tsx create mode 100644 src/components/DateTextField/index.ts delete mode 100644 src/components/FlowTextField/FlowTextField.tsx delete mode 100644 src/components/FlowTextField/index.ts create mode 100644 src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss create mode 100644 src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx create mode 100644 src/pages/orders/components/OrdersDateSelection/index.ts create mode 100644 src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.scss create mode 100644 src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.tsx create mode 100644 src/pages/orders/components/OrdersDateSelectionFullPage/index.ts diff --git a/package-lock.json b/package-lock.json index bf88ff3f..ea9eb719 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@babel/preset-env": "^7.24.5", "@deriv-com/api-hooks": "^0.1.24", "@deriv-com/translations": "^1.2.4", - "@deriv-com/ui": "^1.27.9", + "@deriv-com/ui": "^1.28.0", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-design": "^1.2.24", @@ -2676,9 +2676,9 @@ } }, "node_modules/@deriv-com/ui": { - "version": "1.27.9", - "resolved": "https://registry.npmjs.org/@deriv-com/ui/-/ui-1.27.9.tgz", - "integrity": "sha512-zsNJ/ShXFjNp39VX1f87W9dnh3DSYUl77p3GJ9nyyDem7F43gXJK8MyVFLd0eP5eXlKNkoUttU0CvIIvQxFgDA==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@deriv-com/ui/-/ui-1.28.0.tgz", + "integrity": "sha512-CzkjM6f+xEklcQ88H9/Y3Zho8tBVDxWa73awLKHYx0KBM6g6NXF+6J9BCJGY8DCofbz0+1Q/2QnzuuNaOlX/KQ==", "dependencies": { "@types/react-modal": "^3.16.3", "react-tiny-popover": "^8.0.4" diff --git a/package.json b/package.json index 9c59cb3c..c32bf3e1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@babel/preset-env": "^7.24.5", "@deriv-com/api-hooks": "^0.1.24", "@deriv-com/translations": "^1.2.4", - "@deriv-com/ui": "^1.27.9", + "@deriv-com/ui": "^1.28.0", "@deriv-com/utils": "latest", "@deriv/deriv-api": "^1.0.15", "@deriv/quill-design": "^1.2.24", diff --git a/src/components/DatePicker/DatePicker.scss b/src/components/DatePicker/DatePicker.scss index e69de29b..61da459f 100644 --- a/src/components/DatePicker/DatePicker.scss +++ b/src/components/DatePicker/DatePicker.scss @@ -0,0 +1,147 @@ +.datepicker { + display: flex; + flex-direction: column; + position: relative; + caret-color: transparent; + + &__button { + all: unset; + cursor: pointer; + + &:disabled { + filter: invert(92%) sepia(0%) saturate(112%) hue-rotate(253deg) brightness(106%) contrast(89%); + } + } + + &__container { + position: absolute; + display: inline-block; + width: 28rem; + z-index: 1; + + &--right { + right: 0; + } + + top: 4rem; + + @include mobile { + align-self: center; + top: 5rem; + left: 0; + } + + .react-calendar { + border-radius: 0.5rem; + + button { + border-radius: 0.5rem; + } + + &__navigation { + border-bottom: 0.1rem solid #d6dadb; + height: 5rem; + padding: 1.6rem; + margin-bottom: 0; + + &__arrow { + font-size: 2.4rem; + justify-content: center; + align-items: center; + } + + &__label { + font-weight: bold; + font-size: 1.4rem; + margin-top: 0.5rem; + display: flex; + justify-content: center; + } + + button { + min-width: 2.4rem; + padding: 0 0.6rem; + border-radius: 0.5rem; + + &:enabled:hover, + &:enabled:focus, + &:disabled { + background-color: unset; + } + } + } + + &__viewContainer { + padding: 0.8rem 2rem; + } + + &__month-view { + &__days, + &__weekdays { + /* stylelint-disable-next-line declaration-no-important */ + display: grid !important; // to overwrite flex property of calendar + grid-template-columns: repeat(7, 2.4rem); + grid-auto-rows: 2.4rem; + grid-gap: 1.2rem; + align-items: center; + justify-content: center; + + &__day--neighboringMonth { + color: #757575; + } + + &__day--weekend { + color: revert; + } + + &__weekday { + font-size: 1.2rem; + } + abbr { + text-decoration: none; + } + } + + &__weekdays { + margin-bottom: 1.2rem; + } + + button { + min-width: 2.4rem; + padding: 0.6rem; + border-radius: 0.5rem; + } + } + + &__tile { + font-size: 1.2rem; + + &--active, + &--hasActive { + border-radius: 0.5rem; + background: #d6dadb; + color: unset; + + &:enabled:hover, + &:enabled:focus { + background-color: #d6dadb; + } + } + + &--active { + font-weight: bold; + } + + &--now, + &:disabled { + background-color: unset; + } + } + } + + /* stylelint-disable-next-line selector-class-pattern */ + .react-calendar__month-view__days__day--weekend.react-calendar__month-view__days__day--neighboringMonth { + color: #757575; + } + } +} diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 760f7fe6..d20eb861 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -1,38 +1,40 @@ -import { useEffect, useRef, useState } from 'react'; +import { ComponentProps, useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; import Calendar, { CalendarProps } from 'react-calendar'; import { useOnClickOutside } from 'usehooks-ts'; -import { LegacyCalendar1pxIcon } from '@deriv/quill-icons'; -import FlowTextField, { TFlowFieldProps } from '../FlowTextField/FlowTextField'; +import DateTextField from '../DateTextField/DateTextField'; import { customFormatShortWeekday, unixToDateString } from './utils'; import 'react-calendar/dist/Calendar.css'; import './DatePicker.scss'; -interface TDatePickerProps extends TFlowFieldProps { +interface TDatePickerProps extends ComponentProps { + alignedRight?: boolean; displayFormat?: string; maxDate?: Date; minDate?: Date; mobileAlignment?: 'above' | 'below'; - onDateChange: (formattedDate: string | null) => void; + onDateChange: (date: string) => void; + rightAlignment?: boolean; + showLabel?: boolean; + value?: string; } const DatePicker = ({ - defaultValue, + alignedRight, disabled, - displayFormat = 'YYYY-MM-DD', + displayFormat = 'MMM DD, YYYY', label, maxDate, - message, minDate, - mobileAlignment = 'below', name, onDateChange, - validationSchema, + rightAlignment = false, + showLabel = false, + value, }: TDatePickerProps) => { - console.log('here'); - const [selectedDate, setSelectedDate] = useState(defaultValue ? new Date(defaultValue) : null); + const [selectedDate, setSelectedDate] = useState(value ? new Date(value) : null); const [isCalendarOpen, setIsCalendarOpen] = useState(false); const datePickerRef = useRef(null); - const inputeDateRef = useRef(null); const toggleCalendar = () => { setIsCalendarOpen(prevState => !prevState); @@ -50,45 +52,42 @@ const DatePicker = ({ useEffect(() => { if (selectedDate !== null) { - onDateChange(unixToDateString(selectedDate)); + onDateChange(unixToDateString(selectedDate, displayFormat)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDate]); + const getValue = () => { + const isToday = selectedDate !== null && selectedDate.getDate() === new Date().getDate(); + if (selectedDate !== null && selectedDate !== new Date() && (!isToday || showLabel)) { + return unixToDateString(selectedDate, displayFormat); + } + return ''; + }; + return ( -
- + e.preventDefault()} - ref={inputeDateRef} - renderRightIcon={() => ( - - )} - showMessage type='text' - validationSchema={validationSchema} - value={selectedDate !== null ? unixToDateString(selectedDate, displayFormat) : ''} + value={getValue()} /> {isCalendarOpen && (
{ + alignedRight?: boolean; +} + +const DateTextField = ({ alignedRight = false, label, value, ...rest }: TFlowFieldProps) => { + return ( +
+ } + readOnly + rightPlaceholder={alignedRight && } + value={value} + wrapperClassName='w-full' + /> +
+ ); +}; + +export default DateTextField; diff --git a/src/components/DateTextField/index.ts b/src/components/DateTextField/index.ts new file mode 100644 index 00000000..a68f2bc1 --- /dev/null +++ b/src/components/DateTextField/index.ts @@ -0,0 +1 @@ +export { default as DateTextField } from './DateTextField'; diff --git a/src/components/FlowTextField/FlowTextField.tsx b/src/components/FlowTextField/FlowTextField.tsx deleted file mode 100644 index d5639b16..00000000 --- a/src/components/FlowTextField/FlowTextField.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { forwardRef, Ref, useEffect, useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import * as Yup from 'yup'; -import { Text } from '@deriv-com/ui'; -import WalletTextField, { WalletTextFieldProps } from '../Base/WalletTextField/WalletTextField'; -import { useFlow } from '../FlowProvider'; -import { TextField } from '../TextField'; - -export interface TFlowFieldProps extends WalletTextFieldProps { - isInvalid?: WalletTextFieldProps['isInvalid']; - name: string; - validationSchema?: Yup.AnySchema; -} - -/** - * This component is just a wrapper to the Field Formik component and WalletTextField - * Use this component when you are using the FlowProvider with a form and several inputs, - * and you want those input values to be tracked and validated - */ -const FlowTextField = forwardRef( - ( - { defaultValue, disabled, errorMessage, isInvalid, name, validationSchema, ...rest }: TFlowFieldProps, - ref: Ref - ) => { - const { control } = useForm(); - const [hasTouched, setHasTouched] = useState(false); - const { setFormValues } = useFlow(); - - const validateField = (value: unknown) => { - try { - if (validationSchema) { - validationSchema.validateSync(value); - } - } catch (err: unknown) { - return (err as Yup.ValidationError).message; - } - }; - - useEffect(() => { - const setFormValuesAndTouch = async () => { - if (defaultValue) { - await setFormValues(name, defaultValue, true); - console.log('asdfasdf'); - } - }; - - setFormValuesAndTouch(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - { - return ( -
- { - setHasTouched(true); - field.onBlur(e); - }} - ref={ref} - /> -
- ); - }} - /> - ); - } -); - -FlowTextField.displayName = 'FlowTextField'; -export default FlowTextField; diff --git a/src/components/FlowTextField/index.ts b/src/components/FlowTextField/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/FullPageMobileWrapper/FullPageMobileWrapper.tsx b/src/components/FullPageMobileWrapper/FullPageMobileWrapper.tsx index dfe3fcb9..f4815da4 100644 --- a/src/components/FullPageMobileWrapper/FullPageMobileWrapper.tsx +++ b/src/components/FullPageMobileWrapper/FullPageMobileWrapper.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, ReactNode } from 'react'; import clsx from 'clsx'; -import { LabelPairedArrowLeftLgBoldIcon } from '@deriv/quill-icons'; +import { LabelPairedArrowLeftLgBoldIcon, StandaloneXmarkBoldIcon } from '@deriv/quill-icons'; import './FullPageMobileWrapper.scss'; type TFullPageMobileWrapperProps = { @@ -10,6 +10,7 @@ type TFullPageMobileWrapperProps = { renderHeader?: () => ReactNode; shouldFixedFooter?: boolean; shouldShowBackIcon?: boolean; + shouldShowCloseIcon?: boolean; }; const FullPageMobileWrapper = ({ @@ -20,6 +21,7 @@ const FullPageMobileWrapper = ({ renderHeader, shouldFixedFooter = true, shouldShowBackIcon = true, + shouldShowCloseIcon = false, }: PropsWithChildren) => { return (
)} {renderHeader()} + {shouldShowCloseIcon && ( +
+ +
+ )}
)}
{children}
diff --git a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss new file mode 100644 index 00000000..0ce8ceee --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss @@ -0,0 +1,19 @@ +.orders-date-selection { + @include desktop { + display: flex; + flex-direction: row; + gap: 0.8rem; + padding: 0.4rem 0; + align-items: center; + } + + @include mobile { + margin-top: 1.6rem; + } + + &__input { + input { + padding-left: 1rem; + } + } +} diff --git a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx new file mode 100644 index 00000000..70758be8 --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx @@ -0,0 +1,76 @@ +import { Dispatch, SetStateAction, useState } from 'react'; +import { DatePicker } from '@/components'; +import { LabelPairedCalendarRangeMdRegularIcon } from '@deriv/quill-icons'; +import { useTranslations } from '@deriv-com/translations'; +import { Input, useDevice } from '@deriv-com/ui'; +import { OrdersDateSelectionFullPage } from '../OrdersDateSelectionFullPage'; +import './OrdersDateSelection.scss'; + +type TOrdersDateSelectionProps = { + fromDate: string | null; + setFromDate: Dispatch>; + setToDate: Dispatch>; + toDate: string | null; +}; + +const OrdersDateSelection = ({ fromDate, setFromDate, setToDate, toDate }: TOrdersDateSelectionProps) => { + const { isMobile } = useDevice(); + const { localize } = useTranslations(); + const [isDateSelectionOpen, setIsDateSelectionOpen] = useState(false); + + const getMaxDate = () => { + if (toDate) { + return new Date(toDate); + } + return new Date(); + }; + + if (isMobile) { + return ( +
+ } + onClick={() => setIsDateSelectionOpen(true)} + readOnly + value={fromDate && toDate ? `${fromDate} - ${toDate}` : 'All time'} + /> + {isDateSelectionOpen && ( + setIsDateSelectionOpen(false)} + setFromDate={setFromDate} + setToDate={setToDate} + toDate={toDate} + /> + )} +
+ ); + } + + return ( +
+ + +
+ ); +}; + +export default OrdersDateSelection; diff --git a/src/pages/orders/components/OrdersDateSelection/index.ts b/src/pages/orders/components/OrdersDateSelection/index.ts new file mode 100644 index 00000000..b9d2198a --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelection/index.ts @@ -0,0 +1 @@ +export { default as OrdersDateSelection } from './OrdersDateSelection'; diff --git a/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.scss b/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.scss new file mode 100644 index 00000000..4c48d8a9 --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.scss @@ -0,0 +1,21 @@ +.orders-date-selection-full-page { + position: absolute; + z-index: 1; + inset: 0; + height: 100vh; + + & .mobile-wrapper { + &__header { + span { + margin: 0 auto; + } + } + + &__body { + padding: 2.4rem; + display: flex; + flex-direction: column; + gap: 2.4rem; + } + } +} diff --git a/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.tsx b/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.tsx new file mode 100644 index 00000000..d309528b --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelectionFullPage/OrdersDateSelectionFullPage.tsx @@ -0,0 +1,88 @@ +import { Dispatch, SetStateAction, useState } from 'react'; +import { DatePicker, FullPageMobileWrapper } from '@/components'; +import { Localize, useTranslations } from '@deriv-com/translations'; +import { Button, Text } from '@deriv-com/ui'; +import './OrdersDateSelectionFullPage.scss'; + +type TOrdersDateSelectionFullPageProps = { + fromDate: string | null; + onClickCancel: () => void; + setFromDate: Dispatch>; + setToDate: Dispatch>; + toDate: string | null; +}; + +const OrdersDateSelectionFullPage = ({ + fromDate, + onClickCancel, + setFromDate, + setToDate, + toDate, +}: TOrdersDateSelectionFullPageProps) => { + const { localize } = useTranslations(); + const [startDate, setStartDate] = useState(fromDate); + const [endDate, setEndDate] = useState(toDate); + + return ( + ( +
+ + +
+ )} + renderHeader={() => ( + + + + )} + shouldShowBackIcon={false} + shouldShowCloseIcon + > + + +
+ ); +}; + +export default OrdersDateSelectionFullPage; diff --git a/src/pages/orders/components/OrdersDateSelectionFullPage/index.ts b/src/pages/orders/components/OrdersDateSelectionFullPage/index.ts new file mode 100644 index 00000000..e35e1cad --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelectionFullPage/index.ts @@ -0,0 +1 @@ +export { default as OrdersDateSelectionFullPage } from './OrdersDateSelectionFullPage'; diff --git a/src/pages/orders/components/index.ts b/src/pages/orders/components/index.ts index c7a664bc..a5c6437c 100644 --- a/src/pages/orders/components/index.ts +++ b/src/pages/orders/components/index.ts @@ -3,6 +3,7 @@ export * from './ChatFooter'; export * from './ChatHeader'; export * from './ChatMessages'; export * from './OrderRatingButton'; +export * from './OrdersDateSelection'; export * from './OrderStatusTag'; export * from './OrderTimer'; export * from './TextAreaWithIcon'; diff --git a/src/pages/orders/screens/Orders/Orders.tsx b/src/pages/orders/screens/Orders/Orders.tsx index c8bb6458..cd7fa5ff 100644 --- a/src/pages/orders/screens/Orders/Orders.tsx +++ b/src/pages/orders/screens/Orders/Orders.tsx @@ -1,3 +1,5 @@ +import { useEffect, useState } from 'react'; +import moment from 'moment'; import { ORDERS_STATUS } from '@/constants'; import { api } from '@/hooks'; import { useQueryString } from '@/hooks/custom-hooks'; @@ -9,23 +11,38 @@ const Orders = () => { const { queryString } = useQueryString(); const { isMobile } = useDevice(); const currentTab = queryString.tab ?? ORDERS_STATUS.ACTIVE_ORDERS; + const [fromDate, setFromDate] = useState(null); + const [toDate, setToDate] = useState(null); + const isActive = currentTab === ORDERS_STATUS.ACTIVE_ORDERS; const { data = [], isLoading, loadMoreOrders, - } = api.order.useGetList({ active: currentTab === ORDERS_STATUS.ACTIVE_ORDERS ? 1 : 0 }); + } = api.order.useGetList({ + active: isActive ? 1 : 0, + date_from: !isActive && fromDate ? `${moment(fromDate).startOf('day').unix()}` : undefined, + date_to: !isActive && toDate ? `${moment(toDate).endOf('day').unix()}` : undefined, + }); + + useEffect(() => { + return () => { + setFromDate(null); + setToDate(null); + }; + }, []); return ( <> - - {isMobile && } - + {isMobile && } + ); }; diff --git a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.scss b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.scss index 6bfb4043..788206b4 100644 --- a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.scss +++ b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.scss @@ -7,6 +7,7 @@ @include mobile { margin: 1.6rem; justify-content: center; + flex-direction: column; } &__tabs { @@ -22,4 +23,12 @@ } } } + + &__filters { + display: flex; + flex-direction: row; + gap: 0.8rem; + padding: 0.4rem 0; + align-items: center; + } } diff --git a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx index 12481213..37558bc5 100644 --- a/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx +++ b/src/pages/orders/screens/Orders/OrdersTableHeader/OrdersTableHeader.tsx @@ -1,6 +1,7 @@ -import { DatePicker } from '@/components'; +import { Dispatch, SetStateAction } from 'react'; import { ORDERS_STATUS } from '@/constants/orders'; import { useQueryString } from '@/hooks/custom-hooks'; +import { OrdersDateSelection } from '@/pages/orders/components/OrdersDateSelection'; import { getLocalizedTabs } from '@/utils/tabs'; import { useTranslations } from '@deriv-com/translations'; import { Tab, Tabs, useDevice } from '@deriv-com/ui'; @@ -8,9 +9,13 @@ import './OrdersTableHeader.scss'; type TOrdersTableHeaderProps = { activeTab: string; + fromDate: string | null; + setFromDate: Dispatch>; + setToDate: Dispatch>; + toDate: string | null; }; -const OrdersTableHeader = ({ activeTab }: TOrdersTableHeaderProps) => { +const OrdersTableHeader = ({ activeTab, fromDate, setFromDate, setToDate, toDate }: TOrdersTableHeaderProps) => { const { isMobile } = useDevice(); const { setQueryString } = useQueryString(); const { localize } = useTranslations(); @@ -31,7 +36,14 @@ const OrdersTableHeader = ({ activeTab }: TOrdersTableHeaderProps) => { - console.log(value)} /> + {activeTab === ORDERS_STATUS.PAST_ORDERS && ( + + )}
); }; diff --git a/src/pages/orders/screens/Orders/OrdersTableHeader/__tests__/OrdersTableHeader.spec.tsx b/src/pages/orders/screens/Orders/OrdersTableHeader/__tests__/OrdersTableHeader.spec.tsx index a1b007b5..b1a7eeb0 100644 --- a/src/pages/orders/screens/Orders/OrdersTableHeader/__tests__/OrdersTableHeader.spec.tsx +++ b/src/pages/orders/screens/Orders/OrdersTableHeader/__tests__/OrdersTableHeader.spec.tsx @@ -13,18 +13,38 @@ jest.mock('@/hooks/custom-hooks', () => ({ useQueryString: () => ({ setQueryString: mockFn }), })); +const mockProps = { + activeTab: 'Active orders', + fromDate: '2024-05-12', + setFromDate: jest.fn(), + setToDate: jest.fn(), + toDate: '2024-06-01', +}; + +jest.mock('../../../../components/OrdersDateSelection', () => ({ + OrdersDateSelection: () =>
OrdersDateSelection
, +})); + describe('OrdersTableHeader', () => { it('should render OrdersTableHeader', () => { - render(); + render(); expect(screen.getByTestId('dt_orders_table_header')).toBeInTheDocument(); expect(screen.getByText('Active orders')).toBeInTheDocument(); expect(screen.getByText('Past orders')).toBeInTheDocument(); }); it('should handle clicking on tabs', async () => { - render(); + render(); const pastOrdersTab = screen.getByText('Past orders'); expect(pastOrdersTab).toBeInTheDocument(); await userEvent.click(pastOrdersTab); expect(mockFn).toHaveBeenCalledWith({ tab: 'Past orders' }); }); + it('should render OrdersDateSelection when user is on Past orders tab', () => { + render(); + expect(screen.getByText('OrdersDateSelection')).toBeInTheDocument(); + }); + it('should not render OrdersDateSelection when user is on Active orders tab', () => { + render(); + expect(screen.queryByText('OrdersDateSelection')).not.toBeInTheDocument(); + }); }); From 779da8094d76672beb7152d42f523051e8ff9d18 Mon Sep 17 00:00:00 2001 From: Nada Date: Tue, 11 Jun 2024 15:46:51 +0400 Subject: [PATCH 3/6] chore: add unit tests for date text field, order date selection, order date selection full page --- .../DateTextField/DateTextField.tsx | 8 ++- .../__tests__/DateTextField.spec.tsx | 30 +++++++++++ .../OrdersDateSelection.tsx | 4 +- .../__tests__/OrdersDateSelection.spec.tsx | 54 +++++++++++++++++++ .../OrdersDateSelectionFullPage.spec.tsx | 31 +++++++++++ 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/components/DateTextField/__tests__/DateTextField.spec.tsx create mode 100644 src/pages/orders/components/OrdersDateSelection/__tests__/OrdersDateSelection.spec.tsx create mode 100644 src/pages/orders/components/OrdersDateSelectionFullPage/__tests__/OrdersDateSelectionFullPage.spec.tsx diff --git a/src/components/DateTextField/DateTextField.tsx b/src/components/DateTextField/DateTextField.tsx index a66d8ba1..d4d6d0ec 100644 --- a/src/components/DateTextField/DateTextField.tsx +++ b/src/components/DateTextField/DateTextField.tsx @@ -18,9 +18,13 @@ const DateTextField = ({ alignedRight = false, label, value, ...rest }: TFlowFie })} islabelAnimationDisabled label={value ? '' : label} - leftPlaceholder={!alignedRight && } + leftPlaceholder={ + !alignedRight && + } readOnly - rightPlaceholder={alignedRight && } + rightPlaceholder={ + alignedRight && + } value={value} wrapperClassName='w-full' /> diff --git a/src/components/DateTextField/__tests__/DateTextField.spec.tsx b/src/components/DateTextField/__tests__/DateTextField.spec.tsx new file mode 100644 index 00000000..9f2f0cfa --- /dev/null +++ b/src/components/DateTextField/__tests__/DateTextField.spec.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@testing-library/react'; +import DateTextField from '../DateTextField'; + +const mockProps = { + label: 'Date from', + value: '', +}; + +describe('DateTextField', () => { + it('should render DateTextField', () => { + render(); + expect(screen.getByText('Date from')).toBeInTheDocument(); + }); + it('should render DateTextField with value', () => { + render(); + expect(screen.getByDisplayValue('2024-06-01')).toBeInTheDocument(); + }); + it('should render DateTextField with leftPlaceholder', () => { + render(); + expect(screen.getByTestId('dt_calendar_icon_left')).toBeInTheDocument(); + }); + it('should render DateTextField with rightplaceholder when alignedRight is true', () => { + render(); + expect(screen.getByTestId('dt_calendar_icon_right')).toBeInTheDocument(); + }); + it('should render DateTextField without label when value is present', () => { + render(); + expect(screen.queryByText('Date from')).not.toBeInTheDocument(); + }); +}); diff --git a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx index 70758be8..1d525c2e 100644 --- a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx +++ b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx @@ -31,11 +31,11 @@ const OrdersDateSelection = ({ fromDate, setFromDate, setToDate, toDate }: TOrde } onClick={() => setIsDateSelectionOpen(true)} readOnly - value={fromDate && toDate ? `${fromDate} - ${toDate}` : 'All time'} + value={fromDate && toDate ? `${fromDate} - ${toDate}` : localize('All time')} /> {isDateSelectionOpen && ( ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn(() => ({ isMobile: false })), +})); + +const mockProps = { + fromDate: '', + setFromDate: jest.fn(), + setToDate: jest.fn(), + toDate: '', +}; + +describe('OrdersDateSelection', () => { + it('should render OrdersDateSelection', () => { + render(); + expect(screen.getByText('Date from')).toBeInTheDocument(); + expect(screen.getByText('Today')).toBeInTheDocument(); + }); + it('should render the date input section component on mobile', () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: true }); + render(); + expect(screen.getByText('All time')).toBeInTheDocument(); + }); + it('should open the full page date selection when the input is clicked on mobile', async () => { + render(); + const input = screen.getByPlaceholderText('All time'); + await userEvent.click(input); + expect(screen.getByText('Please select duration')).toBeInTheDocument(); + }); + it('should close the full page date selection when the cancel button is clicked', async () => { + render(); + const input = screen.getByPlaceholderText('All time'); + await userEvent.click(input); + const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + await userEvent.click(cancelButton); + expect(screen.queryByText('Please select duration')).not.toBeInTheDocument(); + }); + it('should show the selected dates when from Date and to Date are provided', async () => { + render(); + const input = screen.getByPlaceholderText('All time'); + expect(input).toHaveValue('2024-06-01 - 2024-06-04'); + }); + it('should show the selected dates when from Date and to Date are provided for desktop', async () => { + (useDevice as jest.Mock).mockReturnValue({ isMobile: false }); + render(); + expect(screen.getByDisplayValue('Jun 01, 2024')).toBeInTheDocument(); + expect(screen.getByDisplayValue('Jun 04, 2024')).toBeInTheDocument(); + }); +}); diff --git a/src/pages/orders/components/OrdersDateSelectionFullPage/__tests__/OrdersDateSelectionFullPage.spec.tsx b/src/pages/orders/components/OrdersDateSelectionFullPage/__tests__/OrdersDateSelectionFullPage.spec.tsx new file mode 100644 index 00000000..a6a2e486 --- /dev/null +++ b/src/pages/orders/components/OrdersDateSelectionFullPage/__tests__/OrdersDateSelectionFullPage.spec.tsx @@ -0,0 +1,31 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import OrdersDateSelectionFullPage from '../OrdersDateSelectionFullPage'; + +const mockProps = { + fromDate: '', + onClickCancel: jest.fn(), + setFromDate: jest.fn(), + setToDate: jest.fn(), + toDate: '', +}; + +describe('OrdersDateSelectionFullPage', () => { + it('should render OrdersDateSelectionFullPage', () => { + render(); + expect(screen.getByText('Please select duration')).toBeInTheDocument(); + }); + it('should call onClickCancel when the cancel button is clicked', async () => { + render(); + const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + await userEvent.click(cancelButton); + expect(mockProps.onClickCancel).toHaveBeenCalledTimes(1); + }); + it('should call setFromDate and setToDate when the OK button is clicked', async () => { + render(); + const okButton = screen.getByRole('button', { name: 'OK' }); + await userEvent.click(okButton); + expect(mockProps.setFromDate).toHaveBeenCalledTimes(1); + expect(mockProps.setToDate).toHaveBeenCalledTimes(1); + }); +}); From aebf140cc5ec53b6df0a835a54c6397737f0c921 Mon Sep 17 00:00:00 2001 From: Nada Date: Tue, 11 Jun 2024 16:03:21 +0400 Subject: [PATCH 4/6] chore: add unit test for date picker --- src/components/DatePicker/DatePicker.tsx | 2 +- .../DatePicker/__tests__/DatePicker.spec.tsx | 39 +++++++++++++++++++ src/components/DatePicker/utils.ts | 13 ------- .../DateTextField/DateTextField.tsx | 6 +-- src/utils/date.ts | 24 ++++++++++++ src/utils/index.ts | 1 + 6 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/components/DatePicker/__tests__/DatePicker.spec.tsx delete mode 100644 src/components/DatePicker/utils.ts create mode 100644 src/utils/date.ts diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index d20eb861..580a2547 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -2,8 +2,8 @@ import { ComponentProps, useEffect, useRef, useState } from 'react'; import clsx from 'clsx'; import Calendar, { CalendarProps } from 'react-calendar'; import { useOnClickOutside } from 'usehooks-ts'; +import { customFormatShortWeekday, unixToDateString } from '@/utils'; import DateTextField from '../DateTextField/DateTextField'; -import { customFormatShortWeekday, unixToDateString } from './utils'; import 'react-calendar/dist/Calendar.css'; import './DatePicker.scss'; diff --git a/src/components/DatePicker/__tests__/DatePicker.spec.tsx b/src/components/DatePicker/__tests__/DatePicker.spec.tsx new file mode 100644 index 00000000..f1b127ee --- /dev/null +++ b/src/components/DatePicker/__tests__/DatePicker.spec.tsx @@ -0,0 +1,39 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import DatePicker from '../DatePicker'; + +const mockProps = { + label: 'Date from', + onDateChange: jest.fn(), + value: '', +}; + +describe('DatePicker', () => { + it('should render DatePicker', () => { + render(); + expect(screen.getByText('Date from')).toBeInTheDocument(); + }); + it('should render DatePicker with value', () => { + render(); + expect(screen.getByDisplayValue('Jun 01, 2024')).toBeInTheDocument(); + }); + it('should open the calendar on clicking the input field', async () => { + render(); + const input = screen.getByPlaceholderText('Date from'); + await userEvent.click(input); + expect(screen.getByTestId('dt_datepicker_container')).toBeInTheDocument(); + }); + it('should close the calendar on clicking outside the calendar', async () => { + render(); + const input = screen.getByPlaceholderText('Date from'); + await userEvent.click(input); + await userEvent.click(document.body); + expect(screen.queryByTestId('dt_datepicker_container')).not.toBeInTheDocument(); + }); + it('should handle date change', async () => { + render(); + const input = screen.getByPlaceholderText('Date from'); + await userEvent.type(input, '2024-06-01'); + expect(mockProps.onDateChange).toHaveBeenCalledWith('Jun 01, 2024'); + }); +}); diff --git a/src/components/DatePicker/utils.ts b/src/components/DatePicker/utils.ts deleted file mode 100644 index 363ded47..00000000 --- a/src/components/DatePicker/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import moment from 'moment'; - -export function customFormatShortWeekday(_locale: string | undefined, date: Date) { - const weekdays = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; - - return weekdays[date.getDay()]; -} - -export function unixToDateString(date: Date, format = 'YYYY-MM-DD') { - const formattedDate = moment(date).format(format); - - return formattedDate; -} diff --git a/src/components/DateTextField/DateTextField.tsx b/src/components/DateTextField/DateTextField.tsx index d4d6d0ec..f4a35141 100644 --- a/src/components/DateTextField/DateTextField.tsx +++ b/src/components/DateTextField/DateTextField.tsx @@ -4,11 +4,11 @@ import { LabelPairedCalendarRangeMdRegularIcon, LegacyCalendarDateFrom1pxIcon } import { Input } from '@deriv-com/ui'; import './DateTextField.scss'; -export interface TFlowFieldProps extends ComponentProps { +type TDateTextFieldProps = ComponentProps & { alignedRight?: boolean; -} +}; -const DateTextField = ({ alignedRight = false, label, value, ...rest }: TFlowFieldProps) => { +const DateTextField = ({ alignedRight = false, label, value, ...rest }: TDateTextFieldProps) => { return (
{ + const weekdays = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; + + return weekdays[date.getDay()]; +}; + +/** + * The below function converts the unix date to a string in the given format. + * @param date + * @param format + * @returns + */ +export const unixToDateString = (date: Date, format = 'YYYY-MM-DD') => { + const formattedDate = moment(date).format(format); + + return formattedDate; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index f97ef030..0b573136 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ export * from './ad-utils'; export * from './adverts'; export * from './currency'; +export * from './date'; export * from './file-dropzone'; export * from './file-uploader'; export * from './format-value'; From 5631d257d36f9c7b425022941eee94c387dbc63f Mon Sep 17 00:00:00 2001 From: Nada Date: Tue, 11 Jun 2024 16:10:31 +0400 Subject: [PATCH 5/6] fix: removed active start date --- src/components/DatePicker/DatePicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 580a2547..6e19771b 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -86,7 +86,6 @@ const DatePicker = ({ data-testid='dt_datepicker_container' > Date: Wed, 12 Jun 2024 10:15:02 +0400 Subject: [PATCH 6/6] fix: pr review comments --- src/components/DatePicker/DatePicker.tsx | 4 ++-- .../OrdersDateSelection/OrdersDateSelection.scss | 1 - .../OrdersDateSelection/OrdersDateSelection.tsx | 9 +-------- .../Orders/OrdersTableHeader/OrdersTableHeader.scss | 1 - 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 6e19771b..a6ab8a67 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -57,9 +57,9 @@ const DatePicker = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDate]); + // To not display the date value when today's date is selected. const getValue = () => { - const isToday = selectedDate !== null && selectedDate.getDate() === new Date().getDate(); - if (selectedDate !== null && selectedDate !== new Date() && (!isToday || showLabel)) { + if (selectedDate && (showLabel || selectedDate.getDate() !== new Date().getDate())) { return unixToDateString(selectedDate, displayFormat); } return ''; diff --git a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss index 0ce8ceee..d4923942 100644 --- a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss +++ b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.scss @@ -1,7 +1,6 @@ .orders-date-selection { @include desktop { display: flex; - flex-direction: row; gap: 0.8rem; padding: 0.4rem 0; align-items: center; diff --git a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx index 1d525c2e..c2dcad98 100644 --- a/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx +++ b/src/pages/orders/components/OrdersDateSelection/OrdersDateSelection.tsx @@ -18,13 +18,6 @@ const OrdersDateSelection = ({ fromDate, setFromDate, setToDate, toDate }: TOrde const { localize } = useTranslations(); const [isDateSelectionOpen, setIsDateSelectionOpen] = useState(false); - const getMaxDate = () => { - if (toDate) { - return new Date(toDate); - } - return new Date(); - }; - if (isMobile) { return (
@@ -54,7 +47,7 @@ const OrdersDateSelection = ({ fromDate, setFromDate, setToDate, toDate }: TOrde