From 1a462711e11858c58f1e562657781ae6fb4100db Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:40:20 +0530 Subject: [PATCH] [WEB-761] fix: Time zone date misalignment (#3986) * fix date time misalignment * fix failing build * fix gantt chart view position fix * comments for getDate method * remove new Date() where not required * changes from my self review --- .../src/ui/components/editor-header.tsx | 2 +- .../src/ui/components/info-popover.tsx | 4 +- .../core/filters/date-filter-modal.tsx | 65 +++++++++++-------- .../core/sidebar/progress-chart.tsx | 8 +-- .../cycles/active-cycle-details.tsx | 17 +++-- web/components/cycles/cycles-board-card.tsx | 6 +- web/components/cycles/cycles-list-item.tsx | 6 +- web/components/cycles/form.tsx | 6 +- .../cycles/gantt-chart/cycles-list-layout.tsx | 5 +- web/components/cycles/sidebar.tsx | 13 ++-- .../widgets/issue-panels/issue-list-item.tsx | 14 ++-- web/components/dropdowns/date.tsx | 6 +- web/components/exporter/single-export.tsx | 5 +- web/components/gantt-chart/chart/root.tsx | 4 +- web/components/gantt-chart/types/index.ts | 4 +- .../gantt-chart/views/month-view.ts | 10 +-- web/components/inbox/inbox-issue-actions.tsx | 28 ++++---- web/components/inbox/inbox-issue-status.tsx | 4 +- .../issues/issue-detail/inbox/sidebar.tsx | 4 +- .../issues/issue-detail/sidebar.tsx | 7 +- .../calendar/dropdowns/months-dropdown.tsx | 8 ++- .../properties/all-properties.tsx | 10 +-- .../spreadsheet/columns/due-date-column.tsx | 6 +- .../spreadsheet/columns/start-date-column.tsx | 4 +- web/components/issues/issue-modal/form.tsx | 8 +-- .../issues/peek-overview/properties.tsx | 6 +- web/components/modules/form.tsx | 6 +- .../gantt-chart/modules-list-layout.tsx | 5 +- web/components/modules/module-card-item.tsx | 6 +- web/components/modules/module-list-item.tsx | 6 +- web/components/modules/sidebar.tsx | 52 ++++++++------- .../notifications/notification-card.tsx | 2 +- .../select-snooze-till-modal.tsx | 4 +- web/constants/inbox.tsx | 7 +- web/contexts/user-notification-context.tsx | 4 +- web/helpers/date-time.helper.ts | 62 +++++++++++++----- web/helpers/filter.helper.ts | 34 ++++++++++ web/helpers/issue.helper.ts | 9 ++- .../projects/[projectId]/pages/[pageId].tsx | 3 +- web/store/cycle.store.ts | 15 +++-- web/store/project-page.store.ts | 20 +++--- 41 files changed, 303 insertions(+), 192 deletions(-) diff --git a/packages/editor/document-editor/src/ui/components/editor-header.tsx b/packages/editor/document-editor/src/ui/components/editor-header.tsx index a322ddddc..aaa4c7be3 100644 --- a/packages/editor/document-editor/src/ui/components/editor-header.tsx +++ b/packages/editor/document-editor/src/ui/components/editor-header.tsx @@ -72,7 +72,7 @@ export const EditorHeader = (props: IEditorHeader) => { Icon={Archive} backgroundColor="bg-blue-500/20" textColor="text-blue-500" - label={`Archived at ${new Date(archivedAt).toLocaleString()}`} + label={`Archived at ${archivedAt.toLocaleString()}`} /> )} diff --git a/packages/editor/document-editor/src/ui/components/info-popover.tsx b/packages/editor/document-editor/src/ui/components/info-popover.tsx index f78dd3473..9a17a9376 100644 --- a/packages/editor/document-editor/src/ui/components/info-popover.tsx +++ b/packages/editor/document-editor/src/ui/components/info-popover.tsx @@ -52,14 +52,14 @@ export const InfoPopover: React.FC = (props) => {
Last updated on
- {renderDate(new Date(documentDetails.last_updated_at))} + {renderDate(documentDetails.last_updated_at)}
Created on
- {renderDate(new Date(documentDetails.created_on))} + {renderDate(documentDetails.created_on)}
diff --git a/web/components/core/filters/date-filter-modal.tsx b/web/components/core/filters/date-filter-modal.tsx index c5238ec1c..3e2f3ff6a 100644 --- a/web/components/core/filters/date-filter-modal.tsx +++ b/web/components/core/filters/date-filter-modal.tsx @@ -8,7 +8,7 @@ import { DateFilterSelect } from "./date-filter-select"; // ui import { Button } from "@plane/ui"; // helpers -import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper"; +import { renderFormattedPayloadDate, renderFormattedDate, getDate } from "helpers/date-time.helper"; type Props = { title: string; @@ -43,7 +43,10 @@ export const DateFilterModal: React.FC = ({ title, handleClose, isOpen, o handleClose(); }; - const isInvalid = watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false; + const date1 = getDate(watch("date1")); + const date2 = getDate(watch("date1")); + + const isInvalid = watch("filterType") === "range" && date1 && date2 ? date1 > date2 : false; return ( @@ -86,35 +89,45 @@ export const DateFilterModal: React.FC = ({ title, handleClose, isOpen, o ( - onChange(date)} - mode="single" - disabled={[ - { after: new Date(watch("date2")) } - ]} - className="border border-custom-border-200 p-3 rounded-md" - /> - )} + render={({ field: { value, onChange } }) => { + const dateValue = getDate(value); + const date2Value = getDate(watch("date2")); + return ( + { + if (!date) return; + onChange(date); + }} + mode="single" + disabled={date2Value ? [{ after: date2Value }] : undefined} + className="border border-custom-border-200 p-3 rounded-md" + /> + ); + }} /> {watch("filterType") === "range" && ( ( - onChange(date)} - mode="single" - disabled={[ - { before: new Date(watch("date1")) } - ]} - className="border border-custom-border-200 p-3 rounded-md" - /> - )} + render={({ field: { value, onChange } }) => { + const dateValue = getDate(value); + const date1Value = getDate(watch("date1")); + return ( + { + if (!date) return; + onChange(date); + }} + mode="single" + disabled={date1Value ? [{ before: date1Value }] : undefined} + className="border border-custom-border-200 p-3 rounded-md" + /> + ); + }} /> )} diff --git a/web/components/core/sidebar/progress-chart.tsx b/web/components/core/sidebar/progress-chart.tsx index ca21756fc..b0ff4ba73 100644 --- a/web/components/core/sidebar/progress-chart.tsx +++ b/web/components/core/sidebar/progress-chart.tsx @@ -3,7 +3,7 @@ import { eachDayOfInterval, isValid } from "date-fns"; // ui import { LineGraph } from "components/ui"; // helpers -import { renderFormattedDateWithoutYear } from "helpers/date-time.helper"; +import { getDate, renderFormattedDateWithoutYear } from "helpers/date-time.helper"; //types import { TCompletionChartDistribution } from "@plane/types"; @@ -47,11 +47,11 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota })); const generateXAxisTickValues = () => { - const start = new Date(startDate); - const end = new Date(endDate); + const start = getDate(startDate); + const end = getDate(endDate); let dates: Date[] = []; - if (isValid(start) && isValid(end)) { + if (start && end && isValid(start) && isValid(end)) { dates = eachDayOfInterval({ start, end }); } diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 425ce7df3..b5d6523ed 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -27,7 +27,12 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; // icons import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react"; // helpers -import { renderFormattedDate, findHowManyDaysLeft, renderFormattedDateWithoutYear } from "helpers/date-time.helper"; +import { + renderFormattedDate, + findHowManyDaysLeft, + renderFormattedDateWithoutYear, + getDate, +} from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle, TCycleGroups } from "@plane/types"; @@ -102,8 +107,10 @@ export const ActiveCycleDetails: React.FC = observer((props /> ); - const endDate = new Date(activeCycle.end_date ?? ""); - const startDate = new Date(activeCycle.start_date ?? ""); + const endDate = getDate(activeCycle.end_date); + const startDate = getDate(activeCycle.start_date); + const daysLeft = findHowManyDaysLeft(activeCycle.end_date) ?? 0; + const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups; const groupedIssues: any = { backlog: activeCycle.backlog_issues, @@ -113,8 +120,6 @@ export const ActiveCycleDetails: React.FC = observer((props cancelled: activeCycle.cancelled_issues, }; - const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups; - const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; @@ -151,8 +156,6 @@ export const ActiveCycleDetails: React.FC = observer((props color: group.color, })); - const daysLeft = findHowManyDaysLeft(activeCycle.end_date) ?? 0; - return (
diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index 7d6b1e000..2b3e4bdc8 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -12,7 +12,7 @@ import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } // icons import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers -import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper"; +import { findHowManyDaysLeft, getDate, renderFormattedDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // constants import { CYCLE_STATUS } from "constants/cycle"; @@ -50,8 +50,8 @@ export const CyclesBoardCard: FC = observer((props) => { const cycleStatus = cycleDetails.status.toLocaleLowerCase(); const isCompleted = cycleStatus === "completed"; - const endDate = new Date(cycleDetails.end_date ?? ""); - const startDate = new Date(cycleDetails.start_date ?? ""); + const endDate = getDate(cycleDetails.end_date); + const startDate = getDate(cycleDetails.start_date); const isDateValid = cycleDetails.start_date || cycleDetails.end_date; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 31958cd84..98cf7b1aa 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -12,7 +12,7 @@ import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarG // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers -import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper"; +import { findHowManyDaysLeft, getDate, renderFormattedDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // constants import { CYCLE_STATUS } from "constants/cycle"; @@ -137,8 +137,8 @@ export const CyclesListItem: FC = observer((props) => { // TODO: change this logic once backend fix the response const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; const isCompleted = cycleStatus === "completed"; - const endDate = new Date(cycleDetails.end_date ?? ""); - const startDate = new Date(cycleDetails.start_date ?? ""); + const endDate = getDate(cycleDetails.end_date); + const startDate = getDate(cycleDetails.start_date); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; diff --git a/web/components/cycles/form.tsx b/web/components/cycles/form.tsx index 799d80438..9941d551d 100644 --- a/web/components/cycles/form.tsx +++ b/web/components/cycles/form.tsx @@ -5,7 +5,7 @@ import { DateRangeDropdown, ProjectDropdown } from "components/dropdowns"; // ui import { Button, Input, TextArea } from "@plane/ui"; // helpers -import { renderFormattedPayloadDate } from "helpers/date-time.helper"; +import { getDate, renderFormattedPayloadDate } from "helpers/date-time.helper"; // types import { ICycle } from "@plane/types"; @@ -135,8 +135,8 @@ export const CycleForm: React.FC = (props) => { className="h-7" minDate={new Date()} value={{ - from: startDateValue ? new Date(startDateValue) : undefined, - to: endDateValue ? new Date(endDateValue) : undefined, + from: getDate(startDateValue), + to: getDate(endDateValue), }} onSelect={(val) => { onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null); diff --git a/web/components/cycles/gantt-chart/cycles-list-layout.tsx b/web/components/cycles/gantt-chart/cycles-list-layout.tsx index 646333aad..146774319 100644 --- a/web/components/cycles/gantt-chart/cycles-list-layout.tsx +++ b/web/components/cycles/gantt-chart/cycles-list-layout.tsx @@ -8,6 +8,7 @@ import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/ import { CycleGanttBlock } from "components/cycles"; // types import { ICycle } from "@plane/types"; +import { getDate } from "helpers/date-time.helper"; // constants import { EUserProjectRoles } from "constants/project"; @@ -45,8 +46,8 @@ export const CyclesListGanttChartView: FC = observer((props) => { data: block, id: block?.id ?? "", sort_order: block?.sort_order ?? 0, - start_date: new Date(block?.start_date ?? ""), - target_date: new Date(block?.end_date ?? ""), + start_date: getDate(block?.start_date), + target_date: getDate(block?.end_date), })); return structuredBlocks; diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 646736bd2..56f137f8d 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -18,8 +18,8 @@ import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui"; // icons import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, CalendarClock } from "lucide-react"; // helpers +import { findHowManyDaysLeft, getDate, renderFormattedPayloadDate } from "helpers/date-time.helper"; import { copyUrlToClipboard } from "helpers/string.helper"; -import { findHowManyDaysLeft, renderFormattedPayloadDate } from "helpers/date-time.helper"; // types import { ICycle } from "@plane/types"; // constants @@ -186,8 +186,11 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const cycleStatus = cycleDetails?.status.toLocaleLowerCase(); const isCompleted = cycleStatus === "completed"; - const isStartValid = new Date(`${cycleDetails?.start_date}`) <= new Date(); - const isEndValid = new Date(`${cycleDetails?.end_date}`) >= new Date(`${cycleDetails?.start_date}`); + const startDate = getDate(cycleDetails?.start_date); + const endDate = getDate(cycleDetails?.end_date); + + const isStartValid = startDate && startDate <= new Date(); + const isEndValid = endDate && startDate && endDate >= startDate; const progressPercentage = cycleDetails ? isCompleted && cycleDetails?.progress_snapshot @@ -317,8 +320,8 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { buttonVariant="background-with-text" minDate={new Date()} value={{ - from: startDateValue ? new Date(startDateValue) : undefined, - to: endDateValue ? new Date(endDateValue) : undefined, + from: getDate(startDateValue), + to: getDate(endDateValue), }} onSelect={(val) => { onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null); diff --git a/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx b/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx index 716a3afc1..b269423c6 100644 --- a/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx +++ b/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx @@ -5,7 +5,7 @@ import { useIssueDetail, useMember, useProject } from "hooks/store"; // ui import { Avatar, AvatarGroup, ControlLink, PriorityIcon } from "@plane/ui"; // helpers -import { findTotalDaysInRange, renderFormattedDate } from "helpers/date-time.helper"; +import { findTotalDaysInRange, getDate, renderFormattedDate } from "helpers/date-time.helper"; // types import { TIssue, TWidgetIssue } from "@plane/types"; @@ -34,6 +34,8 @@ export const AssignedUpcomingIssueListItem: React.FC = obser const blockedByIssueProjectDetails = blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null; + const targetDate = getDate(issueDetails.target_date); + return ( = obser
{issueDetails.name}
- {issueDetails.target_date - ? isToday(new Date(issueDetails.target_date)) - ? "Today" - : renderFormattedDate(issueDetails.target_date) - : "-"} + {targetDate ? (isToday(targetDate) ? "Today" : renderFormattedDate(targetDate)) : "-"}
{blockedByIssues.length > 0 @@ -83,7 +81,7 @@ export const AssignedOverdueIssueListItem: React.FC = observ const blockedByIssueProjectDetails = blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null; - const dueBy = findTotalDaysInRange(new Date(issueDetails.target_date ?? ""), new Date(), false) ?? 0; + const dueBy = findTotalDaysInRange(getDate(issueDetails.target_date), new Date(), false) ?? 0; return ( = observe const projectDetails = getProjectById(issue.project_id); - const dueBy = findTotalDaysInRange(new Date(issue.target_date ?? ""), new Date(), false) ?? 0; + const dueBy = findTotalDaysInRange(getDate(issue.target_date), new Date(), false) ?? 0; return ( = (props) => { {...attributes.popper} > { dropdownOnChange(date ?? null); }} diff --git a/web/components/exporter/single-export.tsx b/web/components/exporter/single-export.tsx index 34e41fc35..bd73824cf 100644 --- a/web/components/exporter/single-export.tsx +++ b/web/components/exporter/single-export.tsx @@ -2,7 +2,7 @@ import { useState, FC } from "react"; // ui import { Button } from "@plane/ui"; // helpers -import { renderFormattedDate } from "helpers/date-time.helper"; +import { getDate, renderFormattedDate } from "helpers/date-time.helper"; // types import { IExportData } from "@plane/types"; @@ -18,7 +18,8 @@ export const SingleExport: FC = ({ service, refreshing }) => { const checkExpiry = (inputDateString: string) => { const currentDate = new Date(); - const expiryDate = new Date(inputDateString); + const expiryDate = getDate(inputDateString); + if (!expiryDate) return false; expiryDate.setDate(expiryDate.getDate() + 7); return expiryDate > currentDate; }; diff --git a/web/components/gantt-chart/chart/root.tsx b/web/components/gantt-chart/chart/root.tsx index be6229ce3..dada139a8 100644 --- a/web/components/gantt-chart/chart/root.tsx +++ b/web/components/gantt-chart/chart/root.tsx @@ -64,9 +64,9 @@ export const ChartViewRoot: FC = observer((props) => { useGanttChart(); // rendering the block structure - const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) => + const renderBlockStructure = (view: ChartDataType, blocks: IGanttBlock[] | null) => blocks - ? blocks.map((block: any) => ({ + ? blocks.map((block: IGanttBlock) => ({ ...block, position: getMonthChartItemPositionWidthInMonth(view, block), })) diff --git a/web/components/gantt-chart/types/index.ts b/web/components/gantt-chart/types/index.ts index 6268e4363..cd90758fc 100644 --- a/web/components/gantt-chart/types/index.ts +++ b/web/components/gantt-chart/types/index.ts @@ -6,8 +6,8 @@ export interface IGanttBlock { width: number; }; sort_order: number; - start_date: Date | null; - target_date: Date | null; + start_date: Date | undefined; + target_date: Date | undefined; } export interface IBlockUpdateData { diff --git a/web/components/gantt-chart/views/month-view.ts b/web/components/gantt-chart/views/month-view.ts index 13d054da1..8978dc317 100644 --- a/web/components/gantt-chart/views/month-view.ts +++ b/web/components/gantt-chart/views/month-view.ts @@ -1,4 +1,5 @@ // types +import { findTotalDaysInRange } from "helpers/date-time.helper"; import { ChartDataType, IGanttBlock } from "../types"; // data import { weeks, months } from "../data"; @@ -167,15 +168,16 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType, const { startDate } = chartData.data; const { start_date: itemStartDate, target_date: itemTargetDate } = itemData; - if (!itemStartDate || !itemTargetDate) return null; + if (!itemStartDate || !itemTargetDate) return; startDate.setHours(0, 0, 0, 0); itemStartDate.setHours(0, 0, 0, 0); itemTargetDate.setHours(0, 0, 0, 0); - // position code starts - const positionTimeDifference: number = startDate.getTime() - itemStartDate.getTime(); - const positionDaysDifference: number = Math.abs(Math.floor(positionTimeDifference / (1000 * 60 * 60 * 24))); + const positionDaysDifference = findTotalDaysInRange(startDate, itemStartDate, false); + + if (!positionDaysDifference) return; + scrollPosition = positionDaysDifference * chartData.data.width; var diffMonths = (itemStartDate.getFullYear() - startDate.getFullYear()) * 12; diff --git a/web/components/inbox/inbox-issue-actions.tsx b/web/components/inbox/inbox-issue-actions.tsx index 48d9157c6..aba20ba3c 100644 --- a/web/components/inbox/inbox-issue-actions.tsx +++ b/web/components/inbox/inbox-issue-actions.tsx @@ -21,6 +21,8 @@ import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Trash2, XCircle import type { TInboxStatus, TInboxDetailedStatus } from "@plane/types"; import { EUserProjectRoles } from "constants/project"; import { ISSUE_DELETED } from "constants/event-tracker"; +//helpers +import { getDate } from "helpers/date-time.helper"; type TInboxIssueActionsHeader = { workspaceSlug: string; @@ -171,11 +173,11 @@ export const InboxIssueActionsHeader: FC = observer((p const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const today = new Date(); - const tomorrow = new Date(today); - tomorrow.setDate(today.getDate() + 1); + const tomorrow = getDate(today); + tomorrow?.setDate(today.getDate() + 1); useEffect(() => { if (!issueStatus || !issueStatus.snoozed_till) return; - setDate(new Date(issueStatus.snoozed_till)); + setDate(issueStatus.snoozed_till); }, [issueStatus]); if (!issueStatus || !issue || !inboxIssues) return <>; @@ -269,19 +271,23 @@ export const InboxIssueActionsHeader: FC = observer((p {({ close }) => (
{ if (!date) return; setDate(date); }} mode="single" className="border border-custom-border-200 rounded-md p-3" - disabled={[ - { - before: tomorrow, - }, - ]} + disabled={ + tomorrow + ? [ + { + before: tomorrow, + }, + ] + : undefined + } />