diff --git a/src/components/ui/FavouriteButton/FavouriteButton.stories.tsx b/src/components/ui/FavouriteButton/FavouriteButton.stories.tsx index baa1cfb6..ace97151 100644 --- a/src/components/ui/FavouriteButton/FavouriteButton.stories.tsx +++ b/src/components/ui/FavouriteButton/FavouriteButton.stories.tsx @@ -4,7 +4,7 @@ import { FavouriteButtonProps } from "./FavouriteButton.types"; import { Default as DefaultStory } from "../Dropdown/Dropdown.stories"; export default { - title: "Components/FavouriteButton", + title: "Components/UI/FavouriteButton", component: FavouriteButton, }; diff --git a/src/components/ui/Icon/Icon.stories.tsx b/src/components/ui/Icon/Icon.stories.tsx index 643c10a9..5e3ace47 100644 --- a/src/components/ui/Icon/Icon.stories.tsx +++ b/src/components/ui/Icon/Icon.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Icon } from "./Icon"; const meta = { - title: "UI/Icon", + title: "Components/UI/Icon", component: Icon, parameters: { layout: "centered", diff --git a/src/components/widgets/Date/DateInput/DateInput.stories.tsx b/src/components/widgets/Date/DateInput/DateInput.stories.tsx index 36db7f2d..140cdbc8 100644 --- a/src/components/widgets/Date/DateInput/DateInput.stories.tsx +++ b/src/components/widgets/Date/DateInput/DateInput.stories.tsx @@ -1,51 +1,335 @@ -import React from "react"; -import { ComponentStory, ComponentMeta } from "@storybook/react"; +import React, { useEffect, useState } from "react"; +import { Meta, StoryFn } from "@storybook/react"; +import { DateInput } from "./DateInput"; +import { Form } from "antd"; +import dayjs from "@/helpers/dayjs"; +import { DateInputProps } from "./DateInput.types"; -import { DateInput } from "."; -import { BaseFieldArgTypes } from "@/components/form/Field/BaseField.argTypes"; - -const category = "Date specific"; +// Extended props for Storybook args +type StoryArgs = DateInputProps & { + timezone?: string; +}; export default { - title: "Work in progress/Widgets/Date/DateInput", + title: "Components/Widgets/Date/DateInput", component: DateInput, + parameters: { + layout: "centered", + }, argTypes: { - ...BaseFieldArgTypes, + timezone: { + control: "select", + options: [ + "UTC", + "Europe/Madrid", + "Europe/London", + "America/New_York", + "Asia/Tokyo", + "Australia/Sydney", + ], + description: "Timezone for the date picker", + defaultValue: "Europe/Madrid", + }, showTime: { - description: "Enable time for picking values", - table: { - defaultValue: { summary: false }, - category, - }, + control: "boolean", + description: "Whether to show time picker", + defaultValue: false, }, - onChange: { - action: "onChange", - table: { - category, - }, + required: { + control: "boolean", + description: "Whether the field is required", + defaultValue: false, }, - value: { - table: { - category, - }, + readOnly: { + control: "boolean", + description: "Whether the field is read-only", + defaultValue: false, }, }, -} as ComponentMeta; +} as Meta; -const Template: ComponentStory = (args) => ( - -); +const Template: StoryFn = (args) => { + const [form] = Form.useForm(); + const fieldName = args.id || "dateField"; + const [currentValue, setCurrentValue] = useState( + args.value, + ); + // Set initial values when args.value changes + useEffect(() => { + if (args.value) { + form.setFieldsValue({ [fieldName]: args.value }); + setCurrentValue(args.value); + } + }, [args.value, form, fieldName]); + + const handleChange = (value: string | null | undefined) => { + form.setFieldValue(fieldName, value); + setCurrentValue(value || undefined); + args.onChange?.(value); + }; + + return ( +
+
{ + const newValue = form.getFieldValue(fieldName); + setCurrentValue(newValue); + }} + > + + + +
+ Debug Information: +
String value: {currentValue}
+
timezone: {args.timezone}
+
showTime: {args.showTime?.toString()}
+
required: {args.required?.toString()}
+
readOnly: {args.readOnly?.toString()}
+
+
+
+ ); +}; + +// Date picker with time export const Basic = Template.bind({}); Basic.args = { + id: "basic-date", + value: "2024-03-10 14:30:00", + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// Required field +export const Required = Template.bind({}); +Required.args = { + id: "required-date", + value: "2024-03-10 14:30:00", + showTime: true, + required: true, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// Read-only field +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + id: "readonly-date", + value: dayjs().format("YYYY-MM-DD HH:mm:ss"), + showTime: true, + required: false, + readOnly: true, + timezone: "Europe/Madrid", +}; + +// Invalid date handling +export const InvalidDate = Template.bind({}); +InvalidDate.args = { + id: "invalid-date", + value: "invalid-date", // Should show error state + showTime: true, required: false, readOnly: false, + timezone: "Europe/Madrid", +}; + +// Date only (no time) +export const DateOnly = Template.bind({}); +DateOnly.args = { + id: "date-only", + value: "2024-03-10", showTime: false, + required: false, + readOnly: false, + timezone: "Europe/Madrid", }; -export const BasicWithTime = Template.bind({}); -BasicWithTime.args = { +// Timezone in OOUI - Madrid +export const TimezoneInOouiMadrid = Template.bind({}); +TimezoneInOouiMadrid.args = { + id: "madrid-tz", + value: "2025-05-26 12:00:00", + showTime: true, required: false, readOnly: false, + timezone: "Europe/Madrid", +}; + +// Timezone in OOUI - Tokyo +export const TimezoneInOouiTokyo = Template.bind({}); +TimezoneInOouiTokyo.args = { + id: "tokyo-tz", + value: "2025-05-26 21:00:00", + showTime: true, + required: false, + readOnly: false, + timezone: "Asia/Tokyo", +}; + +// Timezone in OOUI - UTC +export const TimezoneInOouiUTC = Template.bind({}); +TimezoneInOouiUTC.args = { + id: "utc-tz", + value: "2025-05-26 12:00:00", + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// DST Edge Cases - Madrid (Start of DST) +export const DSTStartMadrid = Template.bind({}); +DSTStartMadrid.args = { + id: "dst-start-madrid", + value: "2025-03-30 01:59:59", // Just before DST starts showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// DST Edge Cases - Madrid (End of DST) +export const DSTEndMadrid = Template.bind({}); +DSTEndMadrid.args = { + id: "dst-end-madrid", + value: "2025-10-26 02:59:59", // Just before DST ends + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// DST Edge Cases - UTC Reference +export const UTCReference = Template.bind({}); +UTCReference.args = { + id: "utc-reference", + value: "2025-03-30 00:59:59", // Reference time in UTC + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// DST Transition - Madrid Spring Forward (Missing Hour) +export const DSTMadridSpringForward = Template.bind({}); +DSTMadridSpringForward.args = { + id: "dst-madrid-spring", + value: "2025-03-30 02:00:00", // This hour doesn't exist due to spring forward + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// DST Transition - Madrid Fall Back (Ambiguous First Hour) +export const DSTMadridFallBackFirst = Template.bind({}); +DSTMadridFallBackFirst.args = { + id: "dst-madrid-fall-first", + value: "2025-10-26 02:00:00", // First occurrence of 2 AM + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// DST Transition - Madrid Fall Back (Ambiguous Second Hour) +export const DSTMadridFallBackSecond = Template.bind({}); +DSTMadridFallBackSecond.args = { + id: "dst-madrid-fall-second", + value: "2025-10-26 02:00:00", // Second occurrence of 2 AM + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// DST Transition - UTC Reference for Madrid Spring Forward +export const DSTUtcSpringForward = Template.bind({}); +DSTUtcSpringForward.args = { + id: "dst-utc-spring", + value: "2025-03-30 01:00:00", // UTC time during Madrid's spring forward + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// DST Transition - UTC Reference for Madrid Fall Back +export const DSTUtcFallBack = Template.bind({}); +DSTUtcFallBack.args = { + id: "dst-utc-fall", + value: "2025-10-26 01:00:00", // UTC time during Madrid's fall back + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// UTC Edge Case - Specific UTC Time +export const SpecificUTCTime = Template.bind({}); +SpecificUTCTime.args = { + id: "specific-utc", + value: "2023-03-26 02:00:00", // Specific UTC time + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// UTC Edge Case - UTC to Madrid DST Transition +export const UTCToMadridDST = Template.bind({}); +UTCToMadridDST.args = { + id: "utc-to-madrid-dst", + value: "2023-03-26 02:00:00", // UTC time during Madrid's DST transition + showTime: true, + required: false, + readOnly: false, + timezone: "Europe/Madrid", +}; + +// UTC Edge Case - UTC Midnight Transition +export const UTCMidnightTransition = Template.bind({}); +UTCMidnightTransition.args = { + id: "utc-midnight", + value: "2023-03-26 00:00:00", // UTC midnight + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", +}; + +// UTC Edge Case - UTC to Tokyo (Next Day) +export const UTCToTokyoNextDay = Template.bind({}); +UTCToTokyoNextDay.args = { + id: "utc-to-tokyo", + value: "2023-03-26 15:00:00", // UTC time that results in next day in Tokyo + showTime: true, + required: false, + readOnly: false, + timezone: "Asia/Tokyo", +}; + +// UTC Edge Case - UTC Last Second of Day +export const UTCLastSecond = Template.bind({}); +UTCLastSecond.args = { + id: "utc-last-second", + value: "2023-03-26 23:59:59", // Last second of the UTC day + showTime: true, + required: false, + readOnly: false, + timezone: "UTC", }; diff --git a/src/components/widgets/Date/DateInput/DateInput.styles.tsx b/src/components/widgets/Date/DateInput/DateInput.styles.tsx deleted file mode 100644 index 9bcf908a..00000000 --- a/src/components/widgets/Date/DateInput/DateInput.styles.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import styled from "styled-components"; -import { RequiredFieldStyle } from "@/components/form/Field"; -import { DatePicker } from "antd"; - -export const RequiredDatePicker = styled(DatePicker)` - ${RequiredFieldStyle} -`; diff --git a/src/components/widgets/Date/DateInput/DateInput.test.tsx b/src/components/widgets/Date/DateInput/DateInput.test.tsx deleted file mode 100644 index 4c0b6705..00000000 --- a/src/components/widgets/Date/DateInput/DateInput.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import { composeStories } from "@storybook/testing-react"; -import * as stories from "./DateInput.stories"; - -const { Basic } = composeStories(stories); - -describe("DateInput", () => { - const testCases = Object.values(composeStories(stories)).map((Story) => [ - Story.storyName!, - Story, - ]); - - test.each(testCases)("Renders %s story", async (_storyName, Story) => { - const tree = render(); - expect(tree.baseElement).toMatchSnapshot(); - }); -}); diff --git a/src/components/widgets/Date/DateInput/DateInput.tsx b/src/components/widgets/Date/DateInput/DateInput.tsx index 8ebee9f2..88d4dfff 100644 --- a/src/components/widgets/Date/DateInput/DateInput.tsx +++ b/src/components/widgets/Date/DateInput/DateInput.tsx @@ -1,64 +1,123 @@ -// @TODO: Review this component -// @ts-nocheck -/* eslint-disable */ -import { DatePicker } from "antd"; -import React from "react"; -import dayjs, { Dayjs } from "dayjs"; -import { DatePickerInputProps } from "./DateInput.types"; -import { RequiredDatePicker } from "./DateInput.styles"; +import { DatePicker as AntDatePicker, Tooltip } from "antd"; +import React, { useCallback, useMemo, memo, useState } from "react"; +import { Dayjs } from "dayjs"; +import { useDatePickerLocale } from "./hooks/useDatePickerLocale"; +import { + DateMode, + DatePickerConfig, + parseDateSafely, +} from "./helpers/DatePicker.helpers"; +import { useDatePickerHandlers } from "./hooks/useDatePickerHandlers"; +import { DateInputProps } from "./DateInput.types"; +import { useRequiredStyle } from "@/hooks/useRequiredStyle"; -const DatePickerConfig = { - date: { - placeholder: "__/__/____", - dateDisplayFormat: "", - dateInternalFormat: "", - }, - time: { - placeholder: "__/__/____ __:__:__", - dateDisplayFormat: "", - dateInternalFormat: "", - }, -}; +const DateInput: React.FC = memo((props: DateInputProps) => { + const { + value, + onChange, + showTime, + id, + readOnly, + required, + timezone = "Europe/Madrid", // TODO: This is hardcoded because server assumes this TZ for the moment + } = props; -export const DateInput: React.FC = ( - props: DatePickerInputProps, -) => { - const { value, onChange, readOnly, required, showTime } = props; - const mode = showTime ? "time" : "date"; - const InputComponent: any = - required && !readOnly ? RequiredDatePicker : DatePicker; + const datePickerLocale = useDatePickerLocale(); + const requiredStyle = useRequiredStyle(required, !!readOnly); + const mode: DateMode = showTime ? "time" : "date"; + const [parseError, setParseError] = useState(null); - function triggerChange(changedValue: undefined | string) { - onChange?.(changedValue!); - } + const internalFormat = DatePickerConfig[mode].dateInternalFormat; - function onValueStringChange(momentDate: Dayjs) { - if (momentDate === null) { - triggerChange(undefined); - return; + // Parse date value using the timezone from ooui + const dateValue = useMemo(() => { + if (!value) return undefined; + + try { + const parsed = parseDateSafely(value, internalFormat, timezone); + + if (!parsed || !parsed.isValid()) { + throw new Error("Invalid date format"); + } + + setParseError(null); + return parsed; + } catch (error) { + console.error({ error, value, timezone, mode }); + const errorMessage = + error instanceof Error ? error.message : "Invalid date"; + setParseError(errorMessage); + return undefined; } + }, [value, internalFormat, timezone, mode]); + + const handleChange = useCallback( + (momentDate: Dayjs | null) => { + if (!momentDate) { + onChange?.(null); + return; + } + try { + const formattedDate = momentDate.format(internalFormat); + setParseError(null); + onChange?.(formattedDate); + } catch (error) { + console.error({ error, timezone, mode }); + const errorMessage = + error instanceof Error ? error.message : "Invalid date"; + setParseError(errorMessage); + } + }, + [onChange, timezone, internalFormat, mode], + ); - triggerChange(momentDate.format(DatePickerConfig[mode].dateInternalFormat)); - } + const { handleKeyDown, handleBlur } = useDatePickerHandlers({ + mode, + showTime, + onChange, + }); - const showTimeParms = showTime - ? { defaultValue: dayjs("00:00:00", "HH:mm:ss") } - : undefined; - const dateValue = value ? dayjs(value) : undefined; + const pickerConfig = useMemo( + () => ({ + style: { + width: "100%", + ...requiredStyle, + ...(parseError && { borderColor: "#ff4d4f" }), + }, + placeholder: DatePickerConfig[mode].placeholder, + format: DatePickerConfig[mode].dateDisplayFormat, + }), + [mode, requiredStyle, parseError], + ); return ( - + + handleBlur(e as any)} + onKeyDown={(e) => handleKeyDown(e as any)} + showNow={false} + showToday={false} + locale={datePickerLocale} + status={parseError ? "error" : undefined} + /> + ); -}; +}); + +DateInput.displayName = "DateInput"; +const MemoizedDateInput = memo(DateInput); + +export { MemoizedDateInput as DateInput }; diff --git a/src/components/widgets/Date/DateInput/DateInput.types.ts b/src/components/widgets/Date/DateInput/DateInput.types.ts index fc8ce19e..b0b076a1 100644 --- a/src/components/widgets/Date/DateInput/DateInput.types.ts +++ b/src/components/widgets/Date/DateInput/DateInput.types.ts @@ -1,7 +1,9 @@ -import { BaseFieldProps } from "@/components/form/Field"; - -export type BaseDatePickerProps = { +export type DateInputProps = { + value?: string; + onChange?: (value: string | null | undefined) => void; showTime?: boolean; + id: string; + readOnly: boolean; + required: boolean; + timezone?: string; }; - -export type DatePickerInputProps = BaseDatePickerProps & BaseFieldProps; diff --git a/src/components/widgets/Date/DateInput/__snapshots__/DateInput.test.tsx.snap b/src/components/widgets/Date/DateInput/__snapshots__/DateInput.test.tsx.snap deleted file mode 100644 index 1f198a15..00000000 --- a/src/components/widgets/Date/DateInput/__snapshots__/DateInput.test.tsx.snap +++ /dev/null @@ -1,95 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DateInput Renders Basic story 1`] = ` - -
-
-
- - - - - - -
-
-
- -`; - -exports[`DateInput Renders BasicWithTime story 1`] = ` - -
-
-
- - - - - - -
-
-
- -`; diff --git a/src/components/widgets/Date/DateInput/helpers/DatePicker.helpers.ts b/src/components/widgets/Date/DateInput/helpers/DatePicker.helpers.ts new file mode 100644 index 00000000..7cbf2c63 --- /dev/null +++ b/src/components/widgets/Date/DateInput/helpers/DatePicker.helpers.ts @@ -0,0 +1,151 @@ +import { Dayjs } from "dayjs"; +import dayjs from "@/helpers/dayjs"; + +export type DateMode = "date" | "time"; + +export const DatePickerConfig = { + date: { + placeholder: "__/__/____", + dateDisplayFormat: "DD/MM/YYYY", + dateInternalFormat: "YYYY-MM-DD", + }, + time: { + placeholder: "__/__/____ __:__:__", + dateDisplayFormat: "DD/MM/YYYY HH:mm:ss", + dateInternalFormat: "YYYY-MM-DD HH:mm:ss", + }, +} as const; + +export type DateTimePatterns = { + day: RegExp; + dayMonth: RegExp; + fullDate: RegExp; + withHours: RegExp; + withMinutes: RegExp; + withSeconds: RegExp; +}; + +export const createFormatRegex = (format: string): RegExp => { + return new RegExp( + "^" + + format + .replace(/DD/g, "\\d{2}") + .replace(/MM/g, "\\d{2}") + .replace(/YYYY/g, "\\d{4}") + .replace(/HH/g, "\\d{2}") + .replace(/mm/g, "\\d{2}") + .replace(/ss/g, "\\d{2}") + .replace(/\//g, "\\/") + .replace(/\s/g, "\\s") + + "$", + ); +}; + +export const createDateTimePatterns = (): DateTimePatterns => ({ + day: createFormatRegex("DD"), + dayMonth: createFormatRegex("DD/MM"), + fullDate: createFormatRegex("DD/MM/YYYY"), + withHours: createFormatRegex("DD/MM/YYYY HH"), + withMinutes: createFormatRegex("DD/MM/YYYY HH:mm"), + withSeconds: createFormatRegex("DD/MM/YYYY HH:mm:ss"), +}); + +type UpdateDateTimeParams = { + currentValue: string | undefined; + now: Dayjs; + mode: DateMode; + showTime: boolean; + onChange: (value: string) => void; +}; + +export const updateDateTime = (params: UpdateDateTimeParams) => { + const { currentValue, now, mode, showTime, onChange } = params; + const patterns = createDateTimePatterns(); + + // Handle undefined or empty value + if (!currentValue || currentValue.trim() === "") { + onChange(now.format(DatePickerConfig[mode].dateInternalFormat)); + return; + } + + // Handle day only + if (patterns.day.test(currentValue)) { + const newDate = now.date(parseInt(currentValue)); + onChange(newDate.format(DatePickerConfig[mode].dateInternalFormat)); + } + + // Handle day and month + if (patterns.dayMonth.test(currentValue)) { + const [day, month] = currentValue.split("/").map((n) => parseInt(n)); + const newDate = now.date(day).month(month - 1); + onChange(newDate.format(DatePickerConfig[mode].dateInternalFormat)); + } + + // Handle full date + if (patterns.fullDate.test(currentValue)) { + const [day, month, year] = currentValue.split("/").map((n) => parseInt(n)); + const newDate = now + .date(day) + .month(month - 1) + .year(year); + + if (!showTime) { + onChange(newDate.format(DatePickerConfig.date.dateInternalFormat)); + } + + onChange(newDate.format(DatePickerConfig.time.dateInternalFormat)); + } + + // Handle time components + if (showTime) { + const [datePart, timePart] = currentValue.split(" "); + const [day, month, year] = datePart.split("/").map((n) => parseInt(n)); + let newDate = now + .date(day) + .month(month - 1) + .year(year); + + if (patterns.withHours.test(currentValue)) { + const [hours] = timePart.split(":").map((n) => parseInt(n)); + newDate = newDate.hour(hours); + onChange(newDate.format(DatePickerConfig.time.dateInternalFormat)); + } + + if (patterns.withMinutes.test(currentValue)) { + const [hours, minutes] = timePart.split(":").map((n) => parseInt(n)); + newDate = newDate.hour(hours).minute(minutes); + onChange(newDate.format(DatePickerConfig.time.dateInternalFormat)); + } + } +}; + +export const shouldHandleEnter = ( + currentValue: string, + showTime: boolean, +): boolean => { + if (!currentValue) return true; + + const patterns = createDateTimePatterns(); + return ( + patterns.day.test(currentValue) || + patterns.dayMonth.test(currentValue) || + patterns.fullDate.test(currentValue) || + (showTime && + (patterns.fullDate.test(currentValue) || + patterns.withHours.test(currentValue) || + patterns.withMinutes.test(currentValue))) + ); +}; + +export const parseDateSafely = ( + value: string, + format: string, + timezone?: string, +): Dayjs | null => { + try { + return dayjs.tz(value, format, timezone); + } catch (e) { + console.error("Parse error:", e); + return null; + } +}; diff --git a/src/components/widgets/Date/DateInput/hooks/useDatePickerHandlers.ts b/src/components/widgets/Date/DateInput/hooks/useDatePickerHandlers.ts new file mode 100644 index 00000000..f36b2656 --- /dev/null +++ b/src/components/widgets/Date/DateInput/hooks/useDatePickerHandlers.ts @@ -0,0 +1,103 @@ +import { useCallback, useRef } from "react"; +import dayjs from "@/helpers/dayjs"; +import { + DateMode, + DatePickerConfig, + shouldHandleEnter, + updateDateTime, +} from "../helpers/DatePicker.helpers"; + +type UseDatePickerHandlersParams = { + mode: DateMode; + showTime?: boolean; + onChange?: (value: string | null | undefined) => void; +}; + +export const useDatePickerHandlers = ({ + mode, + showTime = false, + onChange, +}: UseDatePickerHandlersParams) => { + const escapeHandled = useRef(false); + + const handleBlur = useCallback( + (e: React.FocusEvent) => { + const input = e.target; + if (input.value) { + const dayJsDate = dayjs( + input.value, + DatePickerConfig[mode].dateDisplayFormat, + ).format(DatePickerConfig[mode].dateInternalFormat); + onChange?.(dayJsDate); + } + }, + [mode, onChange], + ); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + // If the event was already handled by a parent component, don't handle it again + if (e.defaultPrevented) { + return; + } + + if (e.key === "Enter") { + const input = e.target as HTMLInputElement; + const currentValue = input.value; + + if (!shouldHandleEnter(currentValue, showTime)) { + return; + } + + e.preventDefault(); + + updateDateTime({ + currentValue, + now: dayjs(), + mode, + showTime, + onChange: (value) => onChange?.(value), + }); + } else if (e.key === "Escape" && !escapeHandled.current) { + escapeHandled.current = true; + // Reset the flag after a short delay + setTimeout(() => { + escapeHandled.current = false; + }, 200); + + e.preventDefault(); + e.stopPropagation(); // Stop the event from bubbling up + + const input = e.currentTarget; + if (input.value === "") { + onChange?.(null); + } else { + const dayJsDate = dayjs( + input.value, + DatePickerConfig[mode].dateDisplayFormat, + ).format(DatePickerConfig[mode].dateInternalFormat); + onChange?.(dayJsDate); + } + + const focusableElements = + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + const elements = Array.from( + document.querySelectorAll(focusableElements), + ) as HTMLElement[]; + const index = elements.indexOf(input); + + setTimeout(() => { + if (index > -1 && index < elements.length - 1) { + elements[index + 1].focus(); + } + }, 100); + } + }, + [showTime, mode, onChange], + ); + + return { + handleKeyDown, + handleBlur, + }; +}; diff --git a/src/components/widgets/Date/DateInput/hooks/useDatePickerLocale.ts b/src/components/widgets/Date/DateInput/hooks/useDatePickerLocale.ts new file mode 100644 index 00000000..47663739 --- /dev/null +++ b/src/components/widgets/Date/DateInput/hooks/useDatePickerLocale.ts @@ -0,0 +1,15 @@ +import enUS from "antd/es/date-picker/locale/en_US"; +import esES from "antd/es/date-picker/locale/es_ES"; +import caES from "antd/es/date-picker/locale/ca_ES"; +import { useLocale } from "@/context/LocaleContext"; + +const antdLocales = { + en_US: enUS, + es_ES: esES, + ca_ES: caES, +}; + +export const useDatePickerLocale = () => { + const { locale } = useLocale(); + return antdLocales[locale]; +}; diff --git a/src/components/widgets/Date/DateInput/index.ts b/src/components/widgets/Date/DateInput/index.ts index 543281a5..02b550b6 100644 --- a/src/components/widgets/Date/DateInput/index.ts +++ b/src/components/widgets/Date/DateInput/index.ts @@ -1 +1,2 @@ -export * from "./DateInput"; \ No newline at end of file +export * from "./DateInput"; +export * from "./hooks/useDatePickerLocale"; diff --git a/src/components/widgets/Date/DateSearch/DateSearch.helper.test.tsx b/src/components/widgets/Date/DateSearch/DateSearch.helper.test.tsx deleted file mode 100644 index 892f3697..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.helper.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import dayjs from "dayjs"; -import { getMomentValue } from "./DateSearch.helper"; -import { defaultDateFormat } from "./DateSearch.types"; - -describe("DateSearch.helper", () => { - beforeEach(() => { - jest.spyOn(console, "warn").mockImplementation(() => {}); - }); - - describe("in getMomentValue method", () => { - it("should return undefined if value is undefined", () => { - const result = getMomentValue(undefined); - expect(result).toBeUndefined(); - }); - it("should return from value and to value as null if we only pass from", () => { - const result = getMomentValue(["12/12/2020", undefined]); - const momentExpected = dayjs("12/12/2020", defaultDateFormat); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(momentExpected.toISOString()); - expect(result[1]).toBe(null); - }); - it("should return to value and from value as null if we only pass to", () => { - const result = getMomentValue([undefined, "01/01/2020"]); - const momentExpected = dayjs("01/01/2020", defaultDateFormat); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(momentExpected.toISOString()); - expect(result[0]).toBe(null); - }); - it("should return from and to values", () => { - const result = getMomentValue(["12/12/2020", "01/01/2020"]); - const moment1Expected = dayjs("12/12/2020", defaultDateFormat); - const moment2Expected = dayjs("01/01/2020", defaultDateFormat); - - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(moment1Expected.toISOString()); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(moment2Expected.toISOString()); - }); - it("should return null array if we pass a null array", () => { - const result = getMomentValue([null, null]); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBe(null); - expect(result[1]).toBe(null); - }); - }); -}); diff --git a/src/components/widgets/Date/DateSearch/DateSearch.helper.ts b/src/components/widgets/Date/DateSearch/DateSearch.helper.ts deleted file mode 100644 index 310051b8..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.helper.ts +++ /dev/null @@ -1,19 +0,0 @@ -import dayjs, { Dayjs } from "dayjs"; -import { defaultDateFormat } from "./DateSearch.types"; - -export const getMomentValue = (value: [string, string]) => { - return ( - value && [ - value[0] ? dayjs(value[0]) : null, - value[1] ? dayjs(value[1]) : null, - ] - ); -}; - -export const convertMomentDateArrayToStringArray = (momentValues: Dayjs[]) => { - const from = momentValues[0] - ? momentValues[0].format(defaultDateFormat) - : null; - const to = momentValues[1] ? momentValues[1].format(defaultDateFormat) : null; - return [from, to]; -}; diff --git a/src/components/widgets/Date/DateSearch/DateSearch.stories.tsx b/src/components/widgets/Date/DateSearch/DateSearch.stories.tsx deleted file mode 100644 index 2e7e0851..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { ComponentStory, ComponentMeta } from "@storybook/react"; - -import { DateSearch } from "."; - -const category = "Date specific"; - -export default { - title: "Work in progress/Widgets/Date/DateSearch", - component: DateSearch, - argTypes: { - onChange: { - action: "onChange", - table: { - category, - }, - }, - value: { - table: { - category, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => { - const [value, setValue] = useState(args.value); - - useEffect(() => { - setValue(args.value); - }, [args.value]); - - return ( - { - setValue(value); - args.onChange?.(value); - }} - /> - ); -}; - -export const Basic = Template.bind({}); -Basic.args = {}; - -export const BasicWithValues = Template.bind({}); -BasicWithValues.args = { value: ["2020-01-01", "2020-12-01"] }; diff --git a/src/components/widgets/Date/DateSearch/DateSearch.test.tsx b/src/components/widgets/Date/DateSearch/DateSearch.test.tsx deleted file mode 100644 index 5f94cf75..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; - -import { composeStories } from "@storybook/testing-react"; -import * as stories from "./DateSearch.stories"; - -describe("DateSearch", () => { - const testCases = Object.values(composeStories(stories)).map((Story) => [ - Story.storyName!, - Story, - ]); - - test.each(testCases)("Renders %s story", async (_storyName, Story) => { - const tree = render(); - expect(tree.baseElement).toMatchSnapshot(); - }); -}); diff --git a/src/components/widgets/Date/DateSearch/DateSearch.tsx b/src/components/widgets/Date/DateSearch/DateSearch.tsx deleted file mode 100644 index 209da17f..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// @TODO: Review this component -// @ts-nocheck -/* eslint-disable */ - -import { DatePicker } from "antd"; -import { DateSearchProps } from "./DateSearch.types"; -// import { Dayjs } from "dayjs"; -import { - convertMomentDateArrayToStringArray, - getMomentValue, -} from "./DateSearch.helper"; - -const defaultDateFormat = "DD/MM/YYYY"; - -export const DateSearch = (props: DateSearchProps) => { - const { value, onChange } = props; - - const momentValue = getMomentValue(value!); - - return ( - { - onChange?.( - convertMomentDateArrayToStringArray(momentValues as any) as [ - string, - string, - ], - ); - }} - > - ); -}; diff --git a/src/components/widgets/Date/DateSearch/DateSearch.types.tsx b/src/components/widgets/Date/DateSearch/DateSearch.types.tsx deleted file mode 100644 index cedbfc83..00000000 --- a/src/components/widgets/Date/DateSearch/DateSearch.types.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ValueOnChange } from "@/components"; - -export type DateSearchProps = ValueOnChange<[string, string]>; - -export const defaultDateFormat = "DD/MM/YYYY"; diff --git a/src/components/widgets/Date/DateSearch/__snapshots__/DateSearch.test.tsx.snap b/src/components/widgets/Date/DateSearch/__snapshots__/DateSearch.test.tsx.snap deleted file mode 100644 index ff0d2cae..00000000 --- a/src/components/widgets/Date/DateSearch/__snapshots__/DateSearch.test.tsx.snap +++ /dev/null @@ -1,202 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DateSearch Renders Basic story 1`] = ` - -
-
-
- -
-
- - - - - -
-
- -
-
- - - - - -
-
- -`; - -exports[`DateSearch Renders BasicWithValues story 1`] = ` - -
-
-
- -
-
- - - - - -
-
- -
-
- - - - - - - - - - -
-
- -`; diff --git a/src/components/widgets/Date/DateSearch/index.ts b/src/components/widgets/Date/DateSearch/index.ts deleted file mode 100644 index 88dd36d1..00000000 --- a/src/components/widgets/Date/DateSearch/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DateSearch'; \ No newline at end of file diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.test.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.test.tsx deleted file mode 100644 index 89fba58e..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.test.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import dayjs from "dayjs"; -import { - getMomentDateValue, - getMomentTimeValue, - convertMomentDateArrayToStringArray, - convertMomentTimeArrayToStringArray, -} from "./DateTimeSearch.helper"; -import { defaultDateFormat } from "../DateSearch/DateSearch.types"; -import { - defaultTimeFormat, - defaultDateForTimeValue, -} from "./DateTimeSearch.types"; - -describe("DateTimeSearch.helper", () => { - describe("in getMomentDateValue method", () => { - it("should return array of null if value is undefined", () => { - const result = getMomentDateValue(undefined); - expect(result[0]).toBe(null); - expect(result[1]).toBe(null); - }); - it("should return from value and to value as null if we only pass from", () => { - const result = getMomentDateValue([ - ["01/01/2020", null], - [null, null], - ]); - const momentExpected = dayjs("01/01/2020", defaultDateFormat); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(momentExpected.toISOString()); - expect(result[1]).toBe(null); - }); - it("should return to value and from value as null if we only pass to", () => { - const result = getMomentDateValue([ - [null, "01/01/2020"], - [null, null], - ]); - const momentExpected = dayjs("01/01/2020", defaultDateFormat); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(momentExpected.toISOString()); - expect(result[0]).toBe(null); - }); - it("should return to and from value if we pass both", () => { - const result = getMomentDateValue([ - ["01/01/2020", "01/12/2020"], - [null, null], - ]); - const momentExpected1 = dayjs("01/01/2020", defaultDateFormat); - const momentExpected2 = dayjs("01/12/2020", defaultDateFormat); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(momentExpected1.toISOString()); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(momentExpected2.toISOString()); - }); - it("should return null array if we pass both null", () => { - const result = getMomentDateValue([ - [null, null], - [null, null], - ]); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeNull(); - expect(result[1]).toBeNull(); - }); - }); - describe("in getMomentTimeValue method", () => { - it("should return array of null if value is undefined", () => { - const result = getMomentTimeValue(undefined); - expect(result[0]).toBe(null); - expect(result[1]).toBe(null); - }); - it("should return from value and to value as null if we only pass from", () => { - const result = getMomentTimeValue([ - [null, null], - ["00:00", null], - ]); - const momentExpected = dayjs( - defaultDateForTimeValue + " " + "00:00", - defaultDateFormat + " " + defaultTimeFormat - ); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(momentExpected.toISOString()); - expect(result[1]).toBe(null); - }); - it("should return to value and from value as null if we only pass to", () => { - const result = getMomentTimeValue([ - [null, null], - [null, "03:15"], - ]); - const momentExpected = dayjs( - defaultDateForTimeValue + " " + "03:15", - defaultDateFormat + " " + defaultTimeFormat - ); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(momentExpected.toISOString()); - expect(result[0]).toBe(null); - }); - it("should return to and from value if we pass both", () => { - const result = getMomentTimeValue([ - [null, null], - ["01:39", "03:15"], - ]); - const momentExpected1 = dayjs( - defaultDateForTimeValue + " " + "01:39", - defaultDateFormat + " " + defaultTimeFormat - ); - - const momentExpected2 = dayjs( - defaultDateForTimeValue + " " + "03:15", - defaultDateFormat + " " + defaultTimeFormat - ); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeDefined(); - expect(result[0].toISOString()).toBe(momentExpected1.toISOString()); - expect(result[1]).toBeDefined(); - expect(result[1].toISOString()).toBe(momentExpected2.toISOString()); - }); - it("should return null array if we pass both null", () => { - const result = getMomentTimeValue([ - [null, null], - [null, null], - ]); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeNull(); - expect(result[1]).toBeNull(); - }); - }); - describe("in convertMomentDateArrayToStringArray method", () => { - it("should return array of nulls if value is undefined", () => { - const result = convertMomentDateArrayToStringArray(undefined); - expect(result[0]).toBe(null); - expect(result[1]).toBe(null); - }); - it("should return from value and to value as null if we only pass from", () => { - const fromDate = "01/04/2018"; - const fromDateMoment = dayjs(fromDate, defaultDateFormat); - - const result = convertMomentDateArrayToStringArray([ - fromDateMoment, - null, - ]); - expect(result[0]).toBe(fromDate); - expect(result[1]).toBe(null); - }); - it("should return to value and from value as null if we only pass to", () => { - const toDate = "01/04/2018"; - const toDateMoment = dayjs(toDate, defaultDateFormat); - - const result = convertMomentDateArrayToStringArray([null, toDateMoment]); - expect(result[1]).toBe(toDate); - expect(result[0]).toBe(null); - }); - - it("should return array of both formatted values if we pass two valid moments", () => { - const fromDate = "01/04/2018"; - const toDate = "23/06/2021"; - const fromDateMoment = dayjs(fromDate, defaultDateFormat); - const toDateMoment = dayjs(toDate, defaultDateFormat); - - const result = convertMomentDateArrayToStringArray([ - fromDateMoment, - toDateMoment, - ]); - expect(result[0]).toBe(fromDate); - expect(result[1]).toBe(toDate); - }); - it("should return null array if we pass both null", () => { - const result = convertMomentDateArrayToStringArray([null, null]); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeNull(); - expect(result[1]).toBeNull(); - }); - }); - describe("in convertMomentTimeArrayToStringArray method", () => { - it("should return array of nulls if value is undefined", () => { - const result = convertMomentTimeArrayToStringArray(undefined); - expect(result[0]).toBe(null); - expect(result[1]).toBe(null); - }); - it("should return from value and to value as null if we only pass from", () => { - const time = "10:01"; - const fromDate = defaultDateForTimeValue + " " + time; - const fromDateMoment = dayjs( - fromDate, - `${defaultDateFormat} ${defaultTimeFormat}` - ); - - const result = convertMomentTimeArrayToStringArray([ - fromDateMoment, - null, - ]); - expect(result[0]).toBe(time); - expect(result[1]).toBe(null); - }); - it("should return to value and from value as null if we only pass to", () => { - const time = "10:01"; - const fromDate = defaultDateForTimeValue + " " + time; - const toDateMoment = dayjs( - fromDate, - `${defaultDateFormat} ${defaultTimeFormat}` - ); - - const result = convertMomentTimeArrayToStringArray([null, toDateMoment]); - expect(result[1]).toBe(time); - expect(result[0]).toBe(null); - }); - - it("should return array of both formatted values if we pass two valid moments", () => { - const timeFrom = "10:01"; - const timeTo = "23:59"; - const fromDate = defaultDateForTimeValue + " " + timeFrom; - const toDate = defaultDateForTimeValue + " " + timeTo; - const fromDateMoment = dayjs( - fromDate, - `${defaultDateFormat} ${defaultTimeFormat}` - ); - const toDateMoment = dayjs( - toDate, - `${defaultDateFormat} ${defaultTimeFormat}` - ); - - const result = convertMomentTimeArrayToStringArray([ - fromDateMoment, - toDateMoment, - ]); - expect(result[0]).toBe(timeFrom); - expect(result[1]).toBe(timeTo); - }); - it("should return null array if we pass both null", () => { - const result = convertMomentTimeArrayToStringArray([null, null]); - expect(Array.isArray(result)).toBeTruthy(); - expect(result.length).toBe(2); - expect(result[0]).toBeNull(); - expect(result[1]).toBeNull(); - }); - }); -}); diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.tsx deleted file mode 100644 index c7e8a179..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.helper.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import dayjs from "dayjs"; -import { defaultDateFormat } from "../DateSearch/DateSearch.types"; -import { - defaultTimeFormat, - defaultDateForTimeValue, -} from "./DateTimeSearch.types"; - -export const getMomentDateValue = ( - value: [[string, string], [string, string]] -) => { - return value && value[0] - ? [ - value[0][0] ? dayjs(value[0][0], defaultDateFormat) : null, - value[0][1] ? dayjs(value[0][1], defaultDateFormat) : null, - ] - : [null, null]; -}; - -export const getMomentTimeValue = ( - value: [[string, string], [string, string]] -) => { - return value && value[1] - ? [ - value[1][0] - ? dayjs( - defaultDateForTimeValue + " " + value[1][0], - `${defaultDateFormat} ${defaultTimeFormat}` - ) - : null, - value[1][1] - ? dayjs( - defaultDateForTimeValue + " " + value[1][1], - `${defaultDateFormat} ${defaultTimeFormat}` - ) - : null, - ] - : [null, null]; -}; - -export const convertMomentDateArrayToStringArray = (momentValues: any[]) => { - const fromDate = - momentValues && momentValues[0] - ? momentValues[0].format(defaultDateFormat) - : null; - const toDate = - momentValues && momentValues[1] - ? momentValues[1].format(defaultDateFormat) - : null; - - return [fromDate, toDate]; -}; - -export const convertMomentTimeArrayToStringArray = (momentValues: any[]) => { - const fromTime = - momentValues && momentValues[0] - ? momentValues[0].format(defaultTimeFormat) - : null; - const toTime = - momentValues && momentValues[1] - ? momentValues[1].format(defaultTimeFormat) - : null; - - return [fromTime, toTime]; -}; diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.stories.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.stories.tsx deleted file mode 100644 index 6445a1f5..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { ComponentStory, ComponentMeta } from "@storybook/react"; - -import { DateTimeSearch } from "."; - -const category = "Date specific"; - -export default { - title: "Work in progress/Widgets/Date/DateTimeSearch", - component: DateTimeSearch, - argTypes: { - onChange: { - action: "onChange", - table: { - category, - }, - }, - value: { - table: { - category, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => { - const [value, setValue] = useState(args.value); - - useEffect(() => { - setValue(args.value); - }, [args.value]); - - return ( - { - setValue(value); - args.onChange?.(value); - }} - /> - ); -}; - -export const Basic = Template.bind({}); -Basic.args = {}; - -// export const BasicWithValues = Template.bind({}); -// BasicWithValues.args = { value: ["2020-01-01", "2020-12-01"] }; diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.test.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.test.tsx deleted file mode 100644 index aab38e44..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; -import { composeStories } from "@storybook/testing-react"; -import * as stories from "./DateTimeSearch.stories"; -import matchMediaMock from "@/mocks/matchMedia.mock"; - -describe("DateTimeSearch", () => { - beforeAll(() => { - matchMediaMock(); - }); - - const testCases = Object.values(composeStories(stories)).map((Story) => [ - Story.storyName!, - Story, - ]); - - test.each(testCases)("Renders %s story", async (_storyName, Story) => { - const tree = render(); - expect(tree.baseElement).toMatchSnapshot(); - }); -}); diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.tsx deleted file mode 100644 index 3f6a40da..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// @TODO: Review this component -// @ts-nocheck -/* eslint-disable */ -import React, { useRef } from "react"; -import { Col, DatePicker, Row, TimePicker } from "antd"; -import { Dayjs } from "dayjs"; -import { defaultDateFormat } from "../DateSearch/DateSearch.types"; -import { DateTimeSearchProps, defaultTimeFormat } from "./DateTimeSearch.types"; -import { - convertMomentDateArrayToStringArray, - convertMomentTimeArrayToStringArray, - getMomentDateValue, - getMomentTimeValue, -} from "./DateTimeSearch.helper"; - -export const DateTimeSearch = (props: DateTimeSearchProps) => { - const { value, onChange } = props; - - const dateRef = useRef<[string, string]>([null, null]); - const timeRef = useRef<[string, string]>([null, null]); - - const momentDateValue = getMomentDateValue(value); - const momentTimeValue = getMomentTimeValue(value); - - return ( - - - { - dateRef.current = convertMomentDateArrayToStringArray( - momentValues, - ) as [string, string]; - onChange([dateRef.current, timeRef.current]); - }} - > - - - { - timeRef.current = convertMomentTimeArrayToStringArray( - momentValues, - ) as [string, string]; - onChange([dateRef.current, timeRef.current]); - }} - > - - - ); -}; diff --git a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.types.tsx b/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.types.tsx deleted file mode 100644 index 56df7e2c..00000000 --- a/src/components/widgets/Date/DateTimeSearch/DateTimeSearch.types.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ValueOnChange } from "@/components"; - -export type DateTimeSearchProps = ValueOnChange< - [[string, string], [string, string]] ->; - -export const defaultTimeFormat = "HH:mm"; - -export const defaultDateForTimeValue = "1970/01/01"; diff --git a/src/components/widgets/Date/DateTimeSearch/__snapshots__/DateTimeSearch.test.tsx.snap b/src/components/widgets/Date/DateTimeSearch/__snapshots__/DateTimeSearch.test.tsx.snap deleted file mode 100644 index b08559bf..00000000 --- a/src/components/widgets/Date/DateTimeSearch/__snapshots__/DateTimeSearch.test.tsx.snap +++ /dev/null @@ -1,189 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DateTimeSearch Renders Basic story 1`] = ` - -
-
-
-
-
- -
-
- - - - - -
-
- -
-
- - - - - -
-
-
-
-
- -
-
- - - - - -
-
- -
-
- - - - - -
-
-
-
- -`; diff --git a/src/components/widgets/Date/DateTimeSearch/index.ts b/src/components/widgets/Date/DateTimeSearch/index.ts deleted file mode 100644 index f5125bc6..00000000 --- a/src/components/widgets/Date/DateTimeSearch/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DateTimeSearch'; \ No newline at end of file diff --git a/src/components/widgets/Date/DateTimeValue/DateTimeValue.tsx b/src/components/widgets/Date/DateTimeValue/DateTimeValue.tsx new file mode 100644 index 00000000..f3c5a027 --- /dev/null +++ b/src/components/widgets/Date/DateTimeValue/DateTimeValue.tsx @@ -0,0 +1,23 @@ +import { ReactElement, useMemo } from "react"; +import { DatePickerConfig } from "../DateInput/helpers/DatePicker.helpers"; +import dayjs from "@/helpers/dayjs"; + +export const DateTimeValue = ({ + value, + timezone = "Europe/Madrid", +}: { + value: any; + timezone?: string; +}): ReactElement => { + return useMemo(() => { + if (!value || (value && value.length === 0)) return <>; + const formattedValue = dayjs + .tz( + value, + DatePickerConfig.time.dateInternalFormat, + timezone || "Europe/Madrid", + ) + .format(DatePickerConfig.time.dateDisplayFormat); + return <>{formattedValue}; + }, [value, timezone]); +}; diff --git a/src/components/widgets/Date/DateTimeValue/index.ts b/src/components/widgets/Date/DateTimeValue/index.ts new file mode 100644 index 00000000..a54ce390 --- /dev/null +++ b/src/components/widgets/Date/DateTimeValue/index.ts @@ -0,0 +1 @@ +export { DateTimeValue } from "./DateTimeValue"; diff --git a/src/components/widgets/Date/DateValue/DateValue.tsx b/src/components/widgets/Date/DateValue/DateValue.tsx new file mode 100644 index 00000000..73ffbb0a --- /dev/null +++ b/src/components/widgets/Date/DateValue/DateValue.tsx @@ -0,0 +1,15 @@ +import { ReactElement, useMemo } from "react"; +import { DatePickerConfig } from "../DateInput/helpers/DatePicker.helpers"; +import dayjs from "@/helpers/dayjs"; + +export const DateValue = ({ value }: { value: any }): ReactElement => { + return useMemo(() => { + if (!value || (value && value.length === 0)) return <>; + + const formattedValue = dayjs( + value, + DatePickerConfig.date.dateInternalFormat, + ).format(DatePickerConfig.date.dateDisplayFormat); + return <>{formattedValue}; + }, [value]); +}; diff --git a/src/components/widgets/Date/DateValue/index.ts b/src/components/widgets/Date/DateValue/index.ts new file mode 100644 index 00000000..fb3e128e --- /dev/null +++ b/src/components/widgets/Date/DateValue/index.ts @@ -0,0 +1 @@ +export { DateValue } from "./DateValue"; diff --git a/src/components/widgets/Date/index.ts b/src/components/widgets/Date/index.ts index 200f7e2e..9c694641 100644 --- a/src/components/widgets/Date/index.ts +++ b/src/components/widgets/Date/index.ts @@ -1,3 +1,3 @@ export * from "./DateInput"; -export * from "./DateSearch"; -export * from "./DateTimeSearch"; +export * from "./DateValue"; +export * from "./DateTimeValue"; diff --git a/src/helpers/dayjs.ts b/src/helpers/dayjs.ts new file mode 100644 index 00000000..d9baa4d2 --- /dev/null +++ b/src/helpers/dayjs.ts @@ -0,0 +1,26 @@ +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc"; + +import advancedFormat from "dayjs/plugin/advancedFormat"; +import customParseFormat from "dayjs/plugin/customParseFormat"; +import isoWeek from "dayjs/plugin/isoWeek"; +import timezone from "dayjs/plugin/timezone"; +import duration from "dayjs/plugin/duration"; +import relativeTime from "dayjs/plugin/relativeTime"; +import weekday from "dayjs/plugin/weekday"; +import localeData from "dayjs/plugin/localeData"; +import "dayjs/locale/es"; +import "dayjs/locale/en"; +import "dayjs/locale/ca"; + +dayjs.extend(utc); +dayjs.extend(advancedFormat); +dayjs.extend(customParseFormat); +dayjs.extend(isoWeek); +dayjs.extend(timezone); +dayjs.extend(duration); +dayjs.extend(relativeTime); +dayjs.extend(weekday); +dayjs.extend(localeData); + +export default dayjs; diff --git a/src/hooks/useRequiredStyle.ts b/src/hooks/useRequiredStyle.ts new file mode 100644 index 00000000..764eb3c8 --- /dev/null +++ b/src/hooks/useRequiredStyle.ts @@ -0,0 +1,16 @@ +import { theme } from "antd"; +import { useMemo } from "react"; + +const { useToken } = theme; + +export const useRequiredStyle = (required: boolean, readOnly: boolean) => { + const { token } = useToken(); + + return useMemo( + () => + required && !readOnly + ? { backgroundColor: token.colorPrimaryBg } + : undefined, + [required, readOnly, token.colorPrimaryBg], + ); +};