From 66cfb3d8f2b72b155f74704510fbee97c5dc5a26 Mon Sep 17 00:00:00 2001 From: vamsikrishnamathala Date: Fri, 6 Jun 2025 17:19:39 +0530 Subject: [PATCH 1/2] fix: timezone conversion for cycles date --- .../cycles/list/cycle-list-item-action.tsx | 8 +++-- web/helpers/date-time.helper.ts | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index c90ff653ea3..eeebc9a006e 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -23,7 +23,7 @@ import { Avatar, AvatarGroup, FavoriteStar, LayersIcon, Tooltip, TransferIcon, s import { CycleQuickActions, TransferIssuesModal } from "@/components/cycles"; import { DateRangeDropdown } from "@/components/dropdowns"; import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; -import { getDate } from "@/helpers/date-time.helper"; +import { getDate, renderSafeFormattedDate } from "@/helpers/date-time.helper"; import { getFileURL } from "@/helpers/file.helper"; // hooks import { generateQueryParams } from "@/helpers/router.helper"; @@ -230,9 +230,11 @@ export const CycleListItemAction: FC = observer((props) => { >
- {cycleDetails.start_date && {format(parseISO(cycleDetails.start_date), "MMM dd, yyyy")}} + {cycleDetails.start_date && ( + {renderSafeFormattedDate(cycleDetails.start_date, "MMM dd, yyyy")} + )} - {cycleDetails.end_date && {format(parseISO(cycleDetails.end_date), "MMM dd, yyyy")}} + {cycleDetails.end_date && {renderSafeFormattedDate(cycleDetails.end_date, "MMM dd, yyyy")}}
{projectUTCOffset && ( diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index cff0da2c2df..73ed48bff4e 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -475,3 +475,38 @@ export const checkDateCriteria = (dateToCheck: Date | null, filterDate: Date, ty return type === "after" ? normalizedCheck >= normalizedFilter : normalizedCheck <= normalizedFilter; }; + +/** + * @returns {string} safely formatted date or fallback text + * @description Safely formats a date using renderFormattedPayloadDate and date-fns format, with fallback for invalid dates + * @param {Date | string | undefined | null} date + * @param {string} formatToken (optional) // default "MMM dd, yyyy" + * @param {string} fallback (optional) // default "Invalid date" + * @example renderSafeFormattedDate("2024-01-01") // "Jan 01, 2024" + * @example renderSafeFormattedDate(null) // "Invalid date" + * @example renderSafeFormattedDate("2024-01-01", "MM/dd/yyyy", "N/A") // "01/01/2024" + */ +export const renderSafeFormattedDate = ( + date: Date | string | undefined | null, + formatToken: string = "MMM dd, yyyy", + fallback: string = "Invalid date" +): string => { + if (!date) return fallback; + + // Use renderFormattedPayloadDate to get a properly formatted payload date + const payloadDate = renderFormattedPayloadDate(date); + + // If renderFormattedPayloadDate returns undefined/null, return fallback + if (!payloadDate) return fallback; + + try { + // Parse and format the payload date + const parsedDate = getDate(payloadDate); + if (!parsedDate || !isValid(parsedDate)) return fallback; + + return format(parsedDate, formatToken); + } catch (error) { + // Return fallback if any error occurs during formatting + return fallback; + } +}; From d7fff8e200cb6e6ea479224fc630b1678a9dc76c Mon Sep 17 00:00:00 2001 From: vamsikrishnamathala Date: Mon, 9 Jun 2025 12:55:12 +0530 Subject: [PATCH 2/2] chore: moved helper function to utils package --- packages/utils/src/datetime.ts | 53 +++++++++++++++++++ .../cycles/list/cycle-list-item-action.tsx | 9 ++-- web/helpers/date-time.helper.ts | 35 ------------ 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/packages/utils/src/datetime.ts b/packages/utils/src/datetime.ts index 0a12a227084..01dd5de01f5 100644 --- a/packages/utils/src/datetime.ts +++ b/packages/utils/src/datetime.ts @@ -333,3 +333,56 @@ export const generateDateArray = (startDate: string | Date, endDate: string | Da } return dateArray; }; + +/** + * @returns {string | null} formatted date in the format of yyyy-mm-dd to be used in payload + * @description Returns date in the formatted format to be used in payload + * @param {Date | string} date + * @example renderFormattedPayloadDate("Jan 01, 20224") // "2024-01-01" + */ +export const renderFormattedPayloadDate = (date: Date | string | undefined | null): string | undefined => { + // Parse the date to check if it is valid + const parsedDate = getDate(date); + // return if undefined + if (!parsedDate) return; + // Check if the parsed date is valid before formatting + if (!isValid(parsedDate)) return; // Return null for invalid dates + // Format the date in payload format (yyyy-mm-dd) + const formattedDate = format(parsedDate, "yyyy-MM-dd"); + return formattedDate; +}; + +/** + * @returns {string} safely formatted date or fallback text + * @description Safely formats a date using renderFormattedPayloadDate and date-fns format, with fallback for invalid dates + * @param {Date | string | undefined | null} date + * @param {string} formatToken (optional) // default "MMM dd, yyyy" + * @param {string} fallback (optional) // default "Invalid date" + * @example renderSafeFormattedDate("2024-01-01") // "Jan 01, 2024" + * @example renderSafeFormattedDate(null) // "Invalid date" + * @example renderSafeFormattedDate("2024-01-01", "MM/dd/yyyy", "N/A") // "01/01/2024" + */ +export const renderSafeFormattedDate = ( + date: Date | string | undefined | null, + formatToken: string = "MMM dd, yyyy", + fallback: string = "Invalid date" +): string => { + if (!date) return fallback; + + // Use renderFormattedPayloadDate to get a properly formatted payload date + const payloadDate = renderFormattedPayloadDate(date); + + // If renderFormattedPayloadDate returns undefined/null, return fallback + if (!payloadDate) return fallback; + + try { + // Parse and format the payload date + const parsedDate = getDate(payloadDate); + if (!parsedDate || !isValid(parsedDate)) return fallback; + + return format(parsedDate, formatToken); + } catch (error) { + // Return fallback if any error occurs during formatting + return fallback; + } +}; diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index eeebc9a006e..9e348026c79 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -1,7 +1,6 @@ "use client"; import React, { FC, MouseEvent, useEffect, useMemo, useState } from "react"; -import { format, parseISO } from "date-fns"; import { observer } from "mobx-react"; import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useForm } from "react-hook-form"; @@ -17,13 +16,13 @@ import { import { useLocalStorage } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; import { ICycle, TCycleGroups } from "@plane/types"; -// ui import { Avatar, AvatarGroup, FavoriteStar, LayersIcon, Tooltip, TransferIcon, setPromiseToast } from "@plane/ui"; +import { renderSafeFormattedDate } from "@plane/utils"; // components import { CycleQuickActions, TransferIssuesModal } from "@/components/cycles"; import { DateRangeDropdown } from "@/components/dropdowns"; import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; -import { getDate, renderSafeFormattedDate } from "@/helpers/date-time.helper"; +import { getDate } from "@/helpers/date-time.helper"; import { getFileURL } from "@/helpers/file.helper"; // hooks import { generateQueryParams } from "@/helpers/router.helper"; @@ -64,7 +63,7 @@ export const CycleListItemAction: FC = observer((props) => { const searchParams = useSearchParams(); const pathname = usePathname(); // store hooks - const { addCycleToFavorites, removeCycleFromFavorites, updateCycleDetails } = useCycle(); + const { addCycleToFavorites, removeCycleFromFavorites } = useCycle(); const { captureEvent } = useEventTracker(); const { allowPermissions } = useUserPermissions(); @@ -77,7 +76,7 @@ export const CycleListItemAction: FC = observer((props) => { const { getUserDetails } = useMember(); // form - const { control, reset, getValues } = useForm({ + const { reset } = useForm({ defaultValues, }); diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index 73ed48bff4e..cff0da2c2df 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -475,38 +475,3 @@ export const checkDateCriteria = (dateToCheck: Date | null, filterDate: Date, ty return type === "after" ? normalizedCheck >= normalizedFilter : normalizedCheck <= normalizedFilter; }; - -/** - * @returns {string} safely formatted date or fallback text - * @description Safely formats a date using renderFormattedPayloadDate and date-fns format, with fallback for invalid dates - * @param {Date | string | undefined | null} date - * @param {string} formatToken (optional) // default "MMM dd, yyyy" - * @param {string} fallback (optional) // default "Invalid date" - * @example renderSafeFormattedDate("2024-01-01") // "Jan 01, 2024" - * @example renderSafeFormattedDate(null) // "Invalid date" - * @example renderSafeFormattedDate("2024-01-01", "MM/dd/yyyy", "N/A") // "01/01/2024" - */ -export const renderSafeFormattedDate = ( - date: Date | string | undefined | null, - formatToken: string = "MMM dd, yyyy", - fallback: string = "Invalid date" -): string => { - if (!date) return fallback; - - // Use renderFormattedPayloadDate to get a properly formatted payload date - const payloadDate = renderFormattedPayloadDate(date); - - // If renderFormattedPayloadDate returns undefined/null, return fallback - if (!payloadDate) return fallback; - - try { - // Parse and format the payload date - const parsedDate = getDate(payloadDate); - if (!parsedDate || !isValid(parsedDate)) return fallback; - - return format(parsedDate, formatToken); - } catch (error) { - // Return fallback if any error occurs during formatting - return fallback; - } -};