chore: date and time standardization all across the platform. (#3283)

* chore: date and time standardization all across the platform.

* chore: update `renderFormattedTime` function.
* remove unwanted code.

* fix: build errors

* chore: update `renderFormattedTime` function params.
This commit is contained in:
Prateek Shourya 2024-01-02 14:45:51 +05:30 committed by GitHub
parent d9ee692ce9
commit 1539340113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 366 additions and 723 deletions

View File

@ -1,11 +1,10 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { renderShortDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// constants
import { NETWORK_CHOICES } from "constants/project";
@ -37,7 +36,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<h6 className="text-custom-text-200">Start Date</h6>
<span>
{cycleDetails.start_date && cycleDetails.start_date !== ""
? renderShortDate(cycleDetails.start_date)
? renderFormattedDate(cycleDetails.start_date)
: "No start date"}
</span>
</div>
@ -45,7 +44,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<h6 className="text-custom-text-200">Target Date</h6>
<span>
{cycleDetails.end_date && cycleDetails.end_date !== ""
? renderShortDate(cycleDetails.end_date)
? renderFormattedDate(cycleDetails.end_date)
: "No end date"}
</span>
</div>
@ -63,7 +62,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<h6 className="text-custom-text-200">Start Date</h6>
<span>
{moduleDetails.start_date && moduleDetails.start_date !== ""
? renderShortDate(moduleDetails.start_date)
? renderFormattedDate(moduleDetails.start_date)
: "No start date"}
</span>
</div>
@ -71,7 +70,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<h6 className="text-custom-text-200">Target Date</h6>
<span>
{moduleDetails.target_date && moduleDetails.target_date !== ""
? renderShortDate(moduleDetails.target_date)
? renderFormattedDate(moduleDetails.target_date)
: "No end date"}
</span>
</div>

View File

@ -14,7 +14,7 @@ import { Button, LayersIcon } from "@plane/ui";
// icons
import { CalendarDays, Download, RefreshCw } from "lucide-react";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "types";
// fetch-keys
@ -156,7 +156,7 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer(
{isProjectLevel && (
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<CalendarDays className="h-3.5 w-3.5" />
{renderShortDate(
{renderFormattedDate(
(cycleId
? cycleDetails?.created_at
: moduleId

View File

@ -48,7 +48,7 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
const csvData = {
Title: data.label,
Description: data.description,
Expiry: data.expired_at ? renderFormattedDate(data.expired_at) : "Never expires",
Expiry: data.expired_at ? renderFormattedDate(data.expired_at)?.replace(",", " ") ?? "" : "Never expires",
"Secret key": data.token ?? "",
};

View File

@ -5,7 +5,7 @@ import { DeleteApiTokenModal } from "components/api-token";
// ui
import { Tooltip } from "@plane/ui";
// helpers
import { renderFormattedDate, timeAgo } from "helpers/date-time.helper";
import { renderFormattedDate, calculateTimeAgo } from "helpers/date-time.helper";
// types
import { IApiToken } from "types/api_token";
@ -49,7 +49,7 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
? token.expired_at
? `Expires ${renderFormattedDate(token.expired_at!)}`
: "Never expires"
: `Expired ${timeAgo(token.expired_at)}`}
: `Expired ${calculateTimeAgo(token.expired_at)}`}
</p>
</div>
</div>

View File

@ -1,7 +1,5 @@
import React from "react";
import useSWR from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
@ -12,7 +10,7 @@ import { Loader } from "@plane/ui";
// icons
import { X } from "lucide-react";
// helpers
import { renderLongDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
type Props = {
isOpen: boolean;
@ -69,7 +67,7 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-background-90 px-3 py-1.5 text-xs">
{item.tag_name}
</span>
<span>{renderLongDateFormat(item.published_at)}</span>
<span>{renderFormattedDate(item.published_at)}</span>
{index === 0 && (
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-primary px-3 py-1.5 text-xs text-white">
New

View File

@ -22,7 +22,7 @@ import {
UsersIcon,
} from "lucide-react";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { IIssueActivity } from "types";
@ -597,7 +597,7 @@ const activityDetails: {
<>
set the start date to{" "}
<span className="font-medium text-custom-text-100">
{renderShortDateWithYearFormat(activity.new_value)}
{renderFormattedDate(activity.new_value)}
</span>
{showIssue && (
<>
@ -646,7 +646,7 @@ const activityDetails: {
<>
set the due date to{" "}
<span className="font-medium text-custom-text-100">
{renderShortDateWithYearFormat(activity.new_value)}
{renderFormattedDate(activity.new_value)}
</span>
{showIssue && (
<>

View File

@ -2,7 +2,6 @@ import { Fragment } from "react";
import { Controller, useForm } from "react-hook-form";
import DatePicker from "react-datepicker";
import { Dialog, Transition } from "@headlessui/react";
// components
import { DateFilterSelect } from "./date-filter-select";
// ui
@ -10,7 +9,7 @@ import { Button } from "@plane/ui";
// icons
import { X } from "lucide-react";
// helpers
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
type Props = {
title: string;
@ -39,8 +38,8 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
const handleFormSubmit = (formData: TFormValues) => {
const { filterType, date1, date2 } = formData;
if (filterType === "range") onSelect([`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`]);
else onSelect([`${renderDateFormat(date1)};${filterType}`]);
if (filterType === "range") onSelect([`${renderFormattedPayloadDate(date1)};after`, `${renderFormattedPayloadDate(date2)};before`]);
else onSelect([`${renderFormattedPayloadDate(date1)};${filterType}`]);
handleClose();
};
@ -121,9 +120,9 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
{watch("filterType") === "range" && (
<h6 className="flex items-center gap-1 text-xs">
<span className="text-custom-text-200">After:</span>
<span>{renderShortDateWithYearFormat(watch("date1"))}</span>
<span>{renderFormattedDate(watch("date1"))}</span>
<span className="ml-1 text-custom-text-200">Before:</span>
{!isInvalid && <span>{renderShortDateWithYearFormat(watch("date2"))}</span>}
{!isInvalid && <span>{renderFormattedDate(watch("date2"))}</span>}
</h6>
)}
<div className="flex justify-end gap-4">

View File

@ -3,7 +3,7 @@ import { ExternalLinkIcon, Tooltip } from "@plane/ui";
// icons
import { Pencil, Trash2, LinkIcon } from "lucide-react";
// helpers
import { timeAgo } from "helpers/date-time.helper";
import { calculateTimeAgo } from "helpers/date-time.helper";
// types
import { ILinkDetails, UserAuth } from "types";
// hooks
@ -89,7 +89,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
</div>
<div className="px-5">
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
Added {timeAgo(link.created_at)}
Added {calculateTimeAgo(link.created_at)}
<br />
by{" "}
{link.created_by_detail.is_bot

View File

@ -1,9 +1,9 @@
import React from "react";
import { eachDayOfInterval } from "date-fns";
// ui
import { LineGraph } from "components/ui";
// helpers
import { getDatesInRange, renderShortNumericDateFormat } from "helpers/date-time.helper";
import { renderFormattedDateWithoutYear } from "helpers/date-time.helper";
//types
import { TCompletionChartDistribution } from "types";
@ -42,25 +42,25 @@ const DashedLine = ({ series, lineGenerator, xScale, yScale }: any) =>
const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, totalIssues }) => {
const chartData = Object.keys(distribution).map((key) => ({
currentDate: renderShortNumericDateFormat(key),
currentDate: renderFormattedDateWithoutYear(key),
pending: distribution[key],
}));
const generateXAxisTickValues = () => {
const dates = getDatesInRange(startDate, endDate);
const dates = eachDayOfInterval({ start: new Date(startDate), end: new Date(endDate) });
const maxDates = 4;
const totalDates = dates.length;
if (totalDates <= maxDates) return dates.map((d) => renderShortNumericDateFormat(d));
if (totalDates <= maxDates) return dates.map((d) => renderFormattedDateWithoutYear(d));
else {
const interval = Math.ceil(totalDates / maxDates);
const limitedDates = [];
for (let i = 0; i < totalDates; i += interval) limitedDates.push(renderShortNumericDateFormat(dates[i]));
for (let i = 0; i < totalDates; i += interval) limitedDates.push(renderFormattedDateWithoutYear(dates[i]));
if (!limitedDates.includes(renderShortNumericDateFormat(dates[totalDates - 1])))
limitedDates.push(renderShortNumericDateFormat(dates[totalDates - 1]));
if (!limitedDates.includes(renderFormattedDateWithoutYear(dates[totalDates - 1])))
limitedDates.push(renderFormattedDateWithoutYear(dates[totalDates - 1]));
return limitedDates;
}

View File

@ -28,7 +28,7 @@ import { ViewIssueLabel } from "components/issues";
// icons
import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react";
// helpers
import { renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper";
import { truncateText } from "helpers/string.helper";
// types
import { ICycle } from "types";
@ -267,12 +267,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
<div className="flex items-center justify-start gap-5 text-custom-text-200">
<div className="flex items-start gap-1">
<CalendarDays className="h-4 w-4" />
<span>{renderShortDateWithYearFormat(startDate)}</span>
<span>{renderFormattedDate(startDate)}</span>
</div>
<ArrowRight className="h-4 w-4 text-custom-text-200" />
<div className="flex items-start gap-1">
<Target className="h-4 w-4" />
<span>{renderShortDateWithYearFormat(endDate)}</span>
<span>{renderFormattedDate(endDate)}</span>
</div>
</div>

View File

@ -10,7 +10,7 @@ import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon }
// icons
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// helpers
import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { ICycle, TCycleGroups } from "types";
@ -53,8 +53,6 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const cycleTotalIssues =
cycle.backlog_issues +
cycle.unstarted_issues +
@ -228,8 +226,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
<div className="flex items-center justify-between">
{isDateValid ? (
<span className="text-xs text-custom-text-300">
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
{renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"}
</span>
) : (
<span className="text-xs text-custom-text-400">No due date</span>

View File

@ -1,7 +1,6 @@
import { FC, MouseEvent, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
// stores
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
@ -13,7 +12,7 @@ import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarG
// icons
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// helpers
import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper";
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { ICycle, TCycleGroups } from "types";
@ -64,8 +63,6 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
const renderDate = cycle.start_date || cycle.end_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const completionPercentage = (cycle.completed_issues / cycleTotalIssues) * 100;
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
@ -204,10 +201,8 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
</div>
{renderDate && (
<span className="flex w-28 items-center justify-center gap-2 text-xs text-custom-text-300">
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")}
{" - "}
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
<span className="flex w-40 items-center justify-center gap-2 text-xs text-custom-text-300">
{renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"}
</span>
)}

View File

@ -1,9 +1,8 @@
import { useRouter } from "next/router";
// ui
import { Tooltip, ContrastIcon } from "@plane/ui";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { ICycle } from "types";
@ -35,7 +34,7 @@ export const CycleGanttBlock = ({ data }: { data: ICycle }) => {
<div className="space-y-1">
<h5>{data?.name}</h5>
<div>
{renderShortDate(data?.start_date ?? "")} to {renderShortDate(data?.end_date ?? "")}
{renderFormattedDate(data?.start_date ?? "")} to {renderFormattedDate(data?.end_date ?? "")}
</div>
</div>
}

View File

@ -32,9 +32,8 @@ import { copyUrlToClipboard } from "helpers/string.helper";
import {
findHowManyDaysLeft,
isDateGreaterThanToday,
renderDateFormat,
renderShortDate,
renderShortMonthDate,
renderFormattedPayloadDate,
renderFormattedDate,
} from "helpers/date-time.helper";
// types
import { ICycle, IIssueFilterOptions } from "types";
@ -141,8 +140,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
if (isDateValidForExistingCycle) {
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
end_date: renderDateFormat(`${watch("end_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
});
setToastAlert({
type: "success",
@ -168,8 +167,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
if (isDateValid) {
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
end_date: renderDateFormat(`${watch("end_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
});
setToastAlert({
type: "success",
@ -209,8 +208,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
if (isDateValidForExistingCycle) {
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
end_date: renderDateFormat(`${watch("end_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
});
setToastAlert({
type: "success",
@ -236,8 +235,8 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
if (isDateValid) {
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
end_date: renderDateFormat(`${watch("end_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
});
setToastAlert({
type: "success",
@ -302,9 +301,6 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const endDate = new Date(watch("end_date") ?? cycleDetails.end_date ?? "");
const startDate = new Date(watch("start_date") ?? cycleDetails.start_date ?? "");
const areYearsEqual =
startDate.getFullYear() === endDate.getFullYear() || isNaN(startDate.getFullYear()) || isNaN(endDate.getFullYear());
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
const issueCount =
@ -396,19 +392,17 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="relative flex w-1/2 items-center rounded-sm">
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
<Popover.Button
className={`text-sm font-medium text-custom-text-300 w-full rounded-sm cursor-pointer hover:bg-custom-background-80 ${
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={isCompleted || !isEditingAllowed}
>
<span
className={`group flex w-full items-center justify-between gap-2 py-1 px-1.5 text-sm ${
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
watch("start_date") ? "" : "text-custom-text-400"
}`}
>
{areYearsEqual
? renderShortDate(startDate, "No date selected")
: renderShortMonthDate(startDate, "No date selected")}
{renderFormattedDate(startDate) ?? "No date selected"}
</span>
</Popover.Button>
@ -450,19 +444,17 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
<>
<Popover.Button
className={`text-sm font-medium text-custom-text-300 w-full rounded-sm cursor-pointer hover:bg-custom-background-80 ${
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={isCompleted || !isEditingAllowed}
>
<span
className={`group flex w-full items-center justify-between gap-2 py-1 px-1.5 text-sm ${
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
watch("end_date") ? "" : "text-custom-text-400"
}`}
>
{areYearsEqual
? renderShortDate(endDate, "No date selected")
: renderShortMonthDate(endDate, "No date selected")}
{renderFormattedDate(endDate) ?? "No date selected"}
</span>
</Popover.Button>

View File

@ -136,7 +136,7 @@ export const TransferIssuesModal: React.FC<Props> = observer(({ isOpen, handleCl
<div className="flex w-full justify-between">
<span>{option?.name}</span>
<span className=" flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
{option.status}
{option.status.toLocaleLowerCase()}
</span>
</div>
</button>

View File

@ -2,7 +2,7 @@ import { useState, FC } from "react";
// ui
import { Button } from "@plane/ui";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IExportData } from "types";
@ -50,7 +50,7 @@ export const SingleExport: FC<Props> = ({ service, refreshing }) => {
</span>
</h4>
<div className="mt-2 flex items-center gap-2 text-xs text-custom-text-200">
<span>{renderShortDateWithYearFormat(service.created_at)}</span>|
<span>{renderFormattedDate(service.created_at)}</span>|
<span>Exported by {service?.initiated_by_detail?.display_name}</span>
</div>
</div>

View File

@ -1,10 +1,9 @@
import { FC } from "react";
// hooks
import { useChart } from "../hooks";
// helpers
import { ChartDraggable } from "../helpers/draggable";
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
@ -64,8 +63,8 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
// call the block update handler with the updated dates
blockUpdateHandler(block.data, {
start_date: renderDateFormat(updatedStartDate),
target_date: renderDateFormat(updatedTargetDate),
start_date: renderFormattedPayloadDate(updatedStartDate) ?? undefined,
target_date: renderFormattedPayloadDate(updatedTargetDate) ?? undefined,
});
};

View File

@ -94,7 +94,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
return (
<Draggable

View File

@ -94,7 +94,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
return (
<Draggable

View File

@ -95,7 +95,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
return (
<Draggable

View File

@ -111,7 +111,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
return (
<Draggable

View File

@ -1,12 +1,11 @@
import { useRouter } from "next/router";
import Link from "next/link";
// ui
import { Tooltip, PriorityIcon } from "@plane/ui";
// icons
import { AlertTriangle, CalendarDays, CheckCircle2, Clock, Copy, XCircle } from "lucide-react";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IInboxIssue } from "types";
// constants
@ -45,11 +44,11 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
</Tooltip>
<Tooltip
tooltipHeading="Created on"
tooltipContent={`${renderShortDateWithYearFormat(issue.created_at ?? "")}`}
tooltipContent={`${renderFormattedDate(issue.created_at ?? "")}`}
>
<div className="flex items-center gap-1 rounded border border-custom-border-200 px-2 py-[0.19rem] text-xs text-custom-text-200 shadow-sm">
<CalendarDays size={12} strokeWidth={1.5} />
<span>{renderShortDateWithYearFormat(issue.created_at ?? "")}</span>
<span>{renderFormattedDate(issue.created_at ?? "")}</span>
</div>
</Tooltip>
</div>

View File

@ -4,7 +4,6 @@ import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useForm } from "react-hook-form";
import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
@ -13,7 +12,7 @@ import { InboxIssueActivity } from "components/inbox";
// ui
import { Loader, StateGroupIcon } from "@plane/ui";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IInboxIssue, IIssue } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
@ -193,12 +192,12 @@ export const InboxMainContent: React.FC = observer(() => {
{new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date() ? (
<p>
This issue was snoozed till{" "}
{renderShortDateWithYearFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
{renderFormattedDate(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
</p>
) : (
<p>
This issue has been snoozed till{" "}
{renderShortDateWithYearFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
{renderFormattedDate(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
</p>
)}
</>

View File

@ -3,7 +3,7 @@ import { CustomMenu } from "@plane/ui";
// icons
import { Trash2 } from "lucide-react";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IImporterService } from "types";
// constants
@ -39,7 +39,7 @@ export const SingleImport: React.FC<Props> = ({ service, refreshing, handleDelet
</span>
</h4>
<div className="mt-2 flex items-center gap-2 text-xs text-custom-text-200">
<span>{renderShortDateWithYearFormat(service.created_at)}</span>|
<span>{renderFormattedDate(service.created_at)}</span>|
<span>Imported by {service.initiated_by_detail.display_name}</span>
</div>
</div>

View File

@ -9,7 +9,7 @@ import { CommentCard } from "components/issues/comment";
// ui
import { Loader, Tooltip } from "@plane/ui";
// helpers
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "helpers/date-time.helper";
// types
import { IIssueActivity } from "types";
import { History } from "lucide-react";
@ -114,11 +114,11 @@ export const IssueActivitySection: React.FC<Props> = ({
)}{" "}
{message}{" "}
<Tooltip
tooltipContent={`${renderLongDateFormat(activityItem.created_at)}, ${render24HourFormatTime(
tooltipContent={`${renderFormattedDate(activityItem.created_at)}, ${renderFormattedTime(
activityItem.created_at
)}`}
>
<span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
<span className="whitespace-nowrap">{calculateTimeAgo(activityItem.created_at)}</span>
</Tooltip>
</div>
</div>

View File

@ -15,7 +15,7 @@ import { ProjectMemberService } from "services/project";
import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
import { renderLongDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
import { convertBytesToSize, getFileExtension, getFileName } from "helpers/attachment.helper";
// type
import { IIssueAttachment } from "types";
@ -77,7 +77,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
<Tooltip
tooltipContent={`${
people?.find((person) => person.member.id === file.updated_by)?.member.display_name ?? ""
} uploaded on ${renderLongDateFormat(file.updated_at)}`}
} uploaded on ${renderFormattedDate(file.updated_at)}`}
>
<span>
<AlertCircle className="h-3 w-3" />

View File

@ -1,21 +1,20 @@
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
// services
import { FileService } from "services/file.service";
// icons
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
// hooks
import useUser from "hooks/use-user";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// ui
import { CustomMenu } from "@plane/ui";
import { CommentReaction } from "components/issues";
import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor";
// helpers
import { timeAgo } from "helpers/date-time.helper";
import { calculateTimeAgo } from "helpers/date-time.helper";
// types
import type { IIssueActivity } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
// services
const fileService = new FileService();
@ -98,7 +97,7 @@ export const CommentCard: React.FC<Props> = ({
<div className="text-xs">
{comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name}
</div>
<p className="mt-0.5 text-xs text-custom-text-200">commented {timeAgo(comment.created_at)}</p>
<p className="mt-0.5 text-xs text-custom-text-200">commented {calculateTimeAgo(comment.created_at)}</p>
</div>
<div className="issue-comments-section p-0">
<form className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}>

View File

@ -4,7 +4,7 @@ import { Droppable } from "@hello-pangea/dnd";
// components
import { CalendarIssueBlocks, ICalendarDate, CalendarQuickAddIssueForm } from "components/issues";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// constants
import { MONTHS_LIST } from "constants/calendar";
import { IIssue } from "types";
@ -52,7 +52,9 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
const [showAllIssues, setShowAllIssues] = useState(false);
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
const issueIdList = groupedIssueIds ? groupedIssueIds[renderDateFormat(date.date)] : null;
const formattedDatePayload = renderFormattedPayloadDate(date.date);
if (!formattedDatePayload) return null;
const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : null;
const totalIssues = issueIdList?.length ?? 0;
return (
@ -78,7 +80,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
{/* content */}
<div className="h-full w-full">
<Droppable droppableId={renderDateFormat(date.date)} isDropDisabled={false}>
<Droppable droppableId={formattedDatePayload} isDropDisabled={false}>
{(provided, snapshot) => (
<div
className={`h-full w-full select-none overflow-y-auto ${
@ -100,9 +102,9 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
<div className="px-2 py-1">
<CalendarQuickAddIssueForm
formKey="target_date"
groupId={renderDateFormat(date.date)}
groupId={formattedDatePayload}
prePopulatedData={{
target_date: renderDateFormat(date.date),
target_date: renderFormattedPayloadDate(date.date),
}}
quickAddCallback={quickAddCallback}
viewId={viewId}

View File

@ -2,7 +2,7 @@ import { observer } from "mobx-react-lite";
// components
import { CalendarDayTile } from "components/issues";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { ICalendarDate, ICalendarWeek } from "./types";
import { IIssue } from "types";
@ -65,7 +65,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
return (
<CalendarDayTile
issuesFilterStore={issuesFilterStore}
key={renderDateFormat(date.date)}
key={renderFormattedPayloadDate(date.date)}
date={date}
issues={issues}
groupedIssueIds={groupedIssueIds}

View File

@ -1,9 +1,8 @@
import { observer } from "mobx-react-lite";
// icons
import { X } from "lucide-react";
// helpers
import { renderLongDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper";
// constants
import { DATE_FILTER_OPTIONS } from "constants/filters";
@ -28,7 +27,7 @@ export const AppliedDateFilters: React.FC<Props> = observer((props) => {
if (dateParts.length === 2) {
const [date, time] = dateParts;
dateLabel = `${capitalizeFirstLetter(time)} ${renderLongDateFormat(date)}`;
dateLabel = `${capitalizeFirstLetter(time)} ${renderFormattedDate(date)}`;
}
}

View File

@ -2,7 +2,7 @@ import { useRouter } from "next/router";
// ui
import { Tooltip, StateGroupIcon } from "@plane/ui";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
@ -34,15 +34,13 @@ export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
<div className="space-y-1">
<h5>{data?.name}</h5>
<div>
{renderShortDate(data?.start_date ?? "")} to {renderShortDate(data?.target_date ?? "")}
{renderFormattedDate(data?.start_date ?? "")} to {renderFormattedDate(data?.target_date ?? "")}
</div>
</div>
}
position="top-left"
>
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
<div className="relative w-full truncate px-2.5 py-1 text-sm text-custom-text-100">{data?.name}</div>
</Tooltip>
<div className="relative w-full truncate px-2.5 py-1 text-sm text-custom-text-100">{data?.name}</div>
</Tooltip>
</div>
);

View File

@ -11,11 +11,10 @@ import useKeypress from "hooks/use-keypress";
import useProjectDetails from "hooks/use-project-details";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { createIssuePayload } from "helpers/issue.helper";
// types
import { IIssue } from "types";
// helpers
import { createIssuePayload } from "helpers/issue.helper";
type Props = {
prePopulatedData?: Partial<IIssue>;
@ -116,8 +115,8 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
...(prePopulatedData ?? {}),
...formData,
start_date: renderDateFormat(new Date()),
target_date: renderDateFormat(new Date(new Date().getTime() + 24 * 60 * 60 * 1000)),
start_date: renderFormattedPayloadDate(new Date()),
target_date: renderFormattedPayloadDate(new Date(new Date().getTime() + 24 * 60 * 60 * 1000)),
});
try {

View File

@ -12,7 +12,7 @@ import { Tooltip } from "@plane/ui";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// helpers
import { renderDateFormat, renderFormattedDate } from "helpers/date-time.helper";
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
export interface IIssuePropertyDate {
value: string | null;
@ -105,7 +105,7 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
onChange={(val, e) => {
e?.stopPropagation();
if (onChange && val) {
onChange(renderDateFormat(val));
onChange(renderFormattedPayloadDate(val));
close();
}
}}

View File

@ -1,9 +1,8 @@
import React from "react";
// hooks
import useSubIssue from "hooks/use-sub-issue";
// helpers
import { renderLongDetailDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
@ -20,7 +19,7 @@ export const SpreadsheetCreatedOnColumn: React.FC<Props> = ({ issue, expandedIss
return (
<>
<div className="flex h-11 w-full items-center justify-center text-xs border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80">
{renderLongDetailDateFormat(issue.created_at)}
{renderFormattedDate(issue.created_at)}
</div>
{isExpanded &&

View File

@ -1,9 +1,8 @@
import React from "react";
// hooks
import useSubIssue from "hooks/use-sub-issue";
// helpers
import { renderLongDetailDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
@ -22,7 +21,7 @@ export const SpreadsheetUpdatedOnColumn: React.FC<Props> = (props) => {
return (
<>
<div className="flex h-11 w-full items-center justify-center text-xs border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80">
{renderLongDetailDateFormat(issue.updated_at)}
{renderFormattedDate(issue.updated_at)}
</div>
{isExpanded &&

View File

@ -7,7 +7,7 @@ import { Loader, Tooltip } from "@plane/ui";
import { ActivityIcon, ActivityMessage } from "components/core";
import { IssueCommentCard } from "./comment-card";
// helpers
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "helpers/date-time.helper";
// types
import { IIssueActivity, IUser } from "types";
@ -104,11 +104,11 @@ export const IssueActivityCard: FC<IIssueActivityCard> = (props) => {
)}
{message}
<Tooltip
tooltipContent={`${renderLongDateFormat(activityItem.created_at)}, ${render24HourFormatTime(
tooltipContent={`${renderFormattedDate(activityItem.created_at)}, ${renderFormattedTime(
activityItem.created_at
)}`}
>
<span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
<span className="whitespace-nowrap">{calculateTimeAgo(activityItem.created_at)}</span>
</Tooltip>
</div>
</div>

View File

@ -10,7 +10,7 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
// components
import { IssueCommentReaction } from "./comment-reaction";
// helpers
import { timeAgo } from "helpers/date-time.helper";
import { calculateTimeAgo } from "helpers/date-time.helper";
// types
import type { IIssueActivity, IUser } from "types";
@ -106,7 +106,7 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
<div className="text-xs">
{comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name}
</div>
<p className="mt-0.5 text-xs text-custom-text-200">commented {timeAgo(comment.created_at)}</p>
<p className="mt-0.5 text-xs text-custom-text-200">commented {calculateTimeAgo(comment.created_at)}</p>
</div>
<div className="issue-comments-section p-0">

View File

@ -4,8 +4,8 @@ import { Popover, Transition } from "@headlessui/react";
import { CalendarDays, X } from "lucide-react";
// react-datepicker
import DatePicker from "react-datepicker";
// import "react-datepicker/dist/react-datepicker.css";
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
// helpers
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
type Props = {
label: string;
@ -35,7 +35,7 @@ export const IssueDateSelect: FC<Props> = ({ label, maxDate, minDate, onChange,
{value ? (
<>
<CalendarDays className="h-3 w-3 flex-shrink-0" />
<span>{renderShortDateWithYearFormat(value)}</span>
<span>{renderFormattedDate(value)}</span>
<button onClick={() => onChange(null)}>
<X className="h-3 w-3 flex-shrink-0" />
</button>
@ -69,7 +69,7 @@ export const IssueDateSelect: FC<Props> = ({ label, maxDate, minDate, onChange,
selected={value ? new Date(value) : null}
onChange={(val) => {
if (!val) onChange("");
else onChange(renderDateFormat(val));
else onChange(renderFormattedPayloadDate(val));
close();
}}

View File

@ -3,12 +3,7 @@ import { CustomDatePicker } from "components/ui";
import { Tooltip } from "@plane/ui";
import { CalendarCheck } from "lucide-react";
// helpers
import {
findHowManyDaysLeft,
renderShortDate,
renderShortDateWithYearFormat,
renderShortMonthDate,
} from "helpers/date-time.helper";
import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
@ -36,14 +31,10 @@ export const ViewDueDateSelect: React.FC<Props> = ({
const minDate = issue.start_date ? new Date(issue.start_date) : null;
minDate?.setDate(minDate.getDate());
const today = new Date();
const endDate = new Date(issue.target_date ?? "");
const areYearsEqual = endDate.getFullYear() === today.getFullYear();
return (
<Tooltip
tooltipHeading="Due date"
tooltipContent={issue.target_date ? renderShortDateWithYearFormat(issue.target_date) ?? "N/A" : "N/A"}
tooltipContent={issue.target_date ? renderFormattedDate(issue.target_date) ?? "N/A" : "N/A"}
position={tooltipPosition}
>
<div
@ -68,11 +59,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
{issue.target_date ? (
<>
<CalendarCheck className="h-3.5 w-3.5 flex-shrink-0" />
<span>
{areYearsEqual
? renderShortDate(issue.target_date ?? "", "_ _")
: renderShortMonthDate(issue.target_date ?? "", "_ _")}
</span>
<span>{renderFormattedDate(issue.target_date) ?? "_ _"}</span>
</>
) : (
<>

View File

@ -3,7 +3,7 @@ import { CustomDatePicker } from "components/ui";
import { Tooltip } from "@plane/ui";
import { CalendarClock } from "lucide-react";
// helpers
import { renderShortDate, renderShortDateWithYearFormat, renderShortMonthDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
@ -30,14 +30,11 @@ export const ViewStartDateSelect: React.FC<Props> = ({
}) => {
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
maxDate?.setDate(maxDate.getDate());
const today = new Date();
const startDate = new Date(issue.start_date ?? "");
const areYearsEqual = startDate.getFullYear() === today.getFullYear();
return (
<Tooltip
tooltipHeading="Start date"
tooltipContent={issue.start_date ? renderShortDateWithYearFormat(issue.start_date) ?? "N/A" : "N/A"}
tooltipContent={issue.start_date ? renderFormattedDate(issue.start_date) ?? "N/A" : "N/A"}
position={tooltipPosition}
>
<div className={`group max-w-[6.5rem] flex-shrink-0 ${className}`}>
@ -56,11 +53,7 @@ export const ViewStartDateSelect: React.FC<Props> = ({
{issue?.start_date ? (
<>
<CalendarClock className="h-3.5 w-3.5 flex-shrink-0" />
<span>
{areYearsEqual
? renderShortDate(issue?.start_date, "_ _")
: renderShortMonthDate(issue?.start_date, "_ _")}
</span>
<span>{renderFormattedDate(issue?.start_date ?? "_ _")}</span>
</>
) : (
<>

View File

@ -1,9 +1,8 @@
import { useRouter } from "next/router";
// ui
import { Tooltip, ModuleStatusIcon } from "@plane/ui";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IModule } from "types";
// constants
@ -25,7 +24,7 @@ export const ModuleGanttBlock = ({ data }: { data: IModule }) => {
<div className="space-y-1">
<h5>{data?.name}</h5>
<div>
{renderShortDate(data?.start_date ?? "")} to {renderShortDate(data?.target_date ?? "")}
{renderFormattedDate(data?.start_date ?? "")} to {renderFormattedDate(data?.target_date ?? "")}
</div>
</div>
}

View File

@ -14,7 +14,7 @@ import { Avatar, AvatarGroup, CustomMenu, LayersIcon, Tooltip } from "@plane/ui"
import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
import { renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IModule } from "types";
// constants
@ -56,8 +56,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
const isDateValid = module.target_date || module.start_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === module.status);
const issueCount = module
@ -213,8 +211,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
{isDateValid ? (
<>
<span className="text-xs text-custom-text-300">
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "}
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
{renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"}
</span>
</>
) : (

View File

@ -14,7 +14,7 @@ import { Avatar, AvatarGroup, CircularProgressIndicator, CustomMenu, Tooltip } f
import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
import { renderShortDate, renderShortMonthDate } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { IModule } from "types";
// constants
@ -49,8 +49,6 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
const renderDate = module.start_date || module.target_date;
const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
const moduleStatus = MODULE_STATUS.find((status) => status.value === module.status);
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
@ -176,10 +174,8 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
</div>
{renderDate && (
<span className="flex w-28 items-center justify-center gap-2 text-xs text-custom-text-300">
{areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")}
{" - "}
{areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")}
<span className="flex w-40 items-center justify-center gap-2 text-xs text-custom-text-300">
{renderFormattedDate(startDate) ?? "_ _"} - {renderFormattedDate(endDate) ?? "_ _"}
</span>
)}

View File

@ -27,12 +27,7 @@ import {
Trash2,
} from "lucide-react";
// helpers
import {
isDateGreaterThanToday,
renderDateFormat,
renderShortDate,
renderShortMonthDate,
} from "helpers/date-time.helper";
import { isDateGreaterThanToday, renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
import { copyUrlToClipboard } from "helpers/string.helper";
// types
import { IIssueFilterOptions, ILinkDetails, IModule, ModuleLink } from "types";
@ -187,8 +182,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
}
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
target_date: renderDateFormat(`${watch("target_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
});
setToastAlert({
type: "success",
@ -212,8 +207,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
}
submitChanges({
start_date: renderDateFormat(`${watch("start_date")}`),
target_date: renderDateFormat(`${watch("target_date")}`),
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
});
setToastAlert({
type: "success",
@ -285,9 +280,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const startDate = new Date(watch("start_date") ?? moduleDetails.start_date ?? "");
const endDate = new Date(watch("target_date") ?? moduleDetails.target_date ?? "");
const areYearsEqual =
startDate.getFullYear() === endDate.getFullYear() || isNaN(startDate.getFullYear()) || isNaN(endDate.getFullYear());
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
const issueCount =
@ -400,19 +392,17 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="relative flex w-1/2 items-center rounded-sm">
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
<Popover.Button
className={`text-sm font-medium text-custom-text-300 w-full rounded-sm cursor-pointer hover:bg-custom-background-80 ${
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={!isEditingAllowed}
>
<span
className={`group flex w-full items-center justify-between gap-2 py-1 px-1.5 text-sm ${
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
watch("start_date") ? "" : "text-custom-text-400"
}`}
>
{areYearsEqual
? renderShortDate(startDate, "No date selected")
: renderShortMonthDate(startDate, "No date selected")}
{renderFormattedDate(startDate) ?? "No date selected"}
</span>
</Popover.Button>
@ -453,19 +443,17 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
<>
<Popover.Button
className={`text-sm font-medium text-custom-text-300 w-full rounded-sm cursor-pointer hover:bg-custom-background-80 ${
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
}`}
disabled={!isEditingAllowed}
>
<span
className={`group flex w-full items-center justify-between gap-2 py-1 px-1.5 text-sm ${
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
watch("target_date") ? "" : "text-custom-text-400"
}`}
>
{areYearsEqual
? renderShortDate(endDate, "No date selected")
: renderShortMonthDate(endDate, "No date selected")}
{renderFormattedDate(endDate) ?? "No date selected"}
</span>
</Popover.Button>

View File

@ -11,11 +11,9 @@ import { snoozeOptions } from "constants/notification";
// helper
import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper";
import {
formatDateDistance,
render12HourFormatTime,
renderLongDateFormat,
renderShortDate,
renderShortDateWithYearFormat,
calculateTimeAgo,
renderFormattedTime,
renderFormattedDate,
} from "helpers/date-time.helper";
// type
import type { IUserNotification } from "types";
@ -112,7 +110,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
{notification.data.issue_activity.field !== "None" ? (
notification.data.issue_activity.field !== "comment" ? (
notification.data.issue_activity.field === "target_date" ? (
renderShortDateWithYearFormat(notification.data.issue_activity.new_value)
renderFormattedDate(notification.data.issue_activity.new_value)
) : notification.data.issue_activity.field === "attachment" ? (
"the issue"
) : notification.data.issue_activity.field === "description" ? (
@ -151,11 +149,11 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
<p className="flex flex-shrink-0 items-center justify-end gap-x-1 text-custom-text-300">
<Clock className="h-4 w-4" />
<span>
Till {renderShortDate(notification.snoozed_till)}, {render12HourFormatTime(notification.snoozed_till)}
Till {renderFormattedDate(notification.snoozed_till)}, {renderFormattedTime(notification.snoozed_till, '12-hour')}
</span>
</p>
) : (
<p className="flex-shrink-0 text-custom-text-300">{formatDateDistance(notification.created_at)}</p>
<p className="flex-shrink-0 text-custom-text-300">{calculateTimeAgo(notification.created_at)}</p>
)}
</div>
</div>
@ -233,7 +231,7 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
markSnoozeNotification(notification.id, item.value).then(() => {
setToastAlert({
title: `Notification snoozed till ${renderLongDateFormat(item.value)}`,
title: `Notification snoozed till ${renderFormattedDate(item.value)}`,
type: "success",
});
});

View File

@ -3,8 +3,8 @@ import { useRouter } from "next/router";
import { useForm, Controller } from "react-hook-form";
import { Transition, Dialog } from "@headlessui/react";
import { X } from "lucide-react";
// date helper
import { getAllTimeIn30MinutesInterval } from "helpers/date-time.helper";
// constants
import { allTimeIn30MinutesInterval12HoursFormat } from "constants/notification";
// hooks
import useToast from "hooks/use-toast";
// ui
@ -33,7 +33,7 @@ const defaultValues: FormValues = {
period: "AM",
};
const timeStamps = getAllTimeIn30MinutesInterval();
const timeStamps = allTimeIn30MinutesInterval12HoursFormat;
export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
const { isOpen, onClose, notification, onSuccess, onSubmit: handleSubmitSnooze } = props;

View File

@ -19,7 +19,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
import { render24HourFormatTime, renderFormattedDate } from "helpers/date-time.helper";
import { renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
// ui
import { CustomMenu, Tooltip } from "@plane/ui";
// components
@ -194,19 +194,19 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
<div className="flex items-center gap-2.5">
{page.archived_at ? (
<Tooltip
tooltipContent={`Archived at ${render24HourFormatTime(page.archived_at)} on ${renderFormattedDate(
tooltipContent={`Archived at ${renderFormattedTime(page.archived_at)} on ${renderFormattedDate(
page.archived_at
)}`}
>
<p className="text-sm text-custom-text-200">{render24HourFormatTime(page.archived_at)}</p>
<p className="text-sm text-custom-text-200">{renderFormattedTime(page.archived_at)}</p>
</Tooltip>
) : (
<Tooltip
tooltipContent={`Last updated at ${render24HourFormatTime(
tooltipContent={`Last updated at ${renderFormattedTime(
page.updated_at
)} on ${renderFormattedDate(page.updated_at)}`}
>
<p className="text-sm text-custom-text-200">{render24HourFormatTime(page.updated_at)}</p>
<p className="text-sm text-custom-text-200">{renderFormattedTime(page.updated_at)}</p>
</Tooltip>
)}
{isEditingAllowed && (

View File

@ -10,7 +10,7 @@ import { Loader } from "@plane/ui";
// image
import recentActivityEmptyState from "public/empty-state/recent_activity.svg";
// helpers
import { timeAgo } from "helpers/date-time.helper";
import { calculateTimeAgo } from "helpers/date-time.helper";
// fetch-keys
import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
@ -71,7 +71,7 @@ export const ProfileActivity = () => {
</span>
)}
</p>
<p className="text-xs text-custom-text-200">{timeAgo(activity.created_at)}</p>
<p className="text-xs text-custom-text-200">{calculateTimeAgo(activity.created_at)}</p>
</div>
</div>
))}

View File

@ -14,7 +14,7 @@ import { Loader, Tooltip } from "@plane/ui";
// icons
import { ChevronDown, Pencil } from "lucide-react";
// helpers
import { renderLongDetailDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
import { renderEmoji } from "helpers/emoji.helper";
// fetch-keys
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
@ -48,7 +48,7 @@ export const ProfileSidebar = () => {
const userDetails = [
{
label: "Joined on",
value: renderLongDetailDateFormat(userProjectsData?.user_data.date_joined ?? ""),
value: renderFormattedDate(userProjectsData?.user_data.date_joined ?? ""),
},
{
label: "Timezone",

View File

@ -8,7 +8,7 @@ import { Button, CustomSelect, Input, TextArea } from "@plane/ui";
import { IProject, IWorkspace } from "types";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
// constants
import { NETWORK_CHOICES } from "constants/project";
// services
@ -310,7 +310,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
{isSubmitting ? "Updating Project..." : "Update Project"}
</Button>
<span className="text-sm italic text-custom-sidebar-text-400">
Created on {renderShortDateWithYearFormat(project?.created_at)}
Created on {renderFormattedDate(project?.created_at)}
</span>
</>
</div>

View File

@ -1,12 +1,11 @@
import React from "react";
import { Popover, Transition } from "@headlessui/react";
// react-datepicker
import DatePicker from "react-datepicker";
// icons
import { CalendarDays, X } from "lucide-react";
// import "react-datepicker/dist/react-datepicker.css";
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
// helpers
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
type Props = {
value: string | null;
@ -25,7 +24,7 @@ export const DateSelect: React.FC<Props> = ({ value, onChange, label, minDate, m
{value ? (
<>
<CalendarDays className="h-3 w-3 flex-shrink-0" />
<span>{renderShortDateWithYearFormat(value)}</span>
<span>{renderFormattedDate(value)}</span>
<button onClick={() => onChange(null)}>
<X className="h-3 w-3" />
</button>
@ -52,7 +51,7 @@ export const DateSelect: React.FC<Props> = ({ value, onChange, label, minDate, m
selected={value ? new Date(value) : null}
onChange={(val) => {
if (!val) onChange("");
else onChange(renderDateFormat(val));
else onChange(renderFormattedPayloadDate(val));
if (closeOnSelect) close();
}}

View File

@ -2,7 +2,7 @@
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
type Props = {
renderAs?: "input" | "button";
@ -45,7 +45,7 @@ export const CustomDatePicker: React.FC<Props> = ({
selected={value ? new Date(value) : null}
onChange={(val) => {
if (!val) onChange(null);
else onChange(renderDateFormat(val));
else onChange(renderFormattedPayloadDate(val));
}}
onCalendarOpen={handleOnOpen}
onCalendarClose={handleOnClose}

View File

@ -1,7 +1,5 @@
import React from "react";
import useSWR from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
@ -12,7 +10,7 @@ import { Loader } from "@plane/ui";
// icons
import { X } from "lucide-react";
// helpers
import { renderLongDateFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
type Props = {
isOpen: boolean;
@ -68,7 +66,7 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-background-90 px-3 py-1.5 text-xs">
{item.tag_name}
</span>
<span>{renderLongDateFormat(item.published_at)}</span>
<span>{renderFormattedDate(item.published_at)}</span>
{index === 0 && (
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-primary px-3 py-1.5 text-xs text-white">
New

View File

@ -2,7 +2,7 @@
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
type Props = {
renderAs?: "input" | "button";
@ -38,7 +38,7 @@ export const CustomRangeDatePicker: React.FC<Props> = ({
selected={value ? new Date(value) : null}
onChange={(val) => {
if (!val) onChange(null);
else onChange(renderDateFormat(val));
else onChange(renderFormattedPayloadDate(val));
}}
className={`${
renderAs === "input"

View File

@ -1,4 +1,6 @@
import { renderDateFormat } from "helpers/date-time.helper";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IWebhook, IWorkspace } from "types";
export const getCurrentHookAsCSV = (
@ -8,8 +10,8 @@ export const getCurrentHookAsCSV = (
) => ({
id: webhook?.id || "",
url: webhook?.url || "",
created_at: renderDateFormat(webhook?.created_at),
updated_at: renderDateFormat(webhook?.updated_at),
created_at: renderFormattedPayloadDate(webhook?.created_at || "") ?? "",
updated_at: renderFormattedPayloadDate(webhook?.updated_at || "") ?? "",
is_active: webhook?.is_active?.toString() || "",
secret_key: secretKey || "",
project: webhook?.project?.toString() || "",

View File

@ -1,9 +1,8 @@
import { useEffect, useRef, useState } from "react";
// ui
import { Tooltip } from "@plane/ui";
// helpers
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
// types
import { IUserActivity } from "types";
// constants
@ -35,7 +34,7 @@ export const ActivityGraph: React.FC<Props> = ({ activities }) => {
const date = new Date(year, month, 1);
while (date.getMonth() === month && date < new Date()) {
dates.push(renderDateFormat(new Date(date)));
dates.push(renderFormattedPayloadDate(new Date(date)) ?? "");
date.setDate(date.getDate() + 1);
}
@ -102,7 +101,7 @@ export const ActivityGraph: React.FC<Props> = ({ activities }) => {
key={`${date}-${index}`}
tooltipContent={`${
isActive ? isActive.activity_count : 0
} activities on ${renderShortDateWithYearFormat(date)}`}
} activities on ${renderFormattedDate(date)}`}
>
<div
className={`${date === "" ? "pointer-events-none opacity-0" : ""} h-4 w-4 rounded ${

View File

@ -1,11 +1,10 @@
import { useRouter } from "next/router";
import Link from "next/link";
// icons
import { AlertTriangle } from "lucide-react";
import { LayersIcon, Loader } from "@plane/ui";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
import { truncateText } from "helpers/string.helper";
// types
import { IIssueLite } from "types";
@ -67,7 +66,7 @@ export const IssuesList: React.FC<Props> = ({ issues, type }) => {
</h5>
<h5 className="col-span-2">{truncateText(issue.name, 30)}</h5>
<h5 className="cursor-default">
{renderShortDateWithYearFormat(new Date(date?.toString() ?? ""))}
{renderFormattedDate(new Date(date?.toString() ?? ""))}
</h5>
</div>
</span>

View File

@ -24,3 +24,34 @@ export const snoozeOptions = [
value: null,
},
];
// Constant for all time values in 30 minutes interval in 12 hours format
export const allTimeIn30MinutesInterval12HoursFormat: Array<{
label: string;
value: string;
}> = [
{ label: "12:00", value: "12:00" },
{ label: "12:30", value: "12:30" },
{ label: "01:00", value: "01:00" },
{ label: "01:30", value: "01:30" },
{ label: "02:00", value: "02:00" },
{ label: "02:30", value: "02:30" },
{ label: "03:00", value: "03:00" },
{ label: "03:30", value: "03:30" },
{ label: "04:00", value: "04:00" },
{ label: "04:30", value: "04:30" },
{ label: "05:00", value: "05:00" },
{ label: "05:30", value: "05:30" },
{ label: "06:00", value: "06:00" },
{ label: "06:30", value: "06:30" },
{ label: "07:00", value: "07:00" },
{ label: "07:30", value: "07:30" },
{ label: "08:00", value: "08:00" },
{ label: "08:30", value: "08:30" },
{ label: "09:00", value: "09:00" },
{ label: "09:30", value: "09:30" },
{ label: "10:00", value: "10:00" },
{ label: "10:30", value: "10:30" },
{ label: "11:00", value: "11:00" },
{ label: "11:30", value: "11:30" },
];

View File

@ -1,5 +1,5 @@
// helpers
import { getWeekNumberOfDate, renderDateFormat } from "helpers/date-time.helper";
import { getWeekNumberOfDate, renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { ICalendarDate, ICalendarPayload } from "components/issues";
@ -73,16 +73,18 @@ export const generateCalendarData = (currentStructure: ICalendarPayload | null,
const date = new Date(year, month, dayNumber + 1);
currentWeekObject[renderDateFormat(date)] = {
date,
year,
month,
day: dayNumber + 1,
week: weekNumber,
is_current_month: date.getMonth() === month,
is_current_week: getWeekNumberOfDate(date) === getWeekNumberOfDate(new Date()),
is_today: date.toDateString() === new Date().toDateString(),
};
const formattedDatePayload = renderFormattedPayloadDate(date);
if (formattedDatePayload)
currentWeekObject[formattedDatePayload] = {
date,
year,
month,
day: dayNumber + 1,
week: weekNumber,
is_current_month: date.getMonth() === month,
is_current_week: getWeekNumberOfDate(date) === getWeekNumberOfDate(new Date()),
is_today: date.toDateString() === new Date().toDateString(),
};
}
calendarData[`y-${year}`][`m-${month}`][`w-${weekNumber}`] = currentWeekObject;

View File

@ -1,384 +1,83 @@
import { format } from "date-fns";
import { differenceInDays, format, formatDistanceToNow, isAfter, isValid, parseISO } from "date-fns";
export const addDays = ({ date, days }: { date: Date; days: number }): Date => {
date.setDate(date.getDate() + days);
return date;
// Format Date Helpers
/**
* @returns {string | null} formatted date in the format of MMM dd, yyyy
* @description Returns date in the formatted format
* @param {Date | string} date
* @example renderFormattedDate("2024-01-01") // Jan 01, 2024
*/
export const renderFormattedDate = (date: string | Date): string | null => {
if (!date) return null;
// Parse the date to check if it is valid
const parsedDate = new Date(date);
// Check if the parsed date is valid before formatting
if (!isValid(parsedDate)) return null; // Return null for invalid dates
// Format the date in format (MMM dd, yyyy)
const formattedDate = format(parsedDate, "MMM dd, yyyy");
return formattedDate;
};
export const renderDateFormat = (date: string | Date | null | undefined, dayFirst: boolean = false) => {
if (!date) return "N/A";
var d = new Date(date),
month = "" + (d.getMonth() + 1),
day = "" + d.getDate(),
year = d.getFullYear();
if (month.length < 2) month = "0" + month;
if (day.length < 2) day = "0" + day;
return dayFirst ? [day, month, year].join("-") : [year, month, day].join("-");
/**
* @returns {string} formatted date in the format of MMM dd
* @description Returns date in the formatted format
* @param {string | Date} date
* @example renderShortDateFormat("2024-01-01") // Jan 01
*/
export const renderFormattedDateWithoutYear = (date: string | Date): string => {
if (!date) return "";
// Parse the date to check if it is valid
const parsedDate = new Date(date);
// Check if the parsed date is valid before formatting
if (!isValid(parsedDate)) return ""; // Return empty string for invalid dates
// Format the date in short format (MMM dd)
const formattedDate = format(parsedDate, "MMM dd");
return formattedDate;
};
export const renderShortNumericDateFormat = (date: string | Date) =>
new Date(date).toLocaleDateString("en-UK", {
day: "numeric",
month: "short",
});
export const renderLongDetailDateFormat = (date: string | Date) =>
new Date(date).toLocaleDateString("en-UK", {
day: "numeric",
month: "long",
year: "numeric",
});
export const findHowManyDaysLeft = (date: string | Date) => {
const today = new Date();
const eventDate = new Date(date);
const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
return Math.ceil(timeDiff / (1000 * 3600 * 24));
/**
* @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): string | null => {
if (!date) return null;
// Parse the date to check if it is valid
const parsedDate = new Date(date);
// Check if the parsed date is valid before formatting
if (!isValid(parsedDate)) return null; // Return null for invalid dates
// Format the date in payload format (yyyy-mm-dd)
const formattedDate = format(parsedDate, "yyyy-MM-dd");
return formattedDate;
};
export const getDatesInRange = (startDate: string | Date, endDate: string | Date) => {
startDate = new Date(startDate);
endDate = new Date(endDate);
const date = new Date(startDate.getTime());
const dates = [];
while (date <= endDate) {
dates.push(new Date(date));
date.setDate(date.getDate() + 1);
}
return dates;
};
export const timeAgo = (time: any) => {
switch (typeof time) {
case "number":
break;
case "string":
time = +new Date(time);
break;
case "object":
if (time.constructor === Date) time = time.getTime();
break;
default:
time = +new Date();
}
var time_formats = [
[60, "seconds", 1], // 60
[120, "1 minute ago", "1 minute from now"], // 60*2
[3600, "minutes", 60], // 60*60, 60
[7200, "1 hour ago", "1 hour from now"], // 60*60*2
[86400, "hours", 3600], // 60*60*24, 60*60
[172800, "Yesterday", "Tomorrow"], // 60*60*24*2
[604800, "days", 86400], // 60*60*24*7, 60*60*24
[1209600, "Last week", "Next week"], // 60*60*24*7*4*2
[2419200, "weeks", 604800], // 60*60*24*7*4, 60*60*24*7
[4838400, "Last month", "Next month"], // 60*60*24*7*4*2
[29030400, "months", 2419200], // 60*60*24*7*4*12, 60*60*24*7*4
[58060800, "Last year", "Next year"], // 60*60*24*7*4*12*2
[2903040000, "years", 29030400], // 60*60*24*7*4*12*100, 60*60*24*7*4*12
[5806080000, "Last century", "Next century"], // 60*60*24*7*4*12*100*2
[58060800000, "centuries", 2903040000], // 60*60*24*7*4*12*100*20, 60*60*24*7*4*12*100
];
var seconds = (+new Date() - time) / 1000,
token = "ago",
list_choice = 1;
if (seconds == 0) {
return "Just now";
}
if (seconds < 0) {
seconds = Math.abs(seconds);
token = "from now";
list_choice = 2;
}
var i = 0,
format: any[];
while ((format = time_formats[i++]))
if (seconds < format[0]) {
if (typeof format[2] == "string") return format[list_choice];
else return Math.floor(seconds / format[2]) + " " + format[1] + " " + token;
}
return time;
};
export const formatDateDistance = (date: string | Date) => {
const today = new Date();
const eventDate = new Date(date);
const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
const days = Math.ceil(timeDiff / (1000 * 3600 * 24));
if (days < 1) {
const hours = Math.ceil(timeDiff / (1000 * 3600));
if (hours < 1) {
const minutes = Math.ceil(timeDiff / (1000 * 60));
if (minutes < 1) {
return "Just now";
} else {
return `${minutes}m`;
}
} else {
return `${hours}h`;
}
} else if (days < 7) {
return `${days}d`;
} else if (days < 30) {
return `${Math.floor(days / 7)}w`;
} else if (days < 365) {
return `${Math.floor(days / 30)}m`;
} else {
return `${Math.floor(days / 365)}y`;
}
};
export const formatLongDateDistance = (date: string | Date) => {
const today = new Date();
const eventDate = new Date(date);
const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
const days = Math.ceil(timeDiff / (1000 * 3600 * 24));
if (days < 1) {
const hours = Math.ceil(timeDiff / (1000 * 3600));
if (hours < 1) {
const minutes = Math.ceil(timeDiff / (1000 * 60));
if (minutes < 1) {
return "Just now";
} else {
return `${minutes} minutes`;
}
} else {
return `${hours} hours`;
}
} else if (days < 7) {
if (days === 1) return `${days} day`;
return `${days} days`;
} else if (days < 30) {
if (Math.floor(days / 7) === 1) return `${Math.floor(days / 7)} week`;
return `${Math.floor(days / 7)} weeks`;
} else if (days < 365) {
if (Math.floor(days / 30) === 1) return `${Math.floor(days / 30)} month`;
return `${Math.floor(days / 30)} months`;
} else {
if (Math.floor(days / 365) === 1) return `${Math.floor(days / 365)} year`;
return `${Math.floor(days / 365)} years`;
}
};
export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => {
if (!date || date === "") return null;
date = new Date(date);
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const day = date.getDate();
const month = months[date.getMonth()];
const year = date.getFullYear();
return isNaN(date.getTime()) ? placeholder ?? "N/A" : ` ${month} ${day}, ${year}`;
};
export const renderShortDate = (date: string | Date, placeholder?: string) => {
if (!date || date === "") return null;
date = new Date(date);
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const day = date.getDate();
const month = months[date.getMonth()];
return isNaN(date.getTime()) ? placeholder ?? "N/A" : `${day} ${month}`;
};
export const renderShortMonthDate = (date: string | Date, placeholder?: string) => {
if (!date || date === "") return null;
date = new Date(date);
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const month = months[date.getMonth()];
const year = date.getFullYear();
return isNaN(date.getTime()) ? placeholder ?? "N/A" : `${month} ${year}`;
};
export const render12HourFormatTime = (date: string | Date): string => {
// Format Time Helpers
/**
* @returns {string} formatted date in the format of hh:mm a or HH:mm
* @description Returns date in 12 hour format if in12HourFormat is true else 24 hour format
* @param {string | Date} date
* @param {boolean} timeFormat (optional) // default 24 hour
* @example renderFormattedTime("2024-01-01 13:00:00") // 13:00
* @example renderFormattedTime("2024-01-01 13:00:00", "12-hour") // 01:00 PM
*/
export const renderFormattedTime = (date: string | Date, timeFormat: "12-hour" | "24-hour" = "24-hour"): string => {
if (!date || date === "") return "";
date = new Date(date);
let hours = date.getHours();
const minutes = date.getMinutes();
let period = "AM";
if (hours >= 12) {
period = "PM";
if (hours > 12) hours -= 12;
// Parse the date to check if it is valid
const parsedDate = new Date(date);
// Check if the parsed date is valid
if (!isValid(parsedDate)) return ""; // Return empty string for invalid dates
// Format the date in 12 hour format if in12HourFormat is true
if (timeFormat === "12-hour") {
const formattedTime = format(parsedDate, "hh:mm a");
return formattedTime;
}
return hours + ":" + (minutes < 10 ? `0${minutes}` : minutes) + " " + period;
// Format the date in 24 hour format
const formattedTime = format(parsedDate, "HH:mm");
return formattedTime;
};
export const render24HourFormatTime = (date: string | Date): string => {
if (!date || date === "") return "";
date = new Date(date);
const hours = date.getHours();
let minutes: any = date.getMinutes();
// add leading zero to single digit minutes
if (minutes < 10) minutes = "0" + minutes;
return hours + ":" + minutes;
};
export const isDateRangeValid = (startDate: string, endDate: string) => new Date(startDate) < new Date(endDate);
export const isDateGreaterThanToday = (dateStr: string) => {
const date = new Date(dateStr);
const today = new Date();
return date > today;
};
export const renderLongDateFormat = (dateString: string | Date) => {
const date = new Date(dateString);
const day = date.getDate();
const year = date.getFullYear();
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const monthIndex = date.getMonth();
const monthName = monthNames[monthIndex];
const suffixes = ["st", "nd", "rd", "th"];
let suffix = "";
if (day === 1 || day === 21 || day === 31) {
suffix = suffixes[0];
} else if (day === 2 || day === 22) {
suffix = suffixes[1];
} else if (day === 3 || day === 23) {
suffix = suffixes[2];
} else {
suffix = suffixes[3];
}
return `${day}${suffix} ${monthName} ${year}`;
};
/**
*
* @returns {Array} Array of time objects with label and value as keys
*/
export const getTimestampAfterCurrentTime = (): Array<{
label: string;
value: Date;
}> => {
const current = new Date();
const time = [];
for (let i = 0; i < 24; i++) {
const newTime = new Date(current.getTime() + i * 60 * 60 * 1000);
time.push({
label: newTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
value: newTime,
});
}
return time;
};
/**
* @returns {Array} Array of date objects with label and value as keys
* @description Returns an array of date objects starting from current date to 7 days after
*/
export const getDatesAfterCurrentDate = (): Array<{
label: string;
value: Date;
}> => {
const current = new Date();
const date = [];
for (let i = 0; i < 7; i++) {
const newDate = new Date(Math.round(current.getTime() / (30 * 60 * 1000)) * 30 * 60 * 1000);
date.push({
label: newDate.toLocaleDateString([], {
day: "numeric",
month: "long",
year: "numeric",
}),
value: newDate,
});
}
return date;
};
/**
* @returns {boolean} true if date is valid
* @description Returns true if date is valid
* @param {string} date
* @example checkIfStringIsDate("2021-01-01") // true
* @example checkIfStringIsDate("2021-01-32") // false
*/
export const checkIfStringIsDate = (date: string): boolean => new Date(date).toString() !== "Invalid Date";
// return an array of dates starting from 12:00 to 23:30 with 30 minutes interval as dates
export const getDatesWith30MinutesInterval = (): Array<Date> => {
const dates = [];
const current = new Date();
for (let i = 0; i < 24; i++) {
const newDate = new Date(current.getTime() + i * 60 * 60 * 1000);
dates.push(newDate);
}
return dates;
};
export const getAllTimeIn30MinutesInterval = (): Array<{
label: string;
value: string;
}> => [
{ label: "12:00", value: "12:00" },
{ label: "12:30", value: "12:30" },
{ label: "01:00", value: "01:00" },
{ label: "01:30", value: "01:30" },
{ label: "02:00", value: "02:00" },
{ label: "02:30", value: "02:30" },
{ label: "03:00", value: "03:00" },
{ label: "03:30", value: "03:30" },
{ label: "04:00", value: "04:00" },
{ label: "04:30", value: "04:30" },
{ label: "05:00", value: "05:00" },
{ label: "05:30", value: "05:30" },
{ label: "06:00", value: "06:00" },
{ label: "06:30", value: "06:30" },
{ label: "07:00", value: "07:00" },
{ label: "07:30", value: "07:30" },
{ label: "08:00", value: "08:00" },
{ label: "08:30", value: "08:30" },
{ label: "09:00", value: "09:00" },
{ label: "09:30", value: "09:30" },
{ label: "10:00", value: "10:00" },
{ label: "10:30", value: "10:30" },
{ label: "11:00", value: "11:00" },
{ label: "11:30", value: "11:30" },
];
// Date Difference Helpers
/**
* @returns {number} total number of days in range
* @description Returns total number of days in range
@ -387,25 +86,73 @@ export const getAllTimeIn30MinutesInterval = (): Array<{
* @param {boolean} inclusive
* @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8
*/
export const findTotalDaysInRange = (startDate: Date | string, endDate: Date | string, inclusive: boolean): number => {
export const findTotalDaysInRange = (
startDate: Date | string,
endDate: Date | string,
inclusive: boolean = true
): number => {
if (!startDate || !endDate) return 0;
startDate = new Date(startDate);
endDate = new Date(endDate);
// find number of days between startDate and endDate
const diffInTime = endDate.getTime() - startDate.getTime();
const diffInDays = Math.floor(diffInTime / (1000 * 3600 * 24));
// if inclusive is true, add 1 to diffInDays
if (inclusive) return diffInDays + 1;
return diffInDays;
// Parse the dates to check if they are valid
const parsedStartDate = new Date(startDate);
const parsedEndDate = new Date(endDate);
// Check if the parsed dates are valid before calculating the difference
if (!isValid(parsedStartDate) || !isValid(parsedEndDate)) return 0; // Return 0 for invalid dates
// Calculate the difference in days
const diffInDays = differenceInDays(parsedEndDate, parsedStartDate);
// Return the difference in days based on inclusive flag
return inclusive ? diffInDays + 1 : diffInDays;
};
export const getUserTimeZoneFromWindow = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
/**
* @returns {number} number of days left from today
* @description Returns number of days left from today
* @param {string | Date} date
* @param {boolean} inclusive (optional) // default true
* @example findHowManyDaysLeft("2024-01-01") // 3
*/
export const findHowManyDaysLeft = (date: string | Date, inclusive: boolean = true): number => {
if (!date) return 0;
// Pass the date to findTotalDaysInRange function to find the total number of days in range from today
return findTotalDaysInRange(new Date(), date, inclusive);
};
// Time Difference Helpers
/**
* @returns {string} formatted date in the form of amount of time passed since the event happened
* @description Returns time passed since the event happened
* @param {string | Date} time
* @example calculateTimeAgo("2023-01-01") // 1 year ago
*/
export const calculateTimeAgo = (time: string | number | Date | null): string => {
if (!time) return "";
// Parse the time to check if it is valid
const parsedTime = typeof time === "string" || typeof time === "number" ? parseISO(String(time)) : time;
if (!parsedTime) return ""; // Return empty string for invalid dates
// Format the time in the form of amount of time passed since the event happened
const distance = formatDistanceToNow(parsedTime, { addSuffix: true });
return distance;
};
// Date Validation Helpers
/**
* @returns {string} boolean value depending on whether the date is greater than today
* @description Returns boolean value depending on whether the date is greater than today
* @param {string} dateStr
* @example isDateGreaterThanToday("2024-01-01") // true
*/
export const isDateGreaterThanToday = (dateStr: string): boolean => {
// Return false if dateStr is not present
if (!dateStr) return false;
// Parse the date to check if it is valid
const date = parseISO(dateStr);
const today = new Date();
// Check if the parsed date is valid
if (!isValid(date)) return false; // Return false for invalid dates
// Return true if the date is greater than today
return isAfter(date, today);
};
// Week Related Helpers
/**
* @returns {number} week number of date
* @description Returns week number of date
@ -414,73 +161,11 @@ export const getUserTimeZoneFromWindow = () => Intl.DateTimeFormat().resolvedOpt
*/
export const getWeekNumberOfDate = (date: Date): number => {
const currentDate = new Date(date);
// Adjust the starting day to Sunday (0) instead of Monday (1)
const startDate = new Date(currentDate.getFullYear(), 0, 1);
// Calculate the number of days between currentDate and startDate
const days = Math.floor((currentDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000));
// Adjust the calculation for weekNumber
const weekNumber = Math.ceil((days + 1) / 7);
return weekNumber;
};
/**
* @returns {Date} first date of week
* @description Returns week number of date
* @param {Date} date
* @example getFirstDateOfWeek(35, 2023) // 2023-08-27T00:00:00.000Z
*/
export const getFirstDateOfWeek = (date: Date): Date => {
const year = date.getFullYear();
const weekNumber = getWeekNumberOfDate(date);
const januaryFirst: Date = new Date(year, 0, 1); // January is month 0
const daysToAdd: number = (weekNumber - 1) * 7; // Subtract 1 from the week number since weeks are 0-indexed
const firstDateOfWeek: Date = new Date(januaryFirst);
firstDateOfWeek.setDate(januaryFirst.getDate() + daysToAdd);
// Adjust the date to Sunday (week start)
const dayOfWeek: number = firstDateOfWeek.getDay();
firstDateOfWeek.setDate(firstDateOfWeek.getDate() - dayOfWeek); // Move back to Sunday
return firstDateOfWeek;
};
/**
* @returns {string} formatted date in the format of MMM dd, yyyy
* @description Returns date in the formatted format
* @param {Date | string} date
* @example renderFormattedDate("2023-01-01") // Jan 01, 2023
*/
export const renderFormattedDate = (date: string | Date): string => {
if (!date) return "";
date = new Date(date);
const formattedDate = format(date, "MMM dd, yyyy");
return formattedDate;
};
/**
* @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("2023-01-01") // "2023-01-01"
*/
export const renderFormattedPayloadDate = (date: Date | string): string | null => {
if (!date) return null;
var d = new Date(date),
month = "" + (d.getMonth() + 1),
day = "" + d.getDate(),
year = d.getFullYear();
if (month.length < 2) month = "0" + month;
if (day.length < 2) day = "0" + day;
return [year, month, day].join("-");
};

View File

@ -20,7 +20,7 @@ import { Spinner } from "@plane/ui";
// assets
import emptyPage from "public/empty-state/page.svg";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { NextPageWithLayout } from "types/app";
import { IPage, IIssue } from "types";
@ -279,7 +279,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
mutatePageDetailsHelper(
pageService.archivePage(workspaceSlug.toString(), projectId.toString(), pageId.toString()),
{
archived_at: renderDateFormat(new Date()),
archived_at: renderFormattedPayloadDate(new Date()),
},
["description_html"],
() =>

View File

@ -15,7 +15,7 @@ import { ExternalLinkIcon, Loader } from "@plane/ui";
// fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys";
// helper
import { timeAgo } from "helpers/date-time.helper";
import { calculateTimeAgo } from "helpers/date-time.helper";
// type
import { NextPageWithLayout } from "types/app";
@ -70,7 +70,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
: activityItem.actor_detail.display_name}
</div>
<p className="mt-0.5 text-xs text-custom-text-200">
Commented {timeAgo(activityItem.created_at)}
Commented {calculateTimeAgo(activityItem.created_at)}
</p>
</div>
<div className="issue-comments-section p-0">
@ -165,7 +165,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
<div className="flex gap-1 truncate">
{message}{" "}
<span className="flex-shrink-0 whitespace-nowrap">
{timeAgo(activityItem.created_at)}
{calculateTimeAgo(activityItem.created_at)}
</span>
</div>
</div>

View File

@ -1,11 +1,10 @@
import { observable, action, makeObservable, runInAction, computed } from "mobx";
// helpers
import { generateCalendarData } from "helpers/calendar.helper";
import { getWeekNumberOfDate } from "helpers/date-time.helper";
// types
import { RootStore } from "./root";
import { ICalendarPayload, ICalendarWeek } from "components/issues";
import { getWeekNumberOfDate } from "helpers/date-time.helper";
export interface ICalendarStore {
calendarFilters: {

View File

@ -10,7 +10,7 @@ import { IIssueResponse } from "../types";
// constants
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
export interface IIssueBaseStore {
groupedIssues(
@ -201,7 +201,7 @@ export class IssueBaseStore implements IIssueBaseStore {
if (Array.isArray(value)) {
if (value.length) return value;
else return ["None"];
} else if (isDate) return [renderDateFormat(value) || "None"];
} else if (isDate) return [renderFormattedPayloadDate(value ?? "") || "None"];
else return [value || "None"];
}
}

View File

@ -6,7 +6,7 @@ import isThisWeek from "date-fns/isThisWeek";
import { ProjectService } from "services/project";
import { PageService } from "services/page.service";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { RootStore } from "./root";
import { IPage, IRecentPages } from "types";
@ -329,7 +329,7 @@ export class PageStore implements IPageStore {
...this.archivedPages,
[projectId]: [
...this.archivedPages[projectId],
{ ...archivedPage, archived_at: renderDateFormat(new Date()) },
{ ...archivedPage, archived_at: renderFormattedPayloadDate(new Date()) },
],
};
});