-
- {dueDateIcon.iconName}
-
- {renderFullDate(issueDetails.target_date)}
+
+
+ {renderFormattedDate(issueDetails.target_date)}
) : (
Empty
diff --git a/space/constants/issue.ts b/space/constants/issue.ts
index fb9c78fcd..f96eb3e0e 100644
--- a/space/constants/issue.ts
+++ b/space/constants/issue.ts
@@ -1,13 +1,13 @@
-// interfaces
+// types
+import { TIssuePriorities } from "@plane/types";
import {
TIssueLayout,
TIssueLayoutViews,
TIssueFilterKeys,
- TIssueFilterPriority,
TIssueFilterPriorityObject,
TIssueFilterState,
TIssueFilterStateObject,
-} from "types/issue";
+} from "@/types/issue";
// issue filters
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]> } = {
@@ -75,7 +75,7 @@ export const issuePriorityFilters: TIssueFilterPriorityObject[] = [
},
];
-export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFilterPriorityObject | undefined => {
+export const issuePriorityFilter = (priorityKey: TIssuePriorities): TIssueFilterPriorityObject | undefined => {
const currentIssuePriority: TIssueFilterPriorityObject | undefined =
issuePriorityFilters && issuePriorityFilters.length > 0
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
diff --git a/space/constants/state.ts b/space/constants/state.ts
new file mode 100644
index 000000000..b0fd622be
--- /dev/null
+++ b/space/constants/state.ts
@@ -0,0 +1,37 @@
+import { TStateGroups } from "@plane/types";
+
+export const STATE_GROUPS: {
+ [key in TStateGroups]: {
+ key: TStateGroups;
+ label: string;
+ color: string;
+ };
+} = {
+ backlog: {
+ key: "backlog",
+ label: "Backlog",
+ color: "#d9d9d9",
+ },
+ unstarted: {
+ key: "unstarted",
+ label: "Unstarted",
+ color: "#3f76ff",
+ },
+ started: {
+ key: "started",
+ label: "Started",
+ color: "#f59e0b",
+ },
+ completed: {
+ key: "completed",
+ label: "Completed",
+ color: "#16a34a",
+ },
+ cancelled: {
+ key: "cancelled",
+ label: "Canceled",
+ color: "#dc2626",
+ },
+};
+
+export const ARCHIVABLE_STATE_GROUPS = [STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key];
diff --git a/space/helpers/date-time.helper.ts b/space/helpers/date-time.helper.ts
index f19a5358b..3930bcb83 100644
--- a/space/helpers/date-time.helper.ts
+++ b/space/helpers/date-time.helper.ts
@@ -1,3 +1,6 @@
+import { format, isValid } from "date-fns";
+import isNumber from "lodash/isNumber";
+
export const timeAgo = (time: any) => {
switch (typeof time) {
case "number":
@@ -14,24 +17,43 @@ export const timeAgo = (time: any) => {
};
/**
- * @description Returns date and month, if date is of the current year
- * @description Returns date, month adn year, if date is of a different year than current
- * @param {string} date
- * @example renderFullDate("2023-01-01") // 1 Jan
- * @example renderFullDate("2021-01-01") // 1 Jan, 2021
+ * This method returns a date from string of type yyyy-mm-dd
+ * This method is recommended to use instead of new Date() as this does not introduce any timezone offsets
+ * @param date
+ * @returns date or undefined
*/
+export const getDate = (date: string | Date | undefined | null): Date | undefined => {
+ try {
+ if (!date || date === "") return;
-export const renderFullDate = (date: string): string => {
- if (!date) return "";
+ if (typeof date !== "string" && !(date instanceof String)) return date;
- const months: string[] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ const [yearString, monthString, dayString] = date.substring(0, 10).split("-");
+ const year = parseInt(yearString);
+ const month = parseInt(monthString);
+ const day = parseInt(dayString);
+ if (!isNumber(year) || !isNumber(month) || !isNumber(day)) return;
- const currentDate: Date = new Date();
- const [year, month, day]: number[] = date.split("-").map(Number);
-
- const formattedMonth: string = months[month - 1];
- const formattedDay: string = day < 10 ? `0${day}` : day.toString();
-
- if (currentDate.getFullYear() === year) return `${formattedDay} ${formattedMonth}`;
- else return `${formattedDay} ${formattedMonth}, ${year}`;
+ return new Date(year, month - 1, day);
+ } catch (e) {
+ return undefined;
+ }
+};
+
+/**
+ * @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 | undefined | null): string | null => {
+ // Parse the date to check if it is valid
+ const parsedDate = getDate(date);
+ // return if undefined
+ if (!parsedDate) return null;
+ // 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;
};
diff --git a/space/helpers/issue.helper.ts b/space/helpers/issue.helper.ts
new file mode 100644
index 000000000..a5159edef
--- /dev/null
+++ b/space/helpers/issue.helper.ts
@@ -0,0 +1,30 @@
+import { differenceInCalendarDays } from "date-fns";
+// types
+import { TStateGroups } from "@plane/types";
+// constants
+import { STATE_GROUPS } from "@/constants/state";
+// helpers
+import { getDate } from "@/helpers/date-time.helper";
+
+/**
+ * @description check if the issue due date should be highlighted
+ * @param date
+ * @param stateGroup
+ * @returns boolean
+ */
+export const shouldHighlightIssueDueDate = (
+ date: string | Date | null,
+ stateGroup: TStateGroups | undefined
+): boolean => {
+ if (!date || !stateGroup) return false;
+ // if the issue is completed or cancelled, don't highlight the due date
+ if ([STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateGroup)) return false;
+
+ const parsedDate = getDate(date);
+ if (!parsedDate) return false;
+
+ const targetDateDistance = differenceInCalendarDays(parsedDate, new Date());
+
+ // if the issue is overdue, highlight the due date
+ return targetDateDistance <= 0;
+};
diff --git a/space/package.json b/space/package.json
index e3dadbff8..0932d7abf 100644
--- a/space/package.json
+++ b/space/package.json
@@ -26,6 +26,7 @@
"@sentry/nextjs": "^8",
"axios": "^1.3.4",
"clsx": "^2.0.0",
+ "date-fns": "^3.6.0",
"dompurify": "^3.0.11",
"dotenv": "^16.3.1",
"js-cookie": "^3.0.1",
diff --git a/space/store/issue.store.ts b/space/store/issue.store.ts
index 4595c6a1d..4f2d845b5 100644
--- a/space/store/issue.store.ts
+++ b/space/store/issue.store.ts
@@ -1,9 +1,11 @@
import { observable, action, makeObservable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
+// types
+import { IStateLite } from "@plane/types";
// services
import IssueService from "@/services/issue.service";
// types
-import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
+import { IIssue, IIssueLabel } from "@/types/issue";
// store
import { RootStore } from "./root.store";
@@ -12,7 +14,7 @@ export interface IIssueStore {
error: any;
// observables
issues: IIssue[];
- states: IIssueState[];
+ states: IStateLite[];
labels: IIssueLabel[];
// filter observables
filteredStates: string[];
@@ -29,7 +31,7 @@ export class IssueStore implements IIssueStore {
loader: boolean = false;
error: any | null = null;
// observables
- states: IIssueState[] = [];
+ states: IStateLite[] = [];
labels: IIssueLabel[] = [];
issues: IIssue[] = [];
// filter observables
diff --git a/space/store/publish/publish.store.ts b/space/store/publish/publish.store.ts
index 066da6390..c90eb5bf9 100644
--- a/space/store/publish/publish.store.ts
+++ b/space/store/publish/publish.store.ts
@@ -1,8 +1,10 @@
import { observable, makeObservable, computed } from "mobx";
+// types
+import { IWorkspaceLite } from "@plane/types";
// store types
import { RootStore } from "@/store/root.store";
// types
-import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project";
+import { TProjectDetails, TViewDetails } from "@/types/project";
import { TPublishEntityType, TPublishSettings } from "@/types/publish";
export interface IPublishStore extends TPublishSettings {
@@ -31,7 +33,7 @@ export class PublishStore implements IPublishStore {
view_props: TViewDetails | undefined;
votes: boolean;
workspace: string | undefined;
- workspace_detail: TWorkspaceDetails | undefined;
+ workspace_detail: IWorkspaceLite | undefined;
constructor(
private store: RootStore,
diff --git a/space/types/issue.d.ts b/space/types/issue.d.ts
index 00bc740d5..3f2c41451 100644
--- a/space/types/issue.d.ts
+++ b/space/types/issue.d.ts
@@ -1,14 +1,19 @@
+import { IStateLite, IWorkspaceLite, TIssuePriorities } from "@plane/types";
+
export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
export type TIssueLayoutOptions = {
[key in TIssueLayout]: boolean;
};
export type TIssueLayoutViews = {
- [key in TIssueLayout]: { title: string; icon: string; className: string };
+ [key in TIssueLayout]: {
+ title: string;
+ icon: string;
+ className: string;
+ };
};
-export type TIssueFilterPriority = "urgent" | "high" | "medium" | "low" | "none";
export type TIssueFilterPriorityObject = {
- key: TIssueFilterPriority;
+ key: TIssuePriorities;
title: string;
className: string;
icon: string;
@@ -30,7 +35,7 @@ export type TDisplayFilters = {
export type TFilters = {
state: TIssueFilterState[];
- priority: TIssueFilterPriority[];
+ priority: TIssuePriorities[];
labels: string[];
};
@@ -44,7 +49,7 @@ export type TIssueQueryFilters = Partial
;
export type TIssueQueryFiltersParams = Partial>;
export type TIssuesResponse = {
- states: IIssueState[];
+ states: IStateLite[];
labels: IIssueLabel[];
issues: IIssue[];
};
@@ -74,13 +79,6 @@ export interface IIssue {
export type IPeekMode = "side" | "modal" | "full";
-export interface IIssueState {
- id: string;
- name: string;
- group: TIssueGroupKey;
- color: string;
-}
-
export interface IIssueLabel {
id: string;
name: string;
@@ -121,7 +119,7 @@ export interface Comment {
updated_at: Date;
updated_by: string;
workspace: string;
- workspace_detail: WorkspaceDetail;
+ workspace_detail: IWorkspaceLite;
}
export interface IIssueReaction {
@@ -182,52 +180,8 @@ export interface ProjectDetail {
description: string;
}
-export interface WorkspaceDetail {
- name: string;
- slug: string;
- id: string;
-}
-
-export interface IssueDetailType {
- [issueId: string]: {
- issue: IIssue;
- comments: Comment[];
- reactions: any[];
- votes: any[];
- };
-}
-
-export type TIssueGroupByOptions = "state" | "priority" | "labels" | null;
-
-export type TIssueParams = "priority" | "state" | "labels";
-
export interface IIssueFilterOptions {
state?: string[] | null;
labels?: string[] | null;
priority?: string[] | null;
}
-
-// issues
-export interface IGroupedIssues {
- [group_id: string]: string[];
-}
-
-export interface ISubGroupedIssues {
- [sub_grouped_id: string]: {
- [group_id: string]: string[];
- };
-}
-
-export type TUnGroupedIssues = string[];
-
-export interface IIssueResponse {
- [issue_id: string]: IIssue;
-}
-
-export type TLoader = "init-loader" | "mutation" | undefined;
-
-export interface ViewFlags {
- enableQuickAdd: boolean;
- enableIssueCreation: boolean;
- enableInlineEditing: boolean;
-}
diff --git a/space/types/project.d.ts b/space/types/project.d.ts
index 3ef979c05..c0ae02583 100644
--- a/space/types/project.d.ts
+++ b/space/types/project.d.ts
@@ -1,11 +1,5 @@
import { TLogoProps } from "@plane/types";
-export type TWorkspaceDetails = {
- name: string;
- slug: string;
- id: string;
-};
-
export type TViewDetails = {
list: boolean;
gantt: boolean;
diff --git a/space/types/publish.d.ts b/space/types/publish.d.ts
index d9c1387c4..726412978 100644
--- a/space/types/publish.d.ts
+++ b/space/types/publish.d.ts
@@ -1,4 +1,5 @@
-import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "./project";
+import { IWorkspaceLite } from "@plane/types";
+import { TProjectDetails, TViewDetails } from "./project";
export type TPublishEntityType = "project";
@@ -19,5 +20,5 @@ export type TPublishSettings = {
view_props: TViewDetails | undefined;
votes: boolean;
workspace: string | undefined;
- workspace_detail: TWorkspaceDetails | undefined;
+ workspace_detail: IWorkspaceLite | undefined;
};
diff --git a/yarn.lock b/yarn.lock
index 05aec74bf..a2f58cf6a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6281,6 +6281,11 @@ date-fns@^2.30.0:
dependencies:
"@babel/runtime" "^7.21.0"
+date-fns@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
+ integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
+
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -10681,7 +10686,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-"prettier-fallback@npm:prettier@^3":
+"prettier-fallback@npm:prettier@^3", prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
version "3.3.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf"
integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==
@@ -10708,11 +10713,6 @@ prettier@^2.8.8:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
-prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf"
- integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==
-
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@@ -12112,16 +12112,7 @@ string-argv@~0.3.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12217,14 +12208,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13648,16 +13632,7 @@ workbox-window@6.6.1, workbox-window@^6.5.4:
"@types/trusted-types" "^2.0.2"
workbox-core "6.6.1"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==