mirror of
https://github.com/makeplane/plane.git
synced 2025-12-23 15:19:37 +01:00
feat: language support (#6472)
* chore: ln support modules constants * fix: translation key * chore: empty state refactor (#6404) * chore: asset path helper hook added * chore: detailed and simple empty state component added * chore: section empty state component added * chore: language translation for all empty states * chore: new empty state implementation * improvement: add more translations * improvement: user permissions and workspace draft empty state * chore: update translation structure * chore: inbox empty states * chore: disabled project features empty state * chore: active cycle progress empty state * chore: notification empty state * chore: connections translation * chore: issue comment, relation, bulk delete, and command k empty state translation * chore: project pages empty state and translations * chore: project module and view related empty state * chore: remove project draft related empty state * chore: project cycle, views and archived issues empty state * chore: project cycles related empty state * chore: project settings empty state * chore: profile issue and acitivity empty state * chore: workspace settings realted constants * chore: stickies and home widgets empty state * chore: remove all reference to deprecated empty state component and constnats * chore: add support to ignore theme in resolved asset path hook * chore: minor updates * fix: build errors --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> * fix: language support fo profile (#6461) * fix: ln support fo profile * fix: merge changes * fix: merge changes * [WEB-3165]feat: language support for issues (#6452) * * chore: moved issue constants to packages * chore: restructured issue constants * improvement: added translations to issue constants * chore: updated translation structure * * chore: updated chinese, spanish and french translation * chore: updated translation for issues mobile header * chore: updated spanish translation * chore: removed translation for issue priorities * fix: build errors * chore: minor updates --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: migrated filters.ts to packages (#6459) Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: workspace drafts constant moved to plane constant package * feat: home language support without stickies (#6443) * feat: home language support without stickies * fix: home sidebar * fix: added missing keys * fix: show all btn * fix: recents empty state * chore: translation update * feat: workspace constant language support and refactor (#6462) * chore: workspace constant language support and refactor * chore: workspace constant language support and refactor * chore: code refactor * chore: code refactor * merge conflict * chore: code refactor --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: tab indices constant moved to plane package (#6464) * chore: notification language support and refactor * chore: ln support for inbox constants (#6432) * chore: ln support for inbox constants * fix: snooze duration * fix: enum * fix: translation keys * fix: inbox status icon * fix: status icon * fix: naming --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * fix: ln support for views constants (#6431) * fix: ln support for views constants * fix: added translation * fix: translation keys * fix: access * chore: code refactor * chore: ln support workspace projects constants (#6429) * chore: ln support workspace projects constants * fix: translation key * fix: removed state translation * fix: removed state translation * fi: added translations * Chore: theme language support and refactor (#6465) * chore: themes language support and refactor * chore: theme language support and refactor * fix * [WEB-3173] chore: language support for cycles constant file (#6415) * chore: ln support for cycles constant file * fix: added chinese * fix: lint * fix: translation key * fix: build errors * minor updates * chore: minor translation update * chore: minor translation update * refactor: move labels contants to packages * refactor: move swr, file and error related constants to packages * chore: timezones constant moved to plane package * chore: metadata constant code refactor * chore: code refactor * fix: dashboard constants moved * chore: code refactor (#6478) * refactor: spreadsheet constants * chore: drafts language support (#6485) * chore: workspace drafts language support * chore: code refactor * feat: ln support for notifications (#6486) * feat: ln support for notifications * fix: translations * * refactor: moved page constants to packages (#6480) * fix: removed use-client * chore: removed unnecessary commnets * chore: workspace draft language support (#6490) * chore: workspace drafts language support * chore: code refactor * chore: draft language support * Feat constant event tracker (#6479) * fix: event tracjer constants * fix: constants event tracker * feat: language translation - projects list (#6493) * feat: added translation to projects list page * chore: restructured translation file * chore: module language support (#6499) * chore: module language support added * chore: code refactor * chore: workspace views language support (#6492) * chore: workspace views language support * chore: code refactor * feat: custom analytics language support (#6494) * feat: custom analytics language support * fix: key * fix: refactoring --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> * chore: minor improvements * feat: language support for intake (#6498) * feat: language support for intake * fix: key name * refactor: authentications related translations * feat: language support issues (#6501) * enhancement: added translations for issue list view * chore: added translations for issue detail widgets * chore: added missing translations * chore: modified issue to work items * chore: updated translations * Feat: workspace settings language support (#6508) * feat: language support for workspace settings * fix: lint * fix: export title * chore project settings language support (#6502) * chore: project settings language support * chore: code refactor * refactor: workspace creation related translations * chore: renamed issues to work items * fix: build errors * fix: lint * chore: modified translations * chore: remove duplicate * improvement: french translation * chore: chinese translation improvement * fix: japanese translations * chore: added spanish translation * minor improvements * fix: miscelleous language translations * fix: clear_all key * fix: moved user permission constants (#6516) * feat: language support for issues (#6513) * chore: added language support to issue detail widgets * improvement: added translation for issue detail * enhancement: added language trasnlation to issue layouts * chore: translation improvement (#6518) * feat: language support description (#6519) * enhancement: added language support for description * fix: updated keys * chore: renamed issue to work item (#6522) * chore: replace missing issue occurances to work items * fix: build errors * minor improvements * fix: profile links * Feat ln cycles (#6528) * feat: added language support for cycles * feat: added language support for cycles * chore: added core.json * fix: translation keys * fix: translation keys (#6530) * fix: changed sidebar keys * fix: removed extras * fix: updated keys * chore: optimize translation imports * fix: updated keys (#6534) * fix: updated keys * fix-sub work items toasts * chore: add missing translation and minor fixes * chore: code refactor * fix: language support keys (#6553) * minor improvements * minor fixes * fix: remove lucide import from constants package * chore: regenerate all translations * chore: addded chinese and japanese translation files * chore: remove all from translations * fix: added member * fix: language support keys (#6558) * fix: renamed keys * fix: space app * chore: renamed issues to work items * chore: update site manifest * chore: updated translations * fix: lang keys * chore: update translations --------- Co-authored-by: gakshita <akshitagoyal1516@gmail.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Co-authored-by: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so> Co-authored-by: Vamsi krishna <matalav55@gmail.com> Co-authored-by: Vamsi Krishna <46787868+vamsikrishnamathala@users.noreply.github.com>
This commit is contained in:
@@ -7,15 +7,15 @@ import { DefaultLayout } from "@/layouts/default-layout";
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||||
description:
|
description:
|
||||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
|
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||||
description:
|
description:
|
||||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
|
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||||
url: "https://plane.so/",
|
url: "https://plane.so/",
|
||||||
},
|
},
|
||||||
keywords:
|
keywords:
|
||||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
|
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
||||||
twitter: {
|
twitter: {
|
||||||
site: "@planepowers",
|
site: "@planepowers",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
import { TXAxisValues, TYAxisValues } from "@plane/types";
|
import { TXAxisValues, TYAxisValues } from "@plane/types";
|
||||||
|
|
||||||
export const ANALYTICS_TABS = [
|
export const ANALYTICS_TABS = [
|
||||||
{ key: "scope_and_demand", title: "Scope and Demand" },
|
{
|
||||||
{ key: "custom", title: "Custom Analytics" },
|
key: "scope_and_demand",
|
||||||
|
i18n_title: "workspace_analytics.tabs.scope_and_demand",
|
||||||
|
},
|
||||||
|
{ key: "custom", i18n_title: "workspace_analytics.tabs.custom" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
|
export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
|
||||||
@@ -62,7 +65,7 @@ export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] =
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
value: "issue_count",
|
value: "issue_count",
|
||||||
label: "Issue Count",
|
label: "Work item Count",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "estimate",
|
value: "estimate",
|
||||||
|
|||||||
@@ -1,56 +1,40 @@
|
|||||||
// types
|
// types
|
||||||
import { TCycleLayoutOptions, TCycleTabOptions } from "@plane/types";
|
|
||||||
|
|
||||||
export const CYCLE_TABS_LIST: {
|
|
||||||
key: TCycleTabOptions;
|
|
||||||
name: string;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
key: "active",
|
|
||||||
name: "Active",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "all",
|
|
||||||
name: "All",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const CYCLE_STATUS: {
|
export const CYCLE_STATUS: {
|
||||||
label: string;
|
i18n_label: string;
|
||||||
value: "current" | "upcoming" | "completed" | "draft";
|
value: "current" | "upcoming" | "completed" | "draft";
|
||||||
title: string;
|
i18n_title: string;
|
||||||
color: string;
|
color: string;
|
||||||
textColor: string;
|
textColor: string;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
label: "day left",
|
i18n_label: "project_cycles.status.days_left",
|
||||||
value: "current",
|
value: "current",
|
||||||
title: "In progress",
|
i18n_title: "project_cycles.status.in_progress",
|
||||||
color: "#F59E0B",
|
color: "#F59E0B",
|
||||||
textColor: "text-amber-500",
|
textColor: "text-amber-500",
|
||||||
bgColor: "bg-amber-50",
|
bgColor: "bg-amber-50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Yet to start",
|
i18n_label: "project_cycles.status.yet_to_start",
|
||||||
value: "upcoming",
|
value: "upcoming",
|
||||||
title: "Yet to start",
|
i18n_title: "project_cycles.status.yet_to_start",
|
||||||
color: "#3F76FF",
|
color: "#3F76FF",
|
||||||
textColor: "text-blue-500",
|
textColor: "text-blue-500",
|
||||||
bgColor: "bg-indigo-50",
|
bgColor: "bg-indigo-50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Completed",
|
i18n_label: "project_cycles.status.completed",
|
||||||
value: "completed",
|
value: "completed",
|
||||||
title: "Completed",
|
i18n_title: "project_cycles.status.completed",
|
||||||
color: "#16A34A",
|
color: "#16A34A",
|
||||||
textColor: "text-green-600",
|
textColor: "text-green-600",
|
||||||
bgColor: "bg-green-50",
|
bgColor: "bg-green-50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Draft",
|
i18n_label: "project_cycles.status.draft",
|
||||||
value: "draft",
|
value: "draft",
|
||||||
title: "Draft",
|
i18n_title: "project_cycles.status.draft",
|
||||||
color: "#525252",
|
color: "#525252",
|
||||||
textColor: "text-custom-text-300",
|
textColor: "text-custom-text-300",
|
||||||
bgColor: "bg-custom-background-90",
|
bgColor: "bg-custom-background-90",
|
||||||
92
packages/constants/src/dashboard.ts
Normal file
92
packages/constants/src/dashboard.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// types
|
||||||
|
import { TIssuesListTypes } from "@plane/types";
|
||||||
|
|
||||||
|
export enum EDurationFilters {
|
||||||
|
NONE = "none",
|
||||||
|
TODAY = "today",
|
||||||
|
THIS_WEEK = "this_week",
|
||||||
|
THIS_MONTH = "this_month",
|
||||||
|
THIS_YEAR = "this_year",
|
||||||
|
CUSTOM = "custom",
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter duration options
|
||||||
|
export const DURATION_FILTER_OPTIONS: {
|
||||||
|
key: EDurationFilters;
|
||||||
|
label: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: EDurationFilters.NONE,
|
||||||
|
label: "All time",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EDurationFilters.TODAY,
|
||||||
|
label: "Due today",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EDurationFilters.THIS_WEEK,
|
||||||
|
label: "Due this week",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EDurationFilters.THIS_MONTH,
|
||||||
|
label: "Due this month",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EDurationFilters.THIS_YEAR,
|
||||||
|
label: "Due this year",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EDurationFilters.CUSTOM,
|
||||||
|
label: "Custom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// random background colors for project cards
|
||||||
|
export const PROJECT_BACKGROUND_COLORS = [
|
||||||
|
"bg-gray-500/20",
|
||||||
|
"bg-green-500/20",
|
||||||
|
"bg-red-500/20",
|
||||||
|
"bg-orange-500/20",
|
||||||
|
"bg-blue-500/20",
|
||||||
|
"bg-yellow-500/20",
|
||||||
|
"bg-pink-500/20",
|
||||||
|
"bg-purple-500/20",
|
||||||
|
];
|
||||||
|
|
||||||
|
// assigned and created issues widgets tabs list
|
||||||
|
export const FILTERED_ISSUES_TABS_LIST: {
|
||||||
|
key: TIssuesListTypes;
|
||||||
|
label: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "upcoming",
|
||||||
|
label: "Upcoming",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "overdue",
|
||||||
|
label: "Overdue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "completed",
|
||||||
|
label: "Marked completed",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// assigned and created issues widgets tabs list
|
||||||
|
export const UNFILTERED_ISSUES_TABS_LIST: {
|
||||||
|
key: TIssuesListTypes;
|
||||||
|
label: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "pending",
|
||||||
|
label: "Pending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "completed",
|
||||||
|
label: "Marked completed",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export type TLinkOptions = {
|
||||||
|
userId: string | undefined;
|
||||||
|
};
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum E_ARCHIVE_ERROR_CODES {
|
|
||||||
"INVALID_ARCHIVE_STATE_GROUP" = 4091,
|
|
||||||
"INVALID_ISSUE_START_DATE" = 4101,
|
|
||||||
"INVALID_ISSUE_TARGET_DATE" = 4102,
|
|
||||||
}
|
|
||||||
@@ -104,7 +104,10 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
|||||||
module_id: payload.module_id,
|
module_id: payload.module_id,
|
||||||
archived_at: payload.archived_at,
|
archived_at: payload.archived_at,
|
||||||
state: payload.state,
|
state: payload.state,
|
||||||
view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "",
|
view_id:
|
||||||
|
path?.includes("workspace-views") || path?.includes("views")
|
||||||
|
? path.split("/").pop()
|
||||||
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (eventName === ISSUE_UPDATED) {
|
if (eventName === ISSUE_UPDATED) {
|
||||||
@@ -166,12 +169,12 @@ export const MODULE_LINK_CREATED = "Module link created";
|
|||||||
export const MODULE_LINK_UPDATED = "Module link updated";
|
export const MODULE_LINK_UPDATED = "Module link updated";
|
||||||
export const MODULE_LINK_DELETED = "Module link deleted";
|
export const MODULE_LINK_DELETED = "Module link deleted";
|
||||||
// Issue Events
|
// Issue Events
|
||||||
export const ISSUE_CREATED = "Issue created";
|
export const ISSUE_CREATED = "Work item created";
|
||||||
export const ISSUE_UPDATED = "Issue updated";
|
export const ISSUE_UPDATED = "Work item updated";
|
||||||
export const ISSUE_DELETED = "Issue deleted";
|
export const ISSUE_DELETED = "Work item deleted";
|
||||||
export const ISSUE_ARCHIVED = "Issue archived";
|
export const ISSUE_ARCHIVED = "Work item archived";
|
||||||
export const ISSUE_RESTORED = "Issue restored";
|
export const ISSUE_RESTORED = "Work item restored";
|
||||||
export const ISSUE_OPENED = "Issue opened";
|
export const ISSUE_OPENED = "Work item opened";
|
||||||
// Project State Events
|
// Project State Events
|
||||||
export const STATE_CREATED = "State created";
|
export const STATE_CREATED = "State created";
|
||||||
export const STATE_UPDATED = "State updated";
|
export const STATE_UPDATED = "State updated";
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const SIDEBAR_CLICKED = "Sidenav clicked";
|
|
||||||
@@ -2,3 +2,56 @@ export enum E_SORT_ORDER {
|
|||||||
ASC = "asc",
|
ASC = "asc",
|
||||||
DESC = "desc",
|
DESC = "desc",
|
||||||
}
|
}
|
||||||
|
export const DATE_AFTER_FILTER_OPTIONS = [
|
||||||
|
{
|
||||||
|
name: "1 week from now",
|
||||||
|
value: "1_weeks;after;fromnow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 weeks from now",
|
||||||
|
value: "2_weeks;after;fromnow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 month from now",
|
||||||
|
value: "1_months;after;fromnow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 months from now",
|
||||||
|
value: "2_months;after;fromnow",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DATE_BEFORE_FILTER_OPTIONS = [
|
||||||
|
{
|
||||||
|
name: "1 week ago",
|
||||||
|
value: "1_weeks;before;fromnow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 weeks ago",
|
||||||
|
value: "2_weeks;before;fromnow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 month ago",
|
||||||
|
i18n_name: "date_filters.1_month_ago",
|
||||||
|
value: "1_months;before;fromnow",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PROJECT_CREATED_AT_FILTER_OPTIONS = [
|
||||||
|
{
|
||||||
|
name: "Today",
|
||||||
|
value: "today;custom;custom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Yesterday",
|
||||||
|
value: "yesterday;custom;custom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Last 7 days",
|
||||||
|
value: "last_7_days;custom;custom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Last 30 days",
|
||||||
|
value: "last_30_days;custom;custom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
91
packages/constants/src/inbox.ts
Normal file
91
packages/constants/src/inbox.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types";
|
||||||
|
|
||||||
|
export enum EInboxIssueCurrentTab {
|
||||||
|
OPEN = "open",
|
||||||
|
CLOSED = "closed",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EInboxIssueStatus {
|
||||||
|
PENDING = -2,
|
||||||
|
DECLINED = -1,
|
||||||
|
SNOOZED = 0,
|
||||||
|
ACCEPTED = 1,
|
||||||
|
DUPLICATE = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TInboxIssueCurrentTab = EInboxIssueCurrentTab;
|
||||||
|
export type TInboxIssueStatus = EInboxIssueStatus;
|
||||||
|
export type TInboxIssue = {
|
||||||
|
id: string;
|
||||||
|
status: TInboxIssueStatus;
|
||||||
|
snoozed_till: Date | null;
|
||||||
|
duplicate_to: string | undefined;
|
||||||
|
source: string;
|
||||||
|
issue: TIssue;
|
||||||
|
created_by: string;
|
||||||
|
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INBOX_STATUS: {
|
||||||
|
key: string;
|
||||||
|
status: TInboxIssueStatus;
|
||||||
|
i18n_title: string;
|
||||||
|
i18n_description: () => string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "pending",
|
||||||
|
i18n_title: "inbox_issue.status.pending.title",
|
||||||
|
status: EInboxIssueStatus.PENDING,
|
||||||
|
i18n_description: () => `inbox_issue.status.pending.description`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "declined",
|
||||||
|
i18n_title: "inbox_issue.status.declined.title",
|
||||||
|
status: EInboxIssueStatus.DECLINED,
|
||||||
|
i18n_description: () => `inbox_issue.status.declined.description`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "snoozed",
|
||||||
|
i18n_title: "inbox_issue.status.snoozed.title",
|
||||||
|
status: EInboxIssueStatus.SNOOZED,
|
||||||
|
i18n_description: () => `inbox_issue.status.snoozed.description`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "accepted",
|
||||||
|
i18n_title: "inbox_issue.status.accepted.title",
|
||||||
|
status: EInboxIssueStatus.ACCEPTED,
|
||||||
|
i18n_description: () => `inbox_issue.status.accepted.description`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duplicate",
|
||||||
|
i18n_title: "inbox_issue.status.duplicate.title",
|
||||||
|
status: EInboxIssueStatus.DUPLICATE,
|
||||||
|
i18n_description: () => `inbox_issue.status.duplicate.description`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const INBOX_ISSUE_ORDER_BY_OPTIONS = [
|
||||||
|
{
|
||||||
|
key: "issue__created_at",
|
||||||
|
i18n_label: "inbox_issue.order_by.created_at",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "issue__updated_at",
|
||||||
|
i18n_label: "inbox_issue.order_by.updated_at",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "issue__sequence_id",
|
||||||
|
i18n_label: "inbox_issue.order_by.id",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const INBOX_ISSUE_SORT_BY_OPTIONS = [
|
||||||
|
{
|
||||||
|
key: "asc",
|
||||||
|
i18n_label: "common.sort.asc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "desc",
|
||||||
|
i18n_label: "common.sort.desc",
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -2,15 +2,29 @@ export * from "./ai";
|
|||||||
export * from "./analytics";
|
export * from "./analytics";
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./endpoints";
|
export * from "./endpoints";
|
||||||
export * from "./event";
|
|
||||||
export * from "./file";
|
export * from "./file";
|
||||||
export * from "./filter";
|
export * from "./filter";
|
||||||
export * from "./graph";
|
export * from "./graph";
|
||||||
export * from "./instance";
|
export * from "./instance";
|
||||||
export * from "./issue";
|
export * from "./issue";
|
||||||
export * from "./metadata";
|
export * from "./metadata";
|
||||||
|
export * from "./notification";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./swr";
|
export * from "./swr";
|
||||||
|
export * from "./tab-indices";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
export * from "./workspace";
|
export * from "./workspace";
|
||||||
export * from "./stickies";
|
export * from "./stickies";
|
||||||
|
export * from "./cycle";
|
||||||
|
export * from "./module";
|
||||||
|
export * from "./project";
|
||||||
|
export * from "./views";
|
||||||
|
export * from "./themes";
|
||||||
|
export * from "./inbox";
|
||||||
|
export * from "./profile";
|
||||||
|
export * from "./workspace-drafts";
|
||||||
|
export * from "./label";
|
||||||
|
export * from "./event-tracker";
|
||||||
|
export * from "./spreadsheet";
|
||||||
|
export * from "./dashboard";
|
||||||
|
export * from "./page";
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
import { List, Kanban } from "lucide-react";
|
|
||||||
|
|
||||||
export const ALL_ISSUES = "All Issues";
|
|
||||||
|
|
||||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
|
||||||
|
|
||||||
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
|
||||||
|
|
||||||
export type TIssueLayout =
|
|
||||||
| "list"
|
|
||||||
| "kanban"
|
|
||||||
| "calendar"
|
|
||||||
| "spreadsheet"
|
|
||||||
| "gantt";
|
|
||||||
|
|
||||||
export type TIssueFilterPriorityObject = {
|
|
||||||
key: TIssuePriorities;
|
|
||||||
title: string;
|
|
||||||
className: string;
|
|
||||||
icon: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum EIssueGroupByToServerOptions {
|
|
||||||
"state" = "state_id",
|
|
||||||
"priority" = "priority",
|
|
||||||
"labels" = "labels__id",
|
|
||||||
"state_detail.group" = "state__group",
|
|
||||||
"assignees" = "assignees__id",
|
|
||||||
"cycle" = "cycle_id",
|
|
||||||
"module" = "issue_module__module_id",
|
|
||||||
"target_date" = "target_date",
|
|
||||||
"project" = "project_id",
|
|
||||||
"created_by" = "created_by",
|
|
||||||
"team_project" = "project_id",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueGroupBYServerToProperty {
|
|
||||||
"state_id" = "state_id",
|
|
||||||
"priority" = "priority",
|
|
||||||
"labels__id" = "label_ids",
|
|
||||||
"state__group" = "state__group",
|
|
||||||
"assignees__id" = "assignee_ids",
|
|
||||||
"cycle_id" = "cycle_id",
|
|
||||||
"issue_module__module_id" = "module_ids",
|
|
||||||
"target_date" = "target_date",
|
|
||||||
"project_id" = "project_id",
|
|
||||||
"created_by" = "created_by",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EServerGroupByToFilterOptions {
|
|
||||||
"state_id" = "state",
|
|
||||||
"priority" = "priority",
|
|
||||||
"labels__id" = "labels",
|
|
||||||
"state__group" = "state_group",
|
|
||||||
"assignees__id" = "assignees",
|
|
||||||
"cycle_id" = "cycle",
|
|
||||||
"issue_module__module_id" = "module",
|
|
||||||
"target_date" = "target_date",
|
|
||||||
"project_id" = "project",
|
|
||||||
"created_by" = "created_by",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueServiceType {
|
|
||||||
ISSUES = "issues",
|
|
||||||
EPICS = "epics",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueLayoutTypes {
|
|
||||||
LIST = "list",
|
|
||||||
KANBAN = "kanban",
|
|
||||||
CALENDAR = "calendar",
|
|
||||||
GANTT = "gantt_chart",
|
|
||||||
SPREADSHEET = "spreadsheet",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssuesStoreType {
|
|
||||||
GLOBAL = "GLOBAL",
|
|
||||||
PROFILE = "PROFILE",
|
|
||||||
TEAM = "TEAM",
|
|
||||||
PROJECT = "PROJECT",
|
|
||||||
CYCLE = "CYCLE",
|
|
||||||
MODULE = "MODULE",
|
|
||||||
TEAM_VIEW = "TEAM_VIEW",
|
|
||||||
PROJECT_VIEW = "PROJECT_VIEW",
|
|
||||||
ARCHIVED = "ARCHIVED",
|
|
||||||
DRAFT = "DRAFT",
|
|
||||||
DEFAULT = "DEFAULT",
|
|
||||||
WORKSPACE_DRAFT = "WORKSPACE_DRAFT",
|
|
||||||
EPIC = "EPIC",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueFilterType {
|
|
||||||
FILTERS = "filters",
|
|
||||||
DISPLAY_FILTERS = "display_filters",
|
|
||||||
DISPLAY_PROPERTIES = "display_properties",
|
|
||||||
KANBAN_FILTERS = "kanban_filters",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueCommentAccessSpecifier {
|
|
||||||
EXTERNAL = "EXTERNAL",
|
|
||||||
INTERNAL = "INTERNAL",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EIssueListRow {
|
|
||||||
HEADER = "HEADER",
|
|
||||||
ISSUE = "ISSUE",
|
|
||||||
NO_ISSUES = "NO_ISSUES",
|
|
||||||
QUICK_ADD = "QUICK_ADD",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|
||||||
[key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>;
|
|
||||||
} = {
|
|
||||||
list: {
|
|
||||||
filters: ["priority", "state", "labels"],
|
|
||||||
},
|
|
||||||
kanban: {
|
|
||||||
filters: ["priority", "state", "labels"],
|
|
||||||
},
|
|
||||||
calendar: {
|
|
||||||
filters: ["priority", "state", "labels"],
|
|
||||||
},
|
|
||||||
spreadsheet: {
|
|
||||||
filters: ["priority", "state", "labels"],
|
|
||||||
},
|
|
||||||
gantt: {
|
|
||||||
filters: ["priority", "state", "labels"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ISSUE_PRIORITIES: {
|
|
||||||
key: TIssuePriorities;
|
|
||||||
title: string;
|
|
||||||
}[] = [
|
|
||||||
{ key: "urgent", title: "Urgent" },
|
|
||||||
{ key: "high", title: "High" },
|
|
||||||
{ key: "medium", title: "Medium" },
|
|
||||||
{ key: "low", title: "Low" },
|
|
||||||
{ key: "none", title: "None" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [
|
|
||||||
{
|
|
||||||
key: "urgent",
|
|
||||||
title: "Urgent",
|
|
||||||
className: "bg-red-500 border-red-500 text-white",
|
|
||||||
icon: "error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "high",
|
|
||||||
title: "High",
|
|
||||||
className: "text-orange-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "medium",
|
|
||||||
title: "Medium",
|
|
||||||
className: "text-yellow-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt_2_bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "low",
|
|
||||||
title: "Low",
|
|
||||||
className: "text-green-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt_1_bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "none",
|
|
||||||
title: "None",
|
|
||||||
className: "text-gray-500 border-custom-border-300",
|
|
||||||
icon: "block",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const SITES_ISSUE_LAYOUTS: {
|
|
||||||
key: TIssueLayout;
|
|
||||||
title: string;
|
|
||||||
icon: any;
|
|
||||||
}[] = [
|
|
||||||
{ key: "list", title: "List", icon: List },
|
|
||||||
{ key: "kanban", title: "Kanban", icon: Kanban },
|
|
||||||
// { key: "calendar", title: "Calendar", icon: Calendar },
|
|
||||||
// { key: "spreadsheet", title: "Spreadsheet", icon: Sheet },
|
|
||||||
// { key: "gantt", title: "Gantt chart", icon: GanttChartSquare },
|
|
||||||
];
|
|
||||||
217
packages/constants/src/issue/common.ts
Normal file
217
packages/constants/src/issue/common.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import {
|
||||||
|
TIssueGroupByOptions,
|
||||||
|
TIssueOrderByOptions,
|
||||||
|
IIssueDisplayProperties,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
|
export const ALL_ISSUES = "All Issues";
|
||||||
|
|
||||||
|
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||||
|
|
||||||
|
export type TIssueFilterPriorityObject = {
|
||||||
|
key: TIssuePriorities;
|
||||||
|
titleTranslationKey: string;
|
||||||
|
className: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum EIssueGroupByToServerOptions {
|
||||||
|
"state" = "state_id",
|
||||||
|
"priority" = "priority",
|
||||||
|
"labels" = "labels__id",
|
||||||
|
"state_detail.group" = "state__group",
|
||||||
|
"assignees" = "assignees__id",
|
||||||
|
"cycle" = "cycle_id",
|
||||||
|
"module" = "issue_module__module_id",
|
||||||
|
"target_date" = "target_date",
|
||||||
|
"project" = "project_id",
|
||||||
|
"created_by" = "created_by",
|
||||||
|
"team_project" = "project_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssueGroupBYServerToProperty {
|
||||||
|
"state_id" = "state_id",
|
||||||
|
"priority" = "priority",
|
||||||
|
"labels__id" = "label_ids",
|
||||||
|
"state__group" = "state__group",
|
||||||
|
"assignees__id" = "assignee_ids",
|
||||||
|
"cycle_id" = "cycle_id",
|
||||||
|
"issue_module__module_id" = "module_ids",
|
||||||
|
"target_date" = "target_date",
|
||||||
|
"project_id" = "project_id",
|
||||||
|
"created_by" = "created_by",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssueServiceType {
|
||||||
|
ISSUES = "issues",
|
||||||
|
EPICS = "epics",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssuesStoreType {
|
||||||
|
GLOBAL = "GLOBAL",
|
||||||
|
PROFILE = "PROFILE",
|
||||||
|
TEAM = "TEAM",
|
||||||
|
PROJECT = "PROJECT",
|
||||||
|
CYCLE = "CYCLE",
|
||||||
|
MODULE = "MODULE",
|
||||||
|
TEAM_VIEW = "TEAM_VIEW",
|
||||||
|
PROJECT_VIEW = "PROJECT_VIEW",
|
||||||
|
ARCHIVED = "ARCHIVED",
|
||||||
|
DRAFT = "DRAFT",
|
||||||
|
DEFAULT = "DEFAULT",
|
||||||
|
WORKSPACE_DRAFT = "WORKSPACE_DRAFT",
|
||||||
|
EPIC = "EPIC",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssueCommentAccessSpecifier {
|
||||||
|
EXTERNAL = "EXTERNAL",
|
||||||
|
INTERNAL = "INTERNAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssueListRow {
|
||||||
|
HEADER = "HEADER",
|
||||||
|
ISSUE = "ISSUE",
|
||||||
|
NO_ISSUES = "NO_ISSUES",
|
||||||
|
QUICK_ADD = "QUICK_ADD",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ISSUE_PRIORITIES: {
|
||||||
|
key: TIssuePriorities;
|
||||||
|
title: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "urgent",
|
||||||
|
title: "Urgent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "high",
|
||||||
|
title: "High",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "medium",
|
||||||
|
title: "Medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "low",
|
||||||
|
title: "Low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "none",
|
||||||
|
title: "None",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = [
|
||||||
|
"state",
|
||||||
|
"priority",
|
||||||
|
"assignees",
|
||||||
|
"labels",
|
||||||
|
"module",
|
||||||
|
"cycle",
|
||||||
|
];
|
||||||
|
|
||||||
|
export type TCreateModalStoreTypes =
|
||||||
|
| EIssuesStoreType.TEAM
|
||||||
|
| EIssuesStoreType.PROJECT
|
||||||
|
| EIssuesStoreType.TEAM_VIEW
|
||||||
|
| EIssuesStoreType.PROJECT_VIEW
|
||||||
|
| EIssuesStoreType.PROFILE
|
||||||
|
| EIssuesStoreType.CYCLE
|
||||||
|
| EIssuesStoreType.MODULE
|
||||||
|
| EIssuesStoreType.EPIC;
|
||||||
|
|
||||||
|
export const ISSUE_GROUP_BY_OPTIONS: {
|
||||||
|
key: TIssueGroupByOptions;
|
||||||
|
titleTranslationKey: string;
|
||||||
|
}[] = [
|
||||||
|
{ key: "state", titleTranslationKey: "common.states" },
|
||||||
|
{ key: "state_detail.group", titleTranslationKey: "common.state_groups" },
|
||||||
|
{ key: "priority", titleTranslationKey: "common.priority" },
|
||||||
|
{ key: "team_project", titleTranslationKey: "common.team_project" }, // required this on team issues
|
||||||
|
{ key: "project", titleTranslationKey: "common.project" }, // required this on my issues
|
||||||
|
{ key: "cycle", titleTranslationKey: "common.cycle" }, // required this on my issues
|
||||||
|
{ key: "module", titleTranslationKey: "common.module" }, // required this on my issues
|
||||||
|
{ key: "labels", titleTranslationKey: "common.labels" },
|
||||||
|
{ key: "assignees", titleTranslationKey: "common.assignees" },
|
||||||
|
{ key: "created_by", titleTranslationKey: "common.created_by" },
|
||||||
|
{ key: null, titleTranslationKey: "common.none" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ISSUE_ORDER_BY_OPTIONS: {
|
||||||
|
key: TIssueOrderByOptions;
|
||||||
|
titleTranslationKey: string;
|
||||||
|
}[] = [
|
||||||
|
{ key: "sort_order", titleTranslationKey: "common.order_by.manual" },
|
||||||
|
{ key: "-created_at", titleTranslationKey: "common.order_by.last_created" },
|
||||||
|
{ key: "-updated_at", titleTranslationKey: "common.order_by.last_updated" },
|
||||||
|
{ key: "start_date", titleTranslationKey: "common.order_by.start_date" },
|
||||||
|
{ key: "target_date", titleTranslationKey: "common.order_by.due_date" },
|
||||||
|
{ key: "-priority", titleTranslationKey: "common.priority" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] =
|
||||||
|
[
|
||||||
|
"assignee",
|
||||||
|
"start_date",
|
||||||
|
"due_date",
|
||||||
|
"labels",
|
||||||
|
"key",
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"sub_issue_count",
|
||||||
|
"link",
|
||||||
|
"attachment_count",
|
||||||
|
"estimate",
|
||||||
|
"created_on",
|
||||||
|
"updated_on",
|
||||||
|
"modules",
|
||||||
|
"cycle",
|
||||||
|
"issue_type",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ISSUE_DISPLAY_PROPERTIES: {
|
||||||
|
key: keyof IIssueDisplayProperties;
|
||||||
|
titleTranslationKey: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "key",
|
||||||
|
titleTranslationKey: "issue.display.properties.id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "issue_type",
|
||||||
|
titleTranslationKey: "issue.display.properties.issue_type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "assignee",
|
||||||
|
titleTranslationKey: "common.assignee",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "start_date",
|
||||||
|
titleTranslationKey: "common.order_by.start_date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "due_date",
|
||||||
|
titleTranslationKey: "common.order_by.due_date",
|
||||||
|
},
|
||||||
|
{ key: "labels", titleTranslationKey: "common.labels" },
|
||||||
|
{
|
||||||
|
key: "priority",
|
||||||
|
titleTranslationKey: "common.priority",
|
||||||
|
},
|
||||||
|
{ key: "state", titleTranslationKey: "common.state" },
|
||||||
|
{
|
||||||
|
key: "sub_issue_count",
|
||||||
|
titleTranslationKey: "issue.display.properties.sub_issue_count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "attachment_count",
|
||||||
|
titleTranslationKey: "issue.display.properties.attachment_count",
|
||||||
|
},
|
||||||
|
{ key: "link", titleTranslationKey: "common.link" },
|
||||||
|
{
|
||||||
|
key: "estimate",
|
||||||
|
titleTranslationKey: "common.estimate",
|
||||||
|
},
|
||||||
|
{ key: "modules", titleTranslationKey: "common.module" },
|
||||||
|
{ key: "cycle", titleTranslationKey: "common.cycle" },
|
||||||
|
];
|
||||||
530
packages/constants/src/issue/filter.ts
Normal file
530
packages/constants/src/issue/filter.ts
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
import {
|
||||||
|
ILayoutDisplayFiltersOptions,
|
||||||
|
TIssueActivityComment,
|
||||||
|
} from "@plane/types";
|
||||||
|
import {
|
||||||
|
TIssueFilterPriorityObject,
|
||||||
|
ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
EIssuesStoreType,
|
||||||
|
} from "./common";
|
||||||
|
|
||||||
|
import { TIssueLayout } from "./layout";
|
||||||
|
|
||||||
|
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
||||||
|
|
||||||
|
export enum EServerGroupByToFilterOptions {
|
||||||
|
"state_id" = "state",
|
||||||
|
"priority" = "priority",
|
||||||
|
"labels__id" = "labels",
|
||||||
|
"state__group" = "state_group",
|
||||||
|
"assignees__id" = "assignees",
|
||||||
|
"cycle_id" = "cycle",
|
||||||
|
"issue_module__module_id" = "module",
|
||||||
|
"target_date" = "target_date",
|
||||||
|
"project_id" = "project",
|
||||||
|
"created_by" = "created_by",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EIssueFilterType {
|
||||||
|
FILTERS = "filters",
|
||||||
|
DISPLAY_FILTERS = "display_filters",
|
||||||
|
DISPLAY_PROPERTIES = "display_properties",
|
||||||
|
KANBAN_FILTERS = "kanban_filters",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||||
|
[key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>;
|
||||||
|
} = {
|
||||||
|
list: {
|
||||||
|
filters: ["priority", "state", "labels"],
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
filters: ["priority", "state", "labels"],
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
filters: ["priority", "state", "labels"],
|
||||||
|
},
|
||||||
|
spreadsheet: {
|
||||||
|
filters: ["priority", "state", "labels"],
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
filters: ["priority", "state", "labels"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [
|
||||||
|
{
|
||||||
|
key: "urgent",
|
||||||
|
titleTranslationKey: "issue.priority.urgent",
|
||||||
|
className: "bg-red-500 border-red-500 text-white",
|
||||||
|
icon: "error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "high",
|
||||||
|
titleTranslationKey: "issue.priority.high",
|
||||||
|
className: "text-orange-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "medium",
|
||||||
|
titleTranslationKey: "issue.priority.medium",
|
||||||
|
className: "text-yellow-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt_2_bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "low",
|
||||||
|
titleTranslationKey: "issue.priority.low",
|
||||||
|
className: "text-green-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt_1_bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "none",
|
||||||
|
titleTranslationKey: "common.none",
|
||||||
|
className: "text-gray-500 border-custom-border-300",
|
||||||
|
icon: "block",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export type TFiltersByLayout = {
|
||||||
|
[layoutType: string]: ILayoutDisplayFiltersOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueFiltersToDisplayByPageType = {
|
||||||
|
[pageType: string]: TFiltersByLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
|
||||||
|
profile_issues: {
|
||||||
|
list: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups", "sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
archived_issues: {
|
||||||
|
list: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: [
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"state_detail.group",
|
||||||
|
"priority",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
draft_issues: {
|
||||||
|
list: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: [
|
||||||
|
"state_detail.group",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"priority",
|
||||||
|
"project",
|
||||||
|
"labels",
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: [
|
||||||
|
"state_detail.group",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"priority",
|
||||||
|
"project",
|
||||||
|
"labels",
|
||||||
|
],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
my_issues: {
|
||||||
|
spreadsheet: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
"subscriber",
|
||||||
|
"project",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
order_by: [],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state_group",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
"subscriber",
|
||||||
|
"project",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: false,
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
issues: {
|
||||||
|
list: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"mentions",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: [
|
||||||
|
"state",
|
||||||
|
"priority",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups", "sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"mentions",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
group_by: [
|
||||||
|
"state",
|
||||||
|
"priority",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
],
|
||||||
|
sub_group_by: [
|
||||||
|
"state",
|
||||||
|
"priority",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
"target_date",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["show_empty_groups", "sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"mentions",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ["key", "issue_type"],
|
||||||
|
display_filters: {
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spreadsheet: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"mentions",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||||
|
display_filters: {
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gantt_chart: {
|
||||||
|
filters: [
|
||||||
|
"priority",
|
||||||
|
"state",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"assignees",
|
||||||
|
"mentions",
|
||||||
|
"created_by",
|
||||||
|
"labels",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"issue_type",
|
||||||
|
],
|
||||||
|
display_properties: ["key", "issue_type"],
|
||||||
|
display_filters: {
|
||||||
|
order_by: [
|
||||||
|
"sort_order",
|
||||||
|
"-created_at",
|
||||||
|
"-updated_at",
|
||||||
|
"start_date",
|
||||||
|
"-priority",
|
||||||
|
],
|
||||||
|
type: [null, "active", "backlog"],
|
||||||
|
},
|
||||||
|
extra_options: {
|
||||||
|
access: true,
|
||||||
|
values: ["sub_issue"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ISSUE_STORE_TO_FILTERS_MAP: Partial<
|
||||||
|
Record<EIssuesStoreType, TFiltersByLayout>
|
||||||
|
> = {
|
||||||
|
[EIssuesStoreType.PROJECT]: ISSUE_DISPLAY_FILTERS_BY_PAGE.issues,
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum EActivityFilterType {
|
||||||
|
ACTIVITY = "ACTIVITY",
|
||||||
|
COMMENT = "COMMENT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TActivityFilters = EActivityFilterType;
|
||||||
|
|
||||||
|
export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<
|
||||||
|
TActivityFilters,
|
||||||
|
{ labelTranslationKey: string }
|
||||||
|
> = {
|
||||||
|
[EActivityFilterType.ACTIVITY]: {
|
||||||
|
labelTranslationKey: "common.updates",
|
||||||
|
},
|
||||||
|
[EActivityFilterType.COMMENT]: {
|
||||||
|
labelTranslationKey: "common.comments",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TActivityFilterOption = {
|
||||||
|
key: TActivityFilters;
|
||||||
|
labelTranslationKey: string;
|
||||||
|
isSelected: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultActivityFilters: TActivityFilters[] = [
|
||||||
|
EActivityFilterType.ACTIVITY,
|
||||||
|
EActivityFilterType.COMMENT,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const filterActivityOnSelectedFilters = (
|
||||||
|
activity: TIssueActivityComment[],
|
||||||
|
filters: TActivityFilters[]
|
||||||
|
): TIssueActivityComment[] =>
|
||||||
|
activity.filter((activity) =>
|
||||||
|
filters.includes(activity.activity_type as TActivityFilters)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ENABLE_ISSUE_DEPENDENCIES = false;
|
||||||
3
packages/constants/src/issue/index.ts
Normal file
3
packages/constants/src/issue/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./common";
|
||||||
|
export * from "./filter";
|
||||||
|
export * from "./layout";
|
||||||
76
packages/constants/src/issue/layout.ts
Normal file
76
packages/constants/src/issue/layout.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
export type TIssueLayout =
|
||||||
|
| "list"
|
||||||
|
| "kanban"
|
||||||
|
| "calendar"
|
||||||
|
| "spreadsheet"
|
||||||
|
| "gantt";
|
||||||
|
|
||||||
|
export enum EIssueLayoutTypes {
|
||||||
|
LIST = "list",
|
||||||
|
KANBAN = "kanban",
|
||||||
|
CALENDAR = "calendar",
|
||||||
|
GANTT = "gantt_chart",
|
||||||
|
SPREADSHEET = "spreadsheet",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TIssueLayoutMap = Record<
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
{
|
||||||
|
key: EIssueLayoutTypes;
|
||||||
|
i18n_title: string;
|
||||||
|
i18n_label: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const SITES_ISSUE_LAYOUTS: {
|
||||||
|
key: TIssueLayout;
|
||||||
|
titleTranslationKey: string;
|
||||||
|
icon: any;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "list",
|
||||||
|
icon: "List",
|
||||||
|
titleTranslationKey: "issue.layouts.list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "kanban",
|
||||||
|
icon: "Kanban",
|
||||||
|
titleTranslationKey: "issue.layouts.kanban",
|
||||||
|
},
|
||||||
|
// { key: "calendar", title: "Calendar", icon: Calendar },
|
||||||
|
// { key: "spreadsheet", title: "Spreadsheet", icon: Sheet },
|
||||||
|
// { key: "gantt", title: "Gantt chart", icon: GanttChartSquare },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ISSUE_LAYOUT_MAP: TIssueLayoutMap = {
|
||||||
|
[EIssueLayoutTypes.LIST]: {
|
||||||
|
key: EIssueLayoutTypes.LIST,
|
||||||
|
i18n_title: "issue.layouts.title.list",
|
||||||
|
i18n_label: "issue.layouts.list",
|
||||||
|
},
|
||||||
|
[EIssueLayoutTypes.KANBAN]: {
|
||||||
|
key: EIssueLayoutTypes.KANBAN,
|
||||||
|
i18n_title: "issue.layouts.title.kanban",
|
||||||
|
i18n_label: "issue.layouts.kanban",
|
||||||
|
},
|
||||||
|
[EIssueLayoutTypes.CALENDAR]: {
|
||||||
|
key: EIssueLayoutTypes.CALENDAR,
|
||||||
|
i18n_title: "issue.layouts.title.calendar",
|
||||||
|
i18n_label: "issue.layouts.calendar",
|
||||||
|
},
|
||||||
|
[EIssueLayoutTypes.SPREADSHEET]: {
|
||||||
|
key: EIssueLayoutTypes.SPREADSHEET,
|
||||||
|
i18n_title: "issue.layouts.title.spreadsheet",
|
||||||
|
i18n_label: "issue.layouts.spreadsheet",
|
||||||
|
},
|
||||||
|
[EIssueLayoutTypes.GANTT]: {
|
||||||
|
key: EIssueLayoutTypes.GANTT,
|
||||||
|
i18n_title: "issue.layouts.title.gantt",
|
||||||
|
i18n_label: "issue.layouts.gantt",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ISSUE_LAYOUTS: {
|
||||||
|
key: EIssueLayoutTypes;
|
||||||
|
i18n_title: string;
|
||||||
|
}[] = Object.values(ISSUE_LAYOUT_MAP);
|
||||||
@@ -3,9 +3,9 @@ export const SITE_NAME =
|
|||||||
export const SITE_TITLE =
|
export const SITE_TITLE =
|
||||||
"Plane | Simple, extensible, open-source project management tool.";
|
"Plane | Simple, extensible, open-source project management tool.";
|
||||||
export const SITE_DESCRIPTION =
|
export const SITE_DESCRIPTION =
|
||||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.";
|
"Open-source project management tool to manage work items, cycles, and product roadmaps easily";
|
||||||
export const SITE_KEYWORDS =
|
export const SITE_KEYWORDS =
|
||||||
"software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration";
|
"software development, plan, ship, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||||
export const SITE_URL = "https://app.plane.so/";
|
export const SITE_URL = "https://app.plane.so/";
|
||||||
export const TWITTER_USER_NAME =
|
export const TWITTER_USER_NAME =
|
||||||
"Plane | Simple, extensible, open-source project management tool.";
|
"Plane | Simple, extensible, open-source project management tool.";
|
||||||
@@ -18,6 +18,6 @@ export const SPACE_SITE_TITLE =
|
|||||||
export const SPACE_SITE_DESCRIPTION =
|
export const SPACE_SITE_DESCRIPTION =
|
||||||
"Plane Publish is a customer feedback management tool built on top of plane.so";
|
"Plane Publish is a customer feedback management tool built on top of plane.so";
|
||||||
export const SPACE_SITE_KEYWORDS =
|
export const SPACE_SITE_KEYWORDS =
|
||||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration";
|
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||||
export const SPACE_SITE_URL = "https://app.plane.so/";
|
export const SPACE_SITE_URL = "https://app.plane.so/";
|
||||||
export const SPACE_TWITTER_USER_NAME = "planepowers";
|
export const SPACE_TWITTER_USER_NAME = "planepowers";
|
||||||
|
|||||||
@@ -1,51 +1,54 @@
|
|||||||
import { GanttChartSquare, LayoutGrid, List } from "lucide-react";
|
|
||||||
// types
|
// types
|
||||||
import { TModuleLayoutOptions, TModuleOrderByOptions, TModuleStatus } from "@plane/types";
|
import {
|
||||||
|
TModuleLayoutOptions,
|
||||||
|
TModuleOrderByOptions,
|
||||||
|
TModuleStatus,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
export const MODULE_STATUS: {
|
export const MODULE_STATUS: {
|
||||||
label: string;
|
i18n_label: string;
|
||||||
value: TModuleStatus;
|
value: TModuleStatus;
|
||||||
color: string;
|
color: string;
|
||||||
textColor: string;
|
textColor: string;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
label: "Backlog",
|
i18n_label: "project_modules.status.backlog",
|
||||||
value: "backlog",
|
value: "backlog",
|
||||||
color: "#a3a3a2",
|
color: "#a3a3a2",
|
||||||
textColor: "text-custom-text-400",
|
textColor: "text-custom-text-400",
|
||||||
bgColor: "bg-custom-background-80",
|
bgColor: "bg-custom-background-80",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Planned",
|
i18n_label: "project_modules.status.planned",
|
||||||
value: "planned",
|
value: "planned",
|
||||||
color: "#3f76ff",
|
color: "#3f76ff",
|
||||||
textColor: "text-blue-500",
|
textColor: "text-blue-500",
|
||||||
bgColor: "bg-indigo-50",
|
bgColor: "bg-indigo-50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "In Progress",
|
i18n_label: "project_modules.status.in_progress",
|
||||||
value: "in-progress",
|
value: "in-progress",
|
||||||
color: "#f39e1f",
|
color: "#f39e1f",
|
||||||
textColor: "text-amber-500",
|
textColor: "text-amber-500",
|
||||||
bgColor: "bg-amber-50",
|
bgColor: "bg-amber-50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Paused",
|
i18n_label: "project_modules.status.paused",
|
||||||
value: "paused",
|
value: "paused",
|
||||||
color: "#525252",
|
color: "#525252",
|
||||||
textColor: "text-custom-text-300",
|
textColor: "text-custom-text-300",
|
||||||
bgColor: "bg-custom-background-90",
|
bgColor: "bg-custom-background-90",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Completed",
|
i18n_label: "project_modules.status.completed",
|
||||||
value: "completed",
|
value: "completed",
|
||||||
color: "#16a34a",
|
color: "#16a34a",
|
||||||
textColor: "text-green-600",
|
textColor: "text-green-600",
|
||||||
bgColor: "bg-green-100",
|
bgColor: "bg-green-100",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Cancelled",
|
i18n_label: "project_modules.status.cancelled",
|
||||||
value: "cancelled",
|
value: "cancelled",
|
||||||
color: "#ef4444",
|
color: "#ef4444",
|
||||||
textColor: "text-red-500",
|
textColor: "text-red-500",
|
||||||
@@ -53,47 +56,50 @@ export const MODULE_STATUS: {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MODULE_VIEW_LAYOUTS: { key: TModuleLayoutOptions; icon: any; title: string }[] = [
|
export const MODULE_VIEW_LAYOUTS: {
|
||||||
|
key: TModuleLayoutOptions;
|
||||||
|
i18n_title: string;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
key: "list",
|
key: "list",
|
||||||
icon: List,
|
i18n_title: "project_modules.layout.list",
|
||||||
title: "List layout",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "board",
|
key: "board",
|
||||||
icon: LayoutGrid,
|
i18n_title: "project_modules.layout.board",
|
||||||
title: "Gallery layout",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "gantt",
|
key: "gantt",
|
||||||
icon: GanttChartSquare,
|
i18n_title: "project_modules.layout.timeline",
|
||||||
title: "Timeline layout",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MODULE_ORDER_BY_OPTIONS: { key: TModuleOrderByOptions; label: string }[] = [
|
export const MODULE_ORDER_BY_OPTIONS: {
|
||||||
|
key: TModuleOrderByOptions;
|
||||||
|
i18n_label: string;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
key: "name",
|
key: "name",
|
||||||
label: "Name",
|
i18n_label: "project_modules.order_by.name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "progress",
|
key: "progress",
|
||||||
label: "Progress",
|
i18n_label: "project_modules.order_by.progress",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "issues_length",
|
key: "issues_length",
|
||||||
label: "Number of issues",
|
i18n_label: "project_modules.order_by.issues",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "target_date",
|
key: "target_date",
|
||||||
label: "Due date",
|
i18n_label: "project_modules.order_by.due_date",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
label: "Created date",
|
i18n_label: "project_modules.order_by.created_at",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "sort_order",
|
key: "sort_order",
|
||||||
label: "Manual",
|
i18n_label: "project_modules.order_by.manual",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -29,12 +29,13 @@ export type TNotificationTab = ENotificationTab.ALL | ENotificationTab.MENTIONS;
|
|||||||
|
|
||||||
export const NOTIFICATION_TABS = [
|
export const NOTIFICATION_TABS = [
|
||||||
{
|
{
|
||||||
label: "All",
|
i18n_label: "notification.tabs.all",
|
||||||
value: ENotificationTab.ALL,
|
value: ENotificationTab.ALL,
|
||||||
count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.total_unread_notifications_count || 0,
|
count: (unReadNotification: TUnreadNotificationsCount) =>
|
||||||
|
unReadNotification?.total_unread_notifications_count || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Mentions",
|
i18n_label: "notification.tabs.mentions",
|
||||||
value: ENotificationTab.MENTIONS,
|
value: ENotificationTab.MENTIONS,
|
||||||
count: (unReadNotification: TUnreadNotificationsCount) =>
|
count: (unReadNotification: TUnreadNotificationsCount) =>
|
||||||
unReadNotification?.mention_unread_notifications_count || 0,
|
unReadNotification?.mention_unread_notifications_count || 0,
|
||||||
@@ -43,15 +44,15 @@ export const NOTIFICATION_TABS = [
|
|||||||
|
|
||||||
export const FILTER_TYPE_OPTIONS = [
|
export const FILTER_TYPE_OPTIONS = [
|
||||||
{
|
{
|
||||||
label: "Assigned to me",
|
i18n_label: "notification.filter.assigned",
|
||||||
value: ENotificationFilterType.ASSIGNED,
|
value: ENotificationFilterType.ASSIGNED,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Created by me",
|
i18n_label: "notification.filter.created",
|
||||||
value: ENotificationFilterType.CREATED,
|
value: ENotificationFilterType.CREATED,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Subscribed by me",
|
i18n_label: "notification.filter.subscribed",
|
||||||
value: ENotificationFilterType.SUBSCRIBED,
|
value: ENotificationFilterType.SUBSCRIBED,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -59,7 +60,7 @@ export const FILTER_TYPE_OPTIONS = [
|
|||||||
export const NOTIFICATION_SNOOZE_OPTIONS = [
|
export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||||
{
|
{
|
||||||
key: "1_day",
|
key: "1_day",
|
||||||
label: "1 day",
|
i18n_label: "notification.snooze.1_day",
|
||||||
value: () => {
|
value: () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
return new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
||||||
@@ -67,7 +68,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "3_days",
|
key: "3_days",
|
||||||
label: "3 days",
|
i18n_label: "notification.snooze.3_days",
|
||||||
value: () => {
|
value: () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return new Date(date.getTime() + 3 * 24 * 60 * 60 * 1000);
|
return new Date(date.getTime() + 3 * 24 * 60 * 60 * 1000);
|
||||||
@@ -75,7 +76,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "5_days",
|
key: "5_days",
|
||||||
label: "5 days",
|
i18n_label: "notification.snooze.5_days",
|
||||||
value: () => {
|
value: () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return new Date(date.getTime() + 5 * 24 * 60 * 60 * 1000);
|
return new Date(date.getTime() + 5 * 24 * 60 * 60 * 1000);
|
||||||
@@ -83,7 +84,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "1_week",
|
key: "1_week",
|
||||||
label: "1 week",
|
i18n_label: "notification.snooze.1_week",
|
||||||
value: () => {
|
value: () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return new Date(date.getTime() + 7 * 24 * 60 * 60 * 1000);
|
return new Date(date.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||||
@@ -91,7 +92,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "2_weeks",
|
key: "2_weeks",
|
||||||
label: "2 weeks",
|
i18n_label: "notification.snooze.2_weeks",
|
||||||
value: () => {
|
value: () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return new Date(date.getTime() + 14 * 24 * 60 * 60 * 1000);
|
return new Date(date.getTime() + 14 * 24 * 60 * 60 * 1000);
|
||||||
@@ -99,7 +100,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "custom",
|
key: "custom",
|
||||||
label: "Custom",
|
i18n_label: "notification.snooze.custom",
|
||||||
value: undefined,
|
value: undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
14
packages/constants/src/page.ts
Normal file
14
packages/constants/src/page.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export enum EPageAccess {
|
||||||
|
PUBLIC = 0,
|
||||||
|
PRIVATE = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreatePageModal = {
|
||||||
|
isOpen: boolean;
|
||||||
|
pageAccess?: EPageAccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_CREATE_PAGE_MODAL_DATA: TCreatePageModal = {
|
||||||
|
isOpen: false,
|
||||||
|
pageAccess: EPageAccess.PUBLIC,
|
||||||
|
};
|
||||||
@@ -1,48 +1,38 @@
|
|||||||
import React from "react";
|
|
||||||
// icons
|
|
||||||
import { Activity, Bell, CircleUser, KeyRound, LucideProps, Settings2 } from "lucide-react";
|
|
||||||
|
|
||||||
export const PROFILE_ACTION_LINKS: {
|
export const PROFILE_ACTION_LINKS: {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
i18n_label: string;
|
||||||
href: string;
|
href: string;
|
||||||
highlight: (pathname: string) => boolean;
|
highlight: (pathname: string) => boolean;
|
||||||
Icon: React.FC<LucideProps>;
|
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
key: "profile",
|
key: "profile",
|
||||||
label: "Profile",
|
i18n_label: "profile.actions.profile",
|
||||||
href: `/profile`,
|
href: `/profile`,
|
||||||
highlight: (pathname: string) => pathname === "/profile/",
|
highlight: (pathname: string) => pathname === "/profile/",
|
||||||
Icon: CircleUser,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "security",
|
key: "security",
|
||||||
label: "Security",
|
i18n_label: "profile.actions.security",
|
||||||
href: `/profile/security`,
|
href: `/profile/security`,
|
||||||
highlight: (pathname: string) => pathname === "/profile/security/",
|
highlight: (pathname: string) => pathname === "/profile/security/",
|
||||||
Icon: KeyRound,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "activity",
|
key: "activity",
|
||||||
label: "Activity",
|
i18n_label: "profile.actions.activity",
|
||||||
href: `/profile/activity`,
|
href: `/profile/activity`,
|
||||||
highlight: (pathname: string) => pathname === "/profile/activity/",
|
highlight: (pathname: string) => pathname === "/profile/activity/",
|
||||||
Icon: Activity,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "appearance",
|
key: "appearance",
|
||||||
label: "Appearance",
|
i18n_label: "profile.actions.appearance",
|
||||||
href: `/profile/appearance`,
|
href: `/profile/appearance`,
|
||||||
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
|
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
|
||||||
Icon: Settings2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "notifications",
|
key: "notifications",
|
||||||
label: "Notifications",
|
i18n_label: "profile.actions.notifications",
|
||||||
href: `/profile/notifications`,
|
href: `/profile/notifications`,
|
||||||
highlight: (pathname: string) => pathname === "/profile/notifications/",
|
highlight: (pathname: string) => pathname === "/profile/notifications/",
|
||||||
Icon: Bell,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -50,7 +40,7 @@ export const PROFILE_VIEWER_TAB = [
|
|||||||
{
|
{
|
||||||
key: "summary",
|
key: "summary",
|
||||||
route: "",
|
route: "",
|
||||||
label: "Summary",
|
i18n_label: "profile.tabs.summary",
|
||||||
selected: "/",
|
selected: "/",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -59,24 +49,25 @@ export const PROFILE_ADMINS_TAB = [
|
|||||||
{
|
{
|
||||||
key: "assigned",
|
key: "assigned",
|
||||||
route: "assigned",
|
route: "assigned",
|
||||||
label: "Assigned",
|
i18n_label: "profile.tabs.assigned",
|
||||||
selected: "/assigned/",
|
selected: "/assigned/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: "created",
|
||||||
route: "created",
|
route: "created",
|
||||||
label: "Created",
|
i18n_label: "profile.tabs.created",
|
||||||
selected: "/created/",
|
selected: "/created/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "subscribed",
|
key: "subscribed",
|
||||||
route: "subscribed",
|
route: "subscribed",
|
||||||
label: "Subscribed",
|
i18n_label: "profile.tabs.subscribed",
|
||||||
selected: "/subscribed/",
|
selected: "/subscribed/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "activity",
|
key: "activity",
|
||||||
route: "activity",
|
route: "activity",
|
||||||
label: "Activity",
|
i18n_label: "profile.tabs.activity",
|
||||||
selected: "/activity/",
|
selected: "/activity/",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,41 +1,65 @@
|
|||||||
// icons
|
// icons
|
||||||
import { Globe2, Lock, LucideIcon } from "lucide-react";
|
import {
|
||||||
import { TProjectAppliedDisplayFilterKeys, TProjectOrderByOptions } from "@plane/types";
|
TProjectAppliedDisplayFilterKeys,
|
||||||
|
TProjectOrderByOptions,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
export const NETWORK_CHOICES: {
|
export type TNetworkChoiceIconKey = "Lock" | "Globe2";
|
||||||
|
|
||||||
|
export type TNetworkChoice = {
|
||||||
key: 0 | 2;
|
key: 0 | 2;
|
||||||
label: string;
|
labelKey: string;
|
||||||
|
i18n_label: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: LucideIcon;
|
iconKey: TNetworkChoiceIconKey;
|
||||||
}[] = [
|
};
|
||||||
|
|
||||||
|
export const NETWORK_CHOICES: TNetworkChoice[] = [
|
||||||
{
|
{
|
||||||
key: 0,
|
key: 0,
|
||||||
label: "Private",
|
labelKey: "Private",
|
||||||
description: "Accessible only by invite",
|
i18n_label: "workspace_projects.network.private.title",
|
||||||
icon: Lock,
|
description: "workspace_projects.network.private.description", //"Accessible only by invite",
|
||||||
|
iconKey: "Lock",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 2,
|
key: 2,
|
||||||
label: "Public",
|
labelKey: "Public",
|
||||||
description: "Anyone in the workspace except Guests can join",
|
i18n_label: "workspace_projects.network.public.title",
|
||||||
icon: Globe2,
|
description: "workspace_projects.network.public.description", //"Anyone in the workspace except Guests can join",
|
||||||
|
iconKey: "Globe2",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GROUP_CHOICES = {
|
export const GROUP_CHOICES = {
|
||||||
backlog: "Backlog",
|
backlog: {
|
||||||
unstarted: "Unstarted",
|
key: "backlog",
|
||||||
started: "Started",
|
i18n_label: "workspace_projects.state.backlog",
|
||||||
completed: "Completed",
|
},
|
||||||
cancelled: "Cancelled",
|
unstarted: {
|
||||||
|
key: "unstarted",
|
||||||
|
i18n_label: "workspace_projects.state.unstarted",
|
||||||
|
},
|
||||||
|
started: {
|
||||||
|
key: "started",
|
||||||
|
i18n_label: "workspace_projects.state.started",
|
||||||
|
},
|
||||||
|
completed: {
|
||||||
|
key: "completed",
|
||||||
|
i18n_label: "workspace_projects.state.completed",
|
||||||
|
},
|
||||||
|
cancelled: {
|
||||||
|
key: "cancelled",
|
||||||
|
i18n_label: "workspace_projects.state.cancelled",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PROJECT_AUTOMATION_MONTHS = [
|
export const PROJECT_AUTOMATION_MONTHS = [
|
||||||
{ label: "1 month", value: 1 },
|
{ i18n_label: "common.months_count", value: 1 },
|
||||||
{ label: "3 months", value: 3 },
|
{ i18n_label: "common.months_count", value: 3 },
|
||||||
{ label: "6 months", value: 6 },
|
{ i18n_label: "common.months_count", value: 6 },
|
||||||
{ label: "9 months", value: 9 },
|
{ i18n_label: "common.months_count", value: 9 },
|
||||||
{ label: "12 months", value: 12 },
|
{ i18n_label: "common.months_count", value: 12 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PROJECT_UNSPLASH_COVERS = [
|
export const PROJECT_UNSPLASH_COVERS = [
|
||||||
@@ -59,55 +83,55 @@ export const PROJECT_UNSPLASH_COVERS = [
|
|||||||
|
|
||||||
export const PROJECT_ORDER_BY_OPTIONS: {
|
export const PROJECT_ORDER_BY_OPTIONS: {
|
||||||
key: TProjectOrderByOptions;
|
key: TProjectOrderByOptions;
|
||||||
label: string;
|
i18n_label: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
key: "sort_order",
|
key: "sort_order",
|
||||||
label: "Manual",
|
i18n_label: "workspace_projects.sort.manual",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "name",
|
key: "name",
|
||||||
label: "Name",
|
i18n_label: "workspace_projects.sort.name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "created_at",
|
key: "created_at",
|
||||||
label: "Created date",
|
i18n_label: "workspace_projects.sort.created_at",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "members_length",
|
key: "members_length",
|
||||||
label: "Number of members",
|
i18n_label: "workspace_projects.sort.members_length",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PROJECT_DISPLAY_FILTER_OPTIONS: {
|
export const PROJECT_DISPLAY_FILTER_OPTIONS: {
|
||||||
key: TProjectAppliedDisplayFilterKeys;
|
key: TProjectAppliedDisplayFilterKeys;
|
||||||
label: string;
|
i18n_label: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
key: "my_projects",
|
key: "my_projects",
|
||||||
label: "My projects",
|
i18n_label: "workspace_projects.scope.my_projects",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "archived_projects",
|
key: "archived_projects",
|
||||||
label: "Archived",
|
i18n_label: "workspace_projects.scope.archived_projects",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PROJECT_ERROR_MESSAGES = {
|
export const PROJECT_ERROR_MESSAGES = {
|
||||||
permissionError: {
|
permissionError: {
|
||||||
title: "You don't have permission to perform this action.",
|
i18n_title: "workspace_projects.error.permission",
|
||||||
message: undefined,
|
i18n_message: undefined,
|
||||||
},
|
},
|
||||||
cycleDeleteError: {
|
cycleDeleteError: {
|
||||||
title: "Error",
|
i18n_title: "error",
|
||||||
message: "Failed to delete cycle",
|
i18n_message: "workspace_projects.error.cycle_delete",
|
||||||
},
|
},
|
||||||
moduleDeleteError: {
|
moduleDeleteError: {
|
||||||
title: "Error",
|
i18n_title: "error",
|
||||||
message: "Failed to delete module",
|
i18n_message: "workspace_projects.error.module_delete",
|
||||||
},
|
},
|
||||||
issueDeleteError: {
|
issueDeleteError: {
|
||||||
title: "Error",
|
i18n_title: "error",
|
||||||
message: "Failed to delete issue",
|
i18n_message: "workspace_projects.error.issue_delete",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
1
packages/constants/src/spreadsheet.ts
Normal file
1
packages/constants/src/spreadsheet.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const SPREADSHEET_SELECT_GROUP = "spreadsheet-issues";
|
||||||
@@ -5,6 +5,11 @@ export type TStateGroups =
|
|||||||
| "completed"
|
| "completed"
|
||||||
| "cancelled";
|
| "cancelled";
|
||||||
|
|
||||||
|
export type TDraggableData = {
|
||||||
|
groupKey: TStateGroups;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const STATE_GROUPS: {
|
export const STATE_GROUPS: {
|
||||||
[key in TStateGroups]: {
|
[key in TStateGroups]: {
|
||||||
key: TStateGroups;
|
key: TStateGroups;
|
||||||
@@ -43,6 +48,13 @@ export const ARCHIVABLE_STATE_GROUPS = [
|
|||||||
STATE_GROUPS.completed.key,
|
STATE_GROUPS.completed.key,
|
||||||
STATE_GROUPS.cancelled.key,
|
STATE_GROUPS.cancelled.key,
|
||||||
];
|
];
|
||||||
|
export const COMPLETED_STATE_GROUPS = [STATE_GROUPS.completed.key];
|
||||||
|
export const PENDING_STATE_GROUPS = [
|
||||||
|
STATE_GROUPS.backlog.key,
|
||||||
|
STATE_GROUPS.unstarted.key,
|
||||||
|
STATE_GROUPS.started.key,
|
||||||
|
STATE_GROUPS.cancelled.key,
|
||||||
|
];
|
||||||
|
|
||||||
export const PROGRESS_STATE_GROUPS_DETAILS = [
|
export const PROGRESS_STATE_GROUPS_DETAILS = [
|
||||||
{
|
{
|
||||||
@@ -66,3 +78,5 @@ export const PROGRESS_STATE_GROUPS_DETAILS = [
|
|||||||
color: "#A3A3A3",
|
color: "#A3A3A3",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const DISPLAY_WORKFLOW_PRO_CTA = false;
|
||||||
|
|||||||
@@ -6,3 +6,11 @@ export const DEFAULT_SWR_CONFIG = {
|
|||||||
refreshInterval: 600000,
|
refreshInterval: 600000,
|
||||||
errorRetryCount: 3,
|
errorRetryCount: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WEB_SWR_CONFIG = {
|
||||||
|
refreshWhenHidden: false,
|
||||||
|
revalidateIfStale: true,
|
||||||
|
revalidateOnFocus: true,
|
||||||
|
revalidateOnMount: true,
|
||||||
|
errorRetryCount: 3,
|
||||||
|
};
|
||||||
|
|||||||
@@ -54,7 +54,14 @@ export const PROJECT_CREATE_TAB_INDICES = [
|
|||||||
"logo_props",
|
"logo_props",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PROJECT_CYCLE_TAB_INDICES = ["name", "description", "date_range", "cancel", "submit", "project_id"];
|
export const PROJECT_CYCLE_TAB_INDICES = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"date_range",
|
||||||
|
"cancel",
|
||||||
|
"submit",
|
||||||
|
"project_id",
|
||||||
|
];
|
||||||
|
|
||||||
export const PROJECT_MODULE_TAB_INDICES = [
|
export const PROJECT_MODULE_TAB_INDICES = [
|
||||||
"name",
|
"name",
|
||||||
@@ -67,9 +74,21 @@ export const PROJECT_MODULE_TAB_INDICES = [
|
|||||||
"submit",
|
"submit",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PROJECT_VIEW_TAB_INDICES = ["name", "description", "filters", "cancel", "submit"];
|
export const PROJECT_VIEW_TAB_INDICES = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"filters",
|
||||||
|
"cancel",
|
||||||
|
"submit",
|
||||||
|
];
|
||||||
|
|
||||||
export const PROJECT_PAGE_TAB_INDICES = ["name", "public", "private", "cancel", "submit"];
|
export const PROJECT_PAGE_TAB_INDICES = [
|
||||||
|
"name",
|
||||||
|
"public",
|
||||||
|
"private",
|
||||||
|
"cancel",
|
||||||
|
"submit",
|
||||||
|
];
|
||||||
|
|
||||||
export enum ETabIndices {
|
export enum ETabIndices {
|
||||||
ISSUE_FORM = "issue-form",
|
ISSUE_FORM = "issue-form",
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
|
export const THEMES = [
|
||||||
|
"light",
|
||||||
|
"dark",
|
||||||
|
"light-contrast",
|
||||||
|
"dark-contrast",
|
||||||
|
"custom",
|
||||||
|
];
|
||||||
|
|
||||||
export interface I_THEME_OPTION {
|
export interface I_THEME_OPTION {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
i18n_label: string;
|
||||||
type: string;
|
type: string;
|
||||||
icon: {
|
icon: {
|
||||||
border: string;
|
border: string;
|
||||||
@@ -16,7 +22,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "system_preference",
|
key: "system_preference",
|
||||||
value: "system",
|
value: "system",
|
||||||
label: "System preference",
|
i18n_label: "System preference",
|
||||||
type: "light",
|
type: "light",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#DEE2E6",
|
border: "#DEE2E6",
|
||||||
@@ -27,7 +33,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "light",
|
key: "light",
|
||||||
value: "light",
|
value: "light",
|
||||||
label: "Light",
|
i18n_label: "Light",
|
||||||
type: "light",
|
type: "light",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#DEE2E6",
|
border: "#DEE2E6",
|
||||||
@@ -38,7 +44,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "dark",
|
key: "dark",
|
||||||
value: "dark",
|
value: "dark",
|
||||||
label: "Dark",
|
i18n_label: "Dark",
|
||||||
type: "dark",
|
type: "dark",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#2E3234",
|
border: "#2E3234",
|
||||||
@@ -49,7 +55,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "light_contrast",
|
key: "light_contrast",
|
||||||
value: "light-contrast",
|
value: "light-contrast",
|
||||||
label: "Light high contrast",
|
i18n_label: "Light high contrast",
|
||||||
type: "light",
|
type: "light",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#000000",
|
border: "#000000",
|
||||||
@@ -60,7 +66,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "dark_contrast",
|
key: "dark_contrast",
|
||||||
value: "dark-contrast",
|
value: "dark-contrast",
|
||||||
label: "Dark high contrast",
|
i18n_label: "Dark high contrast",
|
||||||
type: "dark",
|
type: "dark",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#FFFFFF",
|
border: "#FFFFFF",
|
||||||
@@ -71,7 +77,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
|||||||
{
|
{
|
||||||
key: "custom",
|
key: "custom",
|
||||||
value: "custom",
|
value: "custom",
|
||||||
label: "Custom theme",
|
i18n_label: "Custom theme",
|
||||||
type: "light",
|
type: "light",
|
||||||
icon: {
|
icon: {
|
||||||
border: "#FFC9C9",
|
border: "#FFC9C9",
|
||||||
@@ -36,3 +36,40 @@ export enum EUserProjectRoles {
|
|||||||
MEMBER = 15,
|
MEMBER = 15,
|
||||||
GUEST = 5,
|
GUEST = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TUserPermissionsLevel = EUserPermissionsLevel;
|
||||||
|
|
||||||
|
export enum EUserPermissions {
|
||||||
|
ADMIN = 20,
|
||||||
|
MEMBER = 15,
|
||||||
|
GUEST = 5,
|
||||||
|
}
|
||||||
|
export type TUserPermissions = EUserPermissions;
|
||||||
|
|
||||||
|
export type TUserAllowedPermissionsObject = {
|
||||||
|
create: TUserPermissions[];
|
||||||
|
update: TUserPermissions[];
|
||||||
|
delete: TUserPermissions[];
|
||||||
|
read: TUserPermissions[];
|
||||||
|
};
|
||||||
|
export type TUserAllowedPermissions = {
|
||||||
|
workspace: {
|
||||||
|
[key: string]: Partial<TUserAllowedPermissionsObject>;
|
||||||
|
};
|
||||||
|
project: {
|
||||||
|
[key: string]: Partial<TUserAllowedPermissionsObject>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = {
|
||||||
|
workspace: {
|
||||||
|
dashboard: {
|
||||||
|
read: [
|
||||||
|
EUserPermissions.ADMIN,
|
||||||
|
EUserPermissions.MEMBER,
|
||||||
|
EUserPermissions.GUEST,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
project: {},
|
||||||
|
};
|
||||||
|
|||||||
23
packages/constants/src/views.ts
Normal file
23
packages/constants/src/views.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export enum EViewAccess {
|
||||||
|
PRIVATE,
|
||||||
|
PUBLIC,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VIEW_ACCESS_SPECIFIERS: {
|
||||||
|
key: EViewAccess;
|
||||||
|
i18n_label: string;
|
||||||
|
}[] = [
|
||||||
|
{ key: EViewAccess.PUBLIC, i18n_label: "common.access.public" },
|
||||||
|
{ key: EViewAccess.PRIVATE, i18n_label: "common.access.private" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const VIEW_SORTING_KEY_OPTIONS = [
|
||||||
|
{ key: "name", i18n_label: "project_view.sort_by.name" },
|
||||||
|
{ key: "created_at", i18n_label: "project_view.sort_by.created_at" },
|
||||||
|
{ key: "updated_at", i18n_label: "project_view.sort_by.updated_at" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const VIEW_SORT_BY_OPTIONS = [
|
||||||
|
{ key: "asc", i18n_label: "common.order_by.asc" },
|
||||||
|
{ key: "desc", i18n_label: "common.order_by.desc" },
|
||||||
|
];
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { TStaticViewTypes } from "@plane/types";
|
||||||
|
import { EUserWorkspaceRoles } from "./user";
|
||||||
|
|
||||||
export const ORGANIZATION_SIZE = [
|
export const ORGANIZATION_SIZE = [
|
||||||
"Just myself", // TODO: translate
|
"Just myself", // TODO: translate
|
||||||
"2-10",
|
"2-10",
|
||||||
@@ -74,3 +77,182 @@ export const RESTRICTED_URLS = [
|
|||||||
"instances",
|
"instances",
|
||||||
"instance",
|
"instance",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const WORKSPACE_SETTINGS = {
|
||||||
|
general: {
|
||||||
|
key: "general",
|
||||||
|
i18n_label: "workspace_settings.settings.general.title",
|
||||||
|
href: `/settings`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/`,
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
key: "members",
|
||||||
|
i18n_label: "workspace_settings.settings.members.title",
|
||||||
|
href: `/settings/members`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/members/`,
|
||||||
|
},
|
||||||
|
"billing-and-plans": {
|
||||||
|
key: "billing-and-plans",
|
||||||
|
i18n_label: "workspace_settings.settings.billing_and_plans.title",
|
||||||
|
href: `/settings/billing`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/billing/`,
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
key: "export",
|
||||||
|
i18n_label: "workspace_settings.settings.exports.title",
|
||||||
|
href: `/settings/exports`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/exports/`,
|
||||||
|
},
|
||||||
|
webhooks: {
|
||||||
|
key: "webhooks",
|
||||||
|
i18n_label: "workspace_settings.settings.webhooks.title",
|
||||||
|
href: `/settings/webhooks`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/webhooks/`,
|
||||||
|
},
|
||||||
|
"api-tokens": {
|
||||||
|
key: "api-tokens",
|
||||||
|
i18n_label: "workspace_settings.settings.api_tokens.title",
|
||||||
|
href: `/settings/api-tokens`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN],
|
||||||
|
highlight: (pathname: string, baseUrl: string) =>
|
||||||
|
pathname === `${baseUrl}/settings/api-tokens/`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WORKSPACE_SETTINGS_LINKS: {
|
||||||
|
key: string;
|
||||||
|
i18n_label: string;
|
||||||
|
href: string;
|
||||||
|
access: EUserWorkspaceRoles[];
|
||||||
|
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||||
|
}[] = [
|
||||||
|
WORKSPACE_SETTINGS["general"],
|
||||||
|
WORKSPACE_SETTINGS["members"],
|
||||||
|
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||||
|
WORKSPACE_SETTINGS["export"],
|
||||||
|
WORKSPACE_SETTINGS["webhooks"],
|
||||||
|
WORKSPACE_SETTINGS["api-tokens"],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ROLE = {
|
||||||
|
[EUserWorkspaceRoles.GUEST]: "Guest",
|
||||||
|
[EUserWorkspaceRoles.MEMBER]: "Member",
|
||||||
|
[EUserWorkspaceRoles.ADMIN]: "Admin",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ROLE_DETAILS = {
|
||||||
|
[EUserWorkspaceRoles.GUEST]: {
|
||||||
|
i18n_title: "role_details.guest.title",
|
||||||
|
i18n_description: "role_details.guest.description",
|
||||||
|
},
|
||||||
|
[EUserWorkspaceRoles.MEMBER]: {
|
||||||
|
i18n_title: "role_details.member.title",
|
||||||
|
i18n_description: "role_details.member.description",
|
||||||
|
},
|
||||||
|
[EUserWorkspaceRoles.ADMIN]: {
|
||||||
|
i18n_title: "role_details.admin.title",
|
||||||
|
i18n_description: "role_details.admin.description",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const USER_ROLES = [
|
||||||
|
{
|
||||||
|
value: "Product / Project Manager",
|
||||||
|
i18n_label: "user_roles.product_or_project_manager",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Development / Engineering",
|
||||||
|
i18n_label: "user_roles.development_or_engineering",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Founder / Executive",
|
||||||
|
i18n_label: "user_roles.founder_or_executive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Freelancer / Consultant",
|
||||||
|
i18n_label: "user_roles.freelancer_or_consultant",
|
||||||
|
},
|
||||||
|
{ value: "Marketing / Growth", i18n_label: "user_roles.marketing_or_growth" },
|
||||||
|
{
|
||||||
|
value: "Sales / Business Development",
|
||||||
|
i18n_label: "user_roles.sales_or_business_development",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Support / Operations",
|
||||||
|
i18n_label: "user_roles.support_or_operations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "Student / Professor",
|
||||||
|
i18n_label: "user_roles.student_or_professor",
|
||||||
|
},
|
||||||
|
{ value: "Human Resources", i18n_label: "user_roles.human_resources" },
|
||||||
|
{ value: "Other", i18n_label: "user_roles.other" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const IMPORTERS_LIST = [
|
||||||
|
{
|
||||||
|
provider: "github",
|
||||||
|
type: "import",
|
||||||
|
i18n_title: "importer.github.title",
|
||||||
|
i18n_description: "importer.github.description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "jira",
|
||||||
|
type: "import",
|
||||||
|
i18n_title: "importer.jira.title",
|
||||||
|
i18n_description: "importer.jira.description",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EXPORTERS_LIST = [
|
||||||
|
{
|
||||||
|
provider: "csv",
|
||||||
|
type: "export",
|
||||||
|
i18n_title: "exporter.csv.title",
|
||||||
|
i18n_description: "exporter.csv.description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "xlsx",
|
||||||
|
type: "export",
|
||||||
|
i18n_title: "exporter.excel.title",
|
||||||
|
i18n_description: "exporter.csv.description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: "json",
|
||||||
|
type: "export",
|
||||||
|
i18n_title: "exporter.json.title",
|
||||||
|
i18n_description: "exporter.csv.description",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DEFAULT_GLOBAL_VIEWS_LIST: {
|
||||||
|
key: TStaticViewTypes;
|
||||||
|
i18n_label: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "all-issues",
|
||||||
|
i18n_label: "default_global_view.all_issues",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "assigned",
|
||||||
|
i18n_label: "default_global_view.assigned",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "created",
|
||||||
|
i18n_label: "default_global_view.created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "subscribed",
|
||||||
|
i18n_label: "default_global_view.subscribed",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
171
packages/i18n/src/locales/en/core.json
Normal file
171
packages/i18n/src/locales/en/core.json
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
{
|
||||||
|
"sidebar": {
|
||||||
|
"projects": "Projects",
|
||||||
|
"pages": "Pages",
|
||||||
|
"new_work_item": "New work item",
|
||||||
|
"home": "Home",
|
||||||
|
"your_work": "Your work",
|
||||||
|
"inbox": "Inbox",
|
||||||
|
"workspace": "Workspace",
|
||||||
|
"views": "Views",
|
||||||
|
"analytics": "Analytics",
|
||||||
|
"work_items": "Work items",
|
||||||
|
"cycles": "Cycles",
|
||||||
|
"modules": "Modules",
|
||||||
|
"intake": "Intake",
|
||||||
|
"drafts": "Drafts",
|
||||||
|
"favorites": "Favorites",
|
||||||
|
"pro": "Pro",
|
||||||
|
"upgrade": "Upgrade"
|
||||||
|
},
|
||||||
|
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"email": {
|
||||||
|
"label": "Email",
|
||||||
|
"placeholder": "name@company.com",
|
||||||
|
"errors": {
|
||||||
|
"required": "Email is required",
|
||||||
|
"invalid": "Email is invalid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Password",
|
||||||
|
"set_password": "Set a password",
|
||||||
|
"placeholder": "Enter password",
|
||||||
|
"confirm_password": {
|
||||||
|
"label": "Confirm password",
|
||||||
|
"placeholder": "Confirm password"
|
||||||
|
},
|
||||||
|
"current_password": {
|
||||||
|
"label": "Current password"
|
||||||
|
},
|
||||||
|
"new_password": {
|
||||||
|
"label": "New password",
|
||||||
|
"placeholder": "Enter new password"
|
||||||
|
},
|
||||||
|
"change_password": {
|
||||||
|
"label": {
|
||||||
|
"default": "Change password",
|
||||||
|
"submitting": "Changing password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"match": "Passwords don't match",
|
||||||
|
"empty": "Please enter your password",
|
||||||
|
"length": "Password length should me more than 8 characters",
|
||||||
|
"strength": {
|
||||||
|
"weak": "Password is weak",
|
||||||
|
"strong": "Password is strong"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": "Set password",
|
||||||
|
"toast": {
|
||||||
|
"change_password": {
|
||||||
|
"success": {
|
||||||
|
"title": "Success!",
|
||||||
|
"message": "Password changed successfully."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error!",
|
||||||
|
"message": "Something went wrong. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unique_code": {
|
||||||
|
"label": "Unique code",
|
||||||
|
"placeholder": "gets-sets-flys",
|
||||||
|
"paste_code": "Paste the code sent to your email",
|
||||||
|
"requesting_new_code": "Requesting new code",
|
||||||
|
"sending_code": "Sending code"
|
||||||
|
},
|
||||||
|
"already_have_an_account": "Already have an account?",
|
||||||
|
"login": "Log in",
|
||||||
|
"create_account": "Create an account",
|
||||||
|
"new_to_plane": "New to Plane?",
|
||||||
|
"back_to_sign_in": "Back to sign in",
|
||||||
|
"resend_in": "Resend in {seconds} seconds",
|
||||||
|
"sign_in_with_unique_code": "Sign in with unique code",
|
||||||
|
"forgot_password": "Forgot your password?"
|
||||||
|
},
|
||||||
|
"sign_up": {
|
||||||
|
"header": {
|
||||||
|
"label": "Create an account to start managing work with your team.",
|
||||||
|
"step": {
|
||||||
|
"email": {
|
||||||
|
"header": "Sign up",
|
||||||
|
"sub_header": ""
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"header": "Sign up",
|
||||||
|
"sub_header": "Sign up using an email-password combination."
|
||||||
|
},
|
||||||
|
"unique_code": {
|
||||||
|
"header": "Sign up",
|
||||||
|
"sub_header": "Sign up using a unique code sent to the email address above."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"password": {
|
||||||
|
"strength": "Try setting-up a strong password to proceed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sign_in": {
|
||||||
|
"header": {
|
||||||
|
"label": "Log in to start managing work with your team.",
|
||||||
|
"step": {
|
||||||
|
"email": {
|
||||||
|
"header": "Log in or sign up",
|
||||||
|
"sub_header": ""
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"header": "Log in or sign up",
|
||||||
|
"sub_header": "Use your email-password combination to log in."
|
||||||
|
},
|
||||||
|
"unique_code": {
|
||||||
|
"header": "Log in or sign up",
|
||||||
|
"sub_header": "Log in using a unique code sent to the email address above."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forgot_password": {
|
||||||
|
"title": "Reset your password",
|
||||||
|
"description": "Enter your user account's verified email address and we will send you a password reset link.",
|
||||||
|
"email_sent": "We sent the reset link to your email address",
|
||||||
|
"send_reset_link": "Send reset link",
|
||||||
|
"errors": {
|
||||||
|
"smtp_not_enabled": "We see that your god hasn't enabled SMTP, we will not be able to send a password reset link"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"success": {
|
||||||
|
"title": "Email sent",
|
||||||
|
"message": "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error!",
|
||||||
|
"message": "Something went wrong. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reset_password": {
|
||||||
|
"title": "Set new password",
|
||||||
|
"description": "Secure your account with a strong password"
|
||||||
|
},
|
||||||
|
"set_password": {
|
||||||
|
"title": "Secure your account",
|
||||||
|
"description": "Setting password helps you login securely"
|
||||||
|
},
|
||||||
|
"sign_out": {
|
||||||
|
"toast": {
|
||||||
|
"error": {
|
||||||
|
"title": "Error!",
|
||||||
|
"message": "Failed to sign out. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,11 @@
|
|||||||
import IntlMessageFormat from "intl-messageformat";
|
import IntlMessageFormat from "intl-messageformat";
|
||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import { makeAutoObservable } from "mobx";
|
import merge from "lodash/merge";
|
||||||
|
import { makeAutoObservable, runInAction } from "mobx";
|
||||||
// constants
|
// constants
|
||||||
import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, STORAGE_KEY } from "../constants";
|
import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, STORAGE_KEY } from "../constants";
|
||||||
|
// core translations imports
|
||||||
|
import coreEn from "../locales/en/core.json";
|
||||||
// types
|
// types
|
||||||
import { TLanguage, ILanguageOption, ITranslations } from "../types";
|
import { TLanguage, ILanguageOption, ITranslations } from "../types";
|
||||||
|
|
||||||
@@ -12,42 +15,35 @@ import { TLanguage, ILanguageOption, ITranslations } from "../types";
|
|||||||
* Uses IntlMessageFormat to format the translations
|
* Uses IntlMessageFormat to format the translations
|
||||||
*/
|
*/
|
||||||
export class TranslationStore {
|
export class TranslationStore {
|
||||||
|
// Core translations that are always loaded
|
||||||
|
private coreTranslations: ITranslations = {
|
||||||
|
en: coreEn,
|
||||||
|
};
|
||||||
// List of translations for each language
|
// List of translations for each language
|
||||||
private translations: ITranslations = {};
|
private translations: ITranslations = {};
|
||||||
// Cache for IntlMessageFormat instances
|
// Cache for IntlMessageFormat instances
|
||||||
private messageCache: Map<string, IntlMessageFormat> = new Map();
|
private messageCache: Map<string, IntlMessageFormat> = new Map();
|
||||||
// Current language
|
// Current language
|
||||||
currentLocale: TLanguage = FALLBACK_LANGUAGE;
|
currentLocale: TLanguage = FALLBACK_LANGUAGE;
|
||||||
|
// Loading state
|
||||||
|
isLoading: boolean = true;
|
||||||
|
isInitialized: boolean = false;
|
||||||
|
// Set of loaded languages
|
||||||
|
private loadedLanguages: Set<TLanguage> = new Set();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the TranslationStore class
|
* Constructor for the TranslationStore class
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
|
// Initialize with core translations immediately
|
||||||
|
this.translations = this.coreTranslations;
|
||||||
|
// Initialize language
|
||||||
this.initializeLanguage();
|
this.initializeLanguage();
|
||||||
|
// Load all the translations
|
||||||
this.loadTranslations();
|
this.loadTranslations();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads translations from JSON files and initializes the message cache
|
|
||||||
*/
|
|
||||||
private async loadTranslations() {
|
|
||||||
try {
|
|
||||||
// dynamic import of translations
|
|
||||||
const translations = {
|
|
||||||
en: (await import("../locales/en/translations.json")).default,
|
|
||||||
fr: (await import("../locales/fr/translations.json")).default,
|
|
||||||
es: (await import("../locales/es/translations.json")).default,
|
|
||||||
ja: (await import("../locales/ja/translations.json")).default,
|
|
||||||
"zh-CN": (await import("../locales/zh-CN/translations.json")).default,
|
|
||||||
};
|
|
||||||
this.translations = translations;
|
|
||||||
this.messageCache.clear(); // Clear cache when translations change
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load translations:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Initializes the language based on the local storage or browser language */
|
/** Initializes the language based on the local storage or browser language */
|
||||||
private initializeLanguage() {
|
private initializeLanguage() {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
@@ -62,6 +58,100 @@ export class TranslationStore {
|
|||||||
this.setLanguage(browserLang);
|
this.setLanguage(browserLang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Loads the translations for the current language */
|
||||||
|
private async loadTranslations(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Set initialized to true (Core translations are already loaded)
|
||||||
|
runInAction(() => {
|
||||||
|
this.isInitialized = true;
|
||||||
|
});
|
||||||
|
// Load current and fallback languages in parallel
|
||||||
|
await this.loadPrimaryLanguages();
|
||||||
|
// Load all remaining languages in parallel
|
||||||
|
this.loadRemainingLanguages();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed in translation initialization:", error);
|
||||||
|
runInAction(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPrimaryLanguages(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Load current and fallback languages in parallel
|
||||||
|
const languagesToLoad = new Set<TLanguage>([this.currentLocale]);
|
||||||
|
// Add fallback language only if different from current
|
||||||
|
if (this.currentLocale !== FALLBACK_LANGUAGE) {
|
||||||
|
languagesToLoad.add(FALLBACK_LANGUAGE);
|
||||||
|
}
|
||||||
|
// Load all primary languages in parallel
|
||||||
|
const loadPromises = Array.from(languagesToLoad).map((lang) => this.loadLanguageTranslations(lang));
|
||||||
|
await Promise.all(loadPromises);
|
||||||
|
// Update loading state
|
||||||
|
runInAction(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load primary languages:", error);
|
||||||
|
runInAction(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadRemainingLanguages(): void {
|
||||||
|
const remainingLanguages = SUPPORTED_LANGUAGES.map((lang) => lang.value).filter(
|
||||||
|
(lang) =>
|
||||||
|
!this.loadedLanguages.has(lang as TLanguage) && lang !== this.currentLocale && lang !== FALLBACK_LANGUAGE
|
||||||
|
);
|
||||||
|
// Load all remaining languages in parallel
|
||||||
|
Promise.all(remainingLanguages.map((lang) => this.loadLanguageTranslations(lang as TLanguage))).catch((error) => {
|
||||||
|
console.error("Failed to load some remaining languages:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadLanguageTranslations(language: TLanguage): Promise<void> {
|
||||||
|
// Skip if already loaded
|
||||||
|
if (this.loadedLanguages.has(language)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const translations = await this.importLanguageFile(language);
|
||||||
|
runInAction(() => {
|
||||||
|
// Use lodash merge for deep merging
|
||||||
|
this.translations[language] = merge({}, this.coreTranslations[language] || {}, translations.default);
|
||||||
|
// Add to loaded languages
|
||||||
|
this.loadedLanguages.add(language);
|
||||||
|
// Clear cache
|
||||||
|
this.messageCache.clear();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load translations for ${language}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports the translations for the given language
|
||||||
|
* @param language - The language to import the translations for
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
private importLanguageFile(language: TLanguage): Promise<any> {
|
||||||
|
switch (language) {
|
||||||
|
case "en":
|
||||||
|
return import("../locales/en/translations.json");
|
||||||
|
case "fr":
|
||||||
|
return import("../locales/fr/translations.json");
|
||||||
|
case "es":
|
||||||
|
return import("../locales/es/translations.json");
|
||||||
|
case "ja":
|
||||||
|
return import("../locales/ja/translations.json");
|
||||||
|
case "zh-CN":
|
||||||
|
return import("../locales/zh-CN/translations.json");
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported language: ${language}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks if the language is valid based on the supported languages */
|
/** Checks if the language is valid based on the supported languages */
|
||||||
private isValidLanguage(lang: string | null): lang is TLanguage {
|
private isValidLanguage(lang: string | null): lang is TLanguage {
|
||||||
return lang !== null && this.availableLanguages.some((l) => l.value === lang);
|
return lang !== null && this.availableLanguages.some((l) => l.value === lang);
|
||||||
@@ -173,20 +263,26 @@ export class TranslationStore {
|
|||||||
* Sets the current language and updates the translations
|
* Sets the current language and updates the translations
|
||||||
* @param lng - The new language
|
* @param lng - The new language
|
||||||
*/
|
*/
|
||||||
setLanguage(lng: TLanguage): void {
|
async setLanguage(lng: TLanguage): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!this.isValidLanguage(lng)) {
|
if (!this.isValidLanguage(lng)) {
|
||||||
throw new Error(`Invalid language: ${lng}`);
|
throw new Error(`Invalid language: ${lng}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safeguard in case background loading failed
|
||||||
|
if (!this.loadedLanguages.has(lng)) {
|
||||||
|
await this.loadLanguageTranslations(lng);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem(STORAGE_KEY, lng);
|
localStorage.setItem(STORAGE_KEY, lng);
|
||||||
}
|
|
||||||
this.currentLocale = lng;
|
|
||||||
this.messageCache.clear(); // Clear cache when language changes
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
document.documentElement.lang = lng;
|
document.documentElement.lang = lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.currentLocale = lng;
|
||||||
|
this.messageCache.clear(); // Clear cache when language changes
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to set language:", error);
|
console.error("Failed to set language:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
33
packages/types/src/inbox.d.ts
vendored
33
packages/types/src/inbox.d.ts
vendored
@@ -2,23 +2,6 @@ import { TPaginationInfo } from "./common";
|
|||||||
import { TIssuePriorities } from "./issues";
|
import { TIssuePriorities } from "./issues";
|
||||||
import { TIssue } from "./issues/base";
|
import { TIssue } from "./issues/base";
|
||||||
|
|
||||||
enum EInboxIssueCurrentTab {
|
|
||||||
OPEN = "open",
|
|
||||||
CLOSED = "closed",
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EInboxIssueStatus {
|
|
||||||
PENDING = -2,
|
|
||||||
DECLINED = -1,
|
|
||||||
SNOOZED = 0,
|
|
||||||
ACCEPTED = 1,
|
|
||||||
DUPLICATE = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TInboxIssueCurrentTab = EInboxIssueCurrentTab;
|
|
||||||
|
|
||||||
export type TInboxIssueStatus = EInboxIssueStatus;
|
|
||||||
|
|
||||||
// filters
|
// filters
|
||||||
export type TInboxIssueFilterMemberKeys = "assignees" | "created_by";
|
export type TInboxIssueFilterMemberKeys = "assignees" | "created_by";
|
||||||
|
|
||||||
@@ -38,10 +21,7 @@ export type TInboxIssueFilter = {
|
|||||||
// sorting filters
|
// sorting filters
|
||||||
export type TInboxIssueSortingKeys = "order_by" | "sort_by";
|
export type TInboxIssueSortingKeys = "order_by" | "sort_by";
|
||||||
|
|
||||||
export type TInboxIssueSortingOrderByKeys =
|
export type TInboxIssueSortingOrderByKeys = "issue__created_at" | "issue__updated_at" | "issue__sequence_id";
|
||||||
| "issue__created_at"
|
|
||||||
| "issue__updated_at"
|
|
||||||
| "issue__sequence_id";
|
|
||||||
|
|
||||||
export type TInboxIssueSortingSortByKeys = "asc" | "desc";
|
export type TInboxIssueSortingSortByKeys = "asc" | "desc";
|
||||||
|
|
||||||
@@ -78,17 +58,6 @@ export type TInboxDuplicateIssueDetails = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TInboxIssue = {
|
|
||||||
id: string;
|
|
||||||
status: TInboxIssueStatus;
|
|
||||||
snoozed_till: Date | null;
|
|
||||||
duplicate_to: string | undefined;
|
|
||||||
source: string;
|
|
||||||
issue: TIssue;
|
|
||||||
created_by: string;
|
|
||||||
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
||||||
total_results: number;
|
total_results: number;
|
||||||
};
|
};
|
||||||
|
|||||||
2
packages/types/src/issues.d.ts
vendored
2
packages/types/src/issues.d.ts
vendored
@@ -15,7 +15,7 @@ import type {
|
|||||||
TIssueGroupByOptions,
|
TIssueGroupByOptions,
|
||||||
TIssueOrderByOptions,
|
TIssueOrderByOptions,
|
||||||
TIssueGroupingFilters,
|
TIssueGroupingFilters,
|
||||||
TIssueExtraOptions
|
TIssueExtraOptions,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
|
|
||||||
export interface IIssueCycle {
|
export interface IIssueCycle {
|
||||||
|
|||||||
5
packages/types/src/project/projects.d.ts
vendored
5
packages/types/src/project/projects.d.ts
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
import { EUserProjectRoles } from "@plane/constants";
|
||||||
import type {
|
import type {
|
||||||
IProjectViewProps,
|
IProjectViewProps,
|
||||||
IUser,
|
IUser,
|
||||||
@@ -16,7 +17,7 @@ export interface IPartialProject {
|
|||||||
identifier: string;
|
identifier: string;
|
||||||
sort_order: number | null;
|
sort_order: number | null;
|
||||||
logo_props: TLogoProps;
|
logo_props: TLogoProps;
|
||||||
member_role: TUserPermissions | null;
|
member_role?: TUserPermissions | EUserProjectRoles | null;
|
||||||
archived_at: string | null;
|
archived_at: string | null;
|
||||||
workspace: IWorkspace | string;
|
workspace: IWorkspace | string;
|
||||||
cycle_view: boolean;
|
cycle_view: boolean;
|
||||||
@@ -118,7 +119,7 @@ export interface IProjectMembership {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectBulkAddFormData {
|
export interface IProjectBulkAddFormData {
|
||||||
members: { role: TUserPermissions; member_id: string }[];
|
members: { role: TUserPermissions | EUserProjectRoles; member_id: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGithubRepository {
|
export interface IGithubRepository {
|
||||||
|
|||||||
2
packages/types/src/views.d.ts
vendored
2
packages/types/src/views.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { EViewAccess } from "@/constants/views";
|
import { EViewAccess } from "@plane/constants";
|
||||||
import { TLogoProps } from "./common";
|
import { TLogoProps } from "./common";
|
||||||
import {
|
import {
|
||||||
IIssueDisplayFilterOptions,
|
IIssueDisplayFilterOptions,
|
||||||
|
|||||||
2
packages/types/src/workspace-views.d.ts
vendored
2
packages/types/src/workspace-views.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { EViewAccess } from "@/constants/views";
|
import { EViewAccess } from "@plane/constants";
|
||||||
import {
|
import {
|
||||||
IWorkspaceViewProps,
|
IWorkspaceViewProps,
|
||||||
IIssueDisplayFilterOptions,
|
IIssueDisplayFilterOptions,
|
||||||
|
|||||||
5
packages/types/src/workspace.d.ts
vendored
5
packages/types/src/workspace.d.ts
vendored
@@ -5,6 +5,7 @@ import type {
|
|||||||
IUserLite,
|
IUserLite,
|
||||||
IWorkspaceViewProps,
|
IWorkspaceViewProps,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
|
import { EUserWorkspaceRoles } from "@plane/constants"; // TODO: check if importing this over here causes circular dependency
|
||||||
import { TUserPermissions } from "./enums";
|
import { TUserPermissions } from "./enums";
|
||||||
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
@@ -69,7 +70,7 @@ export type Properties = {
|
|||||||
export interface IWorkspaceMember {
|
export interface IWorkspaceMember {
|
||||||
id: string;
|
id: string;
|
||||||
member: IUserLite;
|
member: IUserLite;
|
||||||
role: TUserPermissions;
|
role: TUserPermissions | EUserWorkspaceRoles;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -87,7 +88,7 @@ export interface IWorkspaceMemberMe {
|
|||||||
default_props: IWorkspaceViewProps;
|
default_props: IWorkspaceViewProps;
|
||||||
id: string;
|
id: string;
|
||||||
member: string;
|
member: string;
|
||||||
role: TUserPermissions;
|
role: TUserPermissions | EUserWorkspaceRoles;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
view_props: IWorkspaceViewProps;
|
view_props: IWorkspaceViewProps;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const metadata: Metadata = {
|
|||||||
url: "https://sites.plane.so/",
|
url: "https://sites.plane.so/",
|
||||||
},
|
},
|
||||||
keywords:
|
keywords:
|
||||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
|
"software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration",
|
||||||
twitter: {
|
twitter: {
|
||||||
site: "@planepowers",
|
site: "@planepowers",
|
||||||
},
|
},
|
||||||
@@ -32,7 +32,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<link rel="shortcut icon" href={`${SPACE_BASE_PATH}/favicon/favicon.ico`} />
|
<link rel="shortcut icon" href={`${SPACE_BASE_PATH}/favicon/favicon.ico`} />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<AppProvider>{children}</AppProvider>
|
<AppProvider>
|
||||||
|
<>{children}</>
|
||||||
|
</AppProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
// components
|
// components
|
||||||
|
import { TranslationProvider } from "@plane/i18n";
|
||||||
import { InstanceProvider } from "@/lib/instance-provider";
|
import { InstanceProvider } from "@/lib/instance-provider";
|
||||||
import { StoreProvider } from "@/lib/store-provider";
|
import { StoreProvider } from "@/lib/store-provider";
|
||||||
import { ToastProvider } from "@/lib/toast-provider";
|
import { ToastProvider } from "@/lib/toast-provider";
|
||||||
@@ -15,9 +16,11 @@ export const AppProvider: FC<IAppProvider> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StoreProvider>
|
<StoreProvider>
|
||||||
|
<TranslationProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<InstanceProvider>{children}</InstanceProvider>
|
<InstanceProvider>{children}</InstanceProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
</TranslationProvider>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Titles: TAuthHeaderDetails = {
|
|||||||
},
|
},
|
||||||
[EAuthModes.SIGN_UP]: {
|
[EAuthModes.SIGN_UP]: {
|
||||||
header: "View, comment, and do more",
|
header: "View, comment, and do more",
|
||||||
subHeader: "Sign up or log in to work with Plane Issues and Pages.",
|
subHeader: "Sign up or log in to work with Plane work items and Pages.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export const AuthHeader: FC<TAuthHeader> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
header: "Comment or react to issues",
|
header: "Comment or react to work itemss",
|
||||||
subHeader: "Use plane to add your valuable inputs to features.",
|
subHeader: "Use plane to add your valuable inputs to features.",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const InstanceFailureView: FC = () => {
|
|||||||
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
||||||
<p className="font-medium text-base text-center">
|
<p className="font-medium text-base text-center">
|
||||||
We were unable to fetch the details of the instance. <br />
|
We were unable to fetch the details of the instance. <br />
|
||||||
Fret not, it might just be a connectivity issue.
|
Fret not, it might just be a connectivity work items.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TFilters } from "@/types/issue";
|
import { TFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { AppliedPriorityFilters } from "./priority";
|
import { AppliedPriorityFilters } from "./priority";
|
||||||
@@ -18,6 +19,7 @@ export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, "
|
|||||||
|
|
||||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||||
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter } = props;
|
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-stretch gap-2">
|
<div className="flex flex-wrap items-stretch gap-2">
|
||||||
@@ -72,7 +74,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
|||||||
onClick={handleRemoveAllFilters}
|
onClick={handleRemoveAllFilters}
|
||||||
className="flex items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1 text-xs text-custom-text-300 hover:text-custom-text-200"
|
className="flex items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1 text-xs text-custom-text-300 hover:text-custom-text-200"
|
||||||
>
|
>
|
||||||
Clear all
|
{t("common.clear_all")}
|
||||||
<X size={12} strokeWidth={2} />
|
<X size={12} strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// ui
|
|
||||||
import { ISSUE_PRIORITY_FILTERS } from "@plane/constants";
|
import { ISSUE_PRIORITY_FILTERS } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
// ui
|
||||||
import { PriorityIcon } from "@plane/ui";
|
import { PriorityIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "./helpers";
|
import { FilterHeader, FilterOption } from "./helpers";
|
||||||
@@ -18,6 +19,9 @@ type Props = {
|
|||||||
export const FilterPriority: React.FC<Props> = observer((props) => {
|
export const FilterPriority: React.FC<Props> = observer((props) => {
|
||||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||||
|
|
||||||
|
// hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||||
@@ -40,11 +44,11 @@ export const FilterPriority: React.FC<Props> = observer((props) => {
|
|||||||
isChecked={appliedFilters?.includes(priority.key) ? true : false}
|
isChecked={appliedFilters?.includes(priority.key) ? true : false}
|
||||||
onClick={() => handleUpdate(priority.key)}
|
onClick={() => handleUpdate(priority.key)}
|
||||||
icon={<PriorityIcon priority={priority.key} className="h-3.5 w-3.5" />}
|
icon={<PriorityIcon priority={priority.key} className="h-3.5 w-3.5" />}
|
||||||
title={priority.title}
|
title={t(priority.titleTranslationKey)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
<p className="text-xs italic text-custom-text-400">{t("common.search.no_matches_found")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const IssueLayoutHOC = observer((props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (getGroupIssueCount(undefined, undefined, false) === 0) {
|
if (getGroupIssueCount(undefined, undefined, false) === 0) {
|
||||||
return <div className="flex w-full h-full items-center justify-center">No Issues Found</div>;
|
return <div className="flex w-full h-full items-center justify-center">No work items Found</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{props.children}</>;
|
return <>{props.children}</>;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||||
<HeaderGroupByCard
|
<HeaderGroupByCard
|
||||||
groupBy={groupBy}
|
groupBy={groupBy}
|
||||||
icon={subList.icon}
|
icon={subList.icon as any}
|
||||||
title={subList.name}
|
title={subList.name}
|
||||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -131,10 +131,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
|||||||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||||
|
|
||||||
if (subGroupByVisibilityToggle === false) return <></>;
|
if (subGroupByVisibilityToggle === false) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${subGroupBy}_${group.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
<div key={`${subGroupBy}_${group.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
||||||
<HeaderGroupByCard groupBy={groupBy} icon={group.icon} title={group.name} count={groupCount} />
|
<HeaderGroupByCard
|
||||||
|
groupBy={groupBy}
|
||||||
|
icon={group.icon}
|
||||||
|
title={group.name}
|
||||||
|
count={groupCount}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -262,7 +266,7 @@ const SubGroup: React.FC<ISubGroup> = observer((props) => {
|
|||||||
<div className="sticky top-[50px] z-[3] py-1 flex w-full items-center bg-custom-background-100 border-y-[0.5px] border-custom-border-200">
|
<div className="sticky top-[50px] z-[3] py-1 flex w-full items-center bg-custom-background-100 border-y-[0.5px] border-custom-border-200">
|
||||||
<div className="sticky left-0 flex-shrink-0">
|
<div className="sticky left-0 flex-shrink-0">
|
||||||
<HeaderSubGroupByCard
|
<HeaderSubGroupByCard
|
||||||
icon={group.icon}
|
icon={group.icon as any}
|
||||||
title={group.name || ""}
|
title={group.name || ""}
|
||||||
count={issueCount}
|
count={issueCount}
|
||||||
isExpanded={isExpanded}
|
isExpanded={isExpanded}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Fragment, MutableRefObject, forwardRef, useRef, useState } from "react";
|
import { Fragment, MutableRefObject, forwardRef, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// plane types
|
// plane types
|
||||||
import { IGroupByColumn, TIssueGroupByOptions, IIssueDisplayProperties, TPaginationData, TLoader } from "@plane/types";
|
import { IGroupByColumn, TIssueGroupByOptions, IIssueDisplayProperties, TPaginationData, TLoader } from "@plane/types";
|
||||||
// plane utils
|
// plane utils
|
||||||
@@ -62,6 +63,8 @@ export const ListGroup = observer((props: Props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
const groupRef = useRef<HTMLDivElement | null>(null);
|
const groupRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
@@ -85,7 +88,7 @@ export const ListGroup = observer((props: Props) => {
|
|||||||
}
|
}
|
||||||
onClick={() => loadMoreIssues(group.id)}
|
onClick={() => loadMoreIssues(group.id)}
|
||||||
>
|
>
|
||||||
Load More ↓
|
{t("common.load_more")} ↓
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
displayPropertyKey="sub_issue_count"
|
displayPropertyKey="sub_issue_count"
|
||||||
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!issue.sub_issues_count}
|
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!issue.sub_issues_count}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
<Tooltip tooltipHeading="Sub-work items" tooltipContent={`${issue.sub_issues_count}`}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
|
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { TIssuePriorities } from "@plane/types";
|
import { TIssuePriorities } from "@plane/types";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
@@ -13,17 +14,19 @@ export const IssueBlockPriority = ({
|
|||||||
priority: TIssuePriorities | null;
|
priority: TIssuePriorities | null;
|
||||||
shouldShowName?: boolean;
|
shouldShowName?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
// hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
const priority_detail = priority != null ? getIssuePriorityFilters(priority) : null;
|
const priority_detail = priority != null ? getIssuePriorityFilters(priority) : null;
|
||||||
|
|
||||||
if (priority_detail === null) return <></>;
|
if (priority_detail === null) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip tooltipHeading="Priority" tooltipContent={priority_detail?.title}>
|
<Tooltip tooltipHeading="Priority" tooltipContent={t(priority_detail?.titleTranslationKey || "")}>
|
||||||
<div className="flex items-center relative w-full h-full">
|
<div className="flex items-center relative w-full h-full">
|
||||||
<div className={`grid h-5 w-5 place-items-center rounded border-[0.5px] gap-2 ${priority_detail?.className}`}>
|
<div className={`grid h-5 w-5 place-items-center rounded border-[0.5px] gap-2 ${priority_detail?.className}`}>
|
||||||
<span className="material-symbols-rounded text-sm">{priority_detail?.icon}</span>
|
<span className="material-symbols-rounded text-sm">{priority_detail?.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
{shouldShowName && <span className="pl-2 text-sm">{priority_detail?.title}</span>}
|
{shouldShowName && <span className="pl-2 text-sm">{t(priority_detail?.titleTranslationKey || "")}</span>}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const getGroupByColumns = (
|
|||||||
case "created_by":
|
case "created_by":
|
||||||
return getCreatedByColumns(member) as any;
|
return getCreatedByColumns(member) as any;
|
||||||
default:
|
default:
|
||||||
if (includeNone) return [{ id: `All Issues`, name: `All Issues`, payload: {}, icon: undefined }];
|
if (includeNone) return [{ id: `All Issues`, name: `All work items`, payload: {}, icon: undefined }];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
// ui
|
// ui
|
||||||
import { SITES_ISSUE_LAYOUTS } from "@plane/constants";
|
import { SITES_ISSUE_LAYOUTS } from "@plane/constants";
|
||||||
|
// plane i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
@@ -19,6 +21,8 @@ type Props = {
|
|||||||
|
|
||||||
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||||
const { anchor } = props;
|
const { anchor } = props;
|
||||||
|
// hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -45,7 +49,7 @@ export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
|||||||
if (!layoutOptions[layout.key]) return;
|
if (!layoutOptions[layout.key]) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip key={layout.key} tooltipContent={layout.title}>
|
<Tooltip key={layout.key} tooltipContent={t(layout.titleTranslationKey)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded transition-all hover:bg-custom-background-100 ${
|
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded transition-all hover:bg-custom-background-100 ${
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
|||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Link copied!",
|
title: "Link copied!",
|
||||||
message: "Issue link copied to clipboard.",
|
message: "Work item link copied to clipboard.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { CalendarCheck2, Signal } from "lucide-react";
|
import { CalendarCheck2, Signal } from "lucide-react";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { getIssuePriorityFilters } from "@plane/utils";
|
import { getIssuePriorityFilters } from "@plane/utils";
|
||||||
@@ -24,6 +25,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDetails, mode }) => {
|
export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDetails, mode }) => {
|
||||||
|
// hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
const { getStateById } = useStates();
|
const { getStateById } = useStates();
|
||||||
const state = getStateById(issueDetails?.state_id ?? undefined);
|
const state = getStateById(issueDetails?.state_id ?? undefined);
|
||||||
|
|
||||||
@@ -40,7 +43,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
|
|||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.INFO,
|
type: TOAST_TYPE.INFO,
|
||||||
title: "Link copied!",
|
title: "Link copied!",
|
||||||
message: "Issue link copied to clipboard",
|
message: "Work item link copied to clipboard",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -95,7 +98,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
|
|||||||
<Icon iconName={priority?.icon} />
|
<Icon iconName={priority?.icon} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span>{priority?.title ?? "None"}</span>
|
<span>{t(priority?.titleTranslationKey || "common.none")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@mui/material": "^5.14.1",
|
"@mui/material": "^5.14.1",
|
||||||
"@plane/constants": "*",
|
"@plane/constants": "*",
|
||||||
"@plane/editor": "*",
|
"@plane/editor": "*",
|
||||||
|
"@plane/i18n": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
"@plane/services": "*",
|
"@plane/services": "*",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Plane Space",
|
"name": "Plane Space",
|
||||||
"short_name": "Plane Space",
|
"short_name": "Plane Space",
|
||||||
"description": "Plane helps you plan your issues, cycles, and product modules.",
|
"description": "Plane helps you plan your work items, cycles, and product modules.",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#f9fafb",
|
"background_color": "#f9fafb",
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={<BreadcrumbLink label={t("analytics")} icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />} />}
|
link={
|
||||||
|
<BreadcrumbLink
|
||||||
|
label={t("workspace_analytics.label")}
|
||||||
|
icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
{analytics_tab === "custom" ? (
|
{analytics_tab === "custom" ? (
|
||||||
|
|||||||
@@ -5,27 +5,40 @@ import { observer } from "mobx-react";
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// plane package imports
|
// plane package imports
|
||||||
import { ANALYTICS_TABS } from "@plane/constants";
|
import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Header, EHeaderVariant } from "@plane/ui";
|
import { Header, EHeaderVariant } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store";
|
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
const AnalyticsPage = observer(() => {
|
const AnalyticsPage = observer(() => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const analytics_tab = searchParams.get("analytics_tab");
|
const analytics_tab = searchParams.get("analytics_tab");
|
||||||
|
// plane imports
|
||||||
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreateProjectModal } = useCommandPalette();
|
const { toggleCreateProjectModal } = useCommandPalette();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement } = useEventTracker();
|
||||||
const { workspaceProjectIds, loader } = useProject();
|
const { workspaceProjectIds, loader } = useProject();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
// helper hooks
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
|
||||||
// derived values
|
// derived values
|
||||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
|
const pageTitle = currentWorkspace?.name
|
||||||
|
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// permissions
|
||||||
|
const canPerformEmptyStateActions = allowPermissions(
|
||||||
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
|
EUserPermissionsLevel.WORKSPACE
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: refactor loader implementation
|
// TODO: refactor loader implementation
|
||||||
return (
|
return (
|
||||||
@@ -46,7 +59,7 @@ const AnalyticsPage = observer(() => {
|
|||||||
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
|
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tab.title}
|
{t(tab.i18n_title)}
|
||||||
<div
|
<div
|
||||||
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
|
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
|
||||||
/>
|
/>
|
||||||
@@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.WORKSPACE_ANALYTICS}
|
title={t("workspace_analytics.empty_state.general.title")}
|
||||||
primaryButtonOnClick={() => {
|
description={t("workspace_analytics.empty_state.general.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
customPrimaryButton={
|
||||||
|
<ComicBoxButton
|
||||||
|
label={t("workspace_analytics.empty_state.general.primary_button.text")}
|
||||||
|
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||||
|
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||||
|
onClick={() => {
|
||||||
setTrackElement("Analytics empty state");
|
setTrackElement("Analytics empty state");
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!canPerformEmptyStateActions}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { PenSquare } from "lucide-react";
|
import { PenSquare } from "lucide-react";
|
||||||
import { EIssuesStoreType } from "@plane/constants";
|
import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@@ -12,8 +13,6 @@ import { CreateUpdateIssueModal } from "@/components/issues";
|
|||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUserPermissions, useWorkspaceDraftIssues } from "@/hooks/store";
|
import { useProject, useUserPermissions, useWorkspaceDraftIssues } from "@/hooks/store";
|
||||||
// plane-web
|
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
export const WorkspaceDraftHeader = observer(() => {
|
export const WorkspaceDraftHeader = observer(() => {
|
||||||
// state
|
// state
|
||||||
@@ -22,7 +21,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { paginationInfo } = useWorkspaceDraftIssues();
|
const { paginationInfo } = useWorkspaceDraftIssues();
|
||||||
const { joinedProjectIds } = useProject();
|
const { joinedProjectIds } = useProject();
|
||||||
// check if user is authorized to create draft issue
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// check if user is authorized to create draft work item
|
||||||
const isAuthorizedUser = allowPermissions(
|
const isAuthorizedUser = allowPermissions(
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
EUserPermissionsLevel.WORKSPACE
|
EUserPermissionsLevel.WORKSPACE
|
||||||
@@ -42,7 +43,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={<BreadcrumbLink label={`Drafts`} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />}
|
link={
|
||||||
|
<BreadcrumbLink label={t("drafts")} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
{paginationInfo?.total_count && paginationInfo?.total_count > 0 ? (
|
{paginationInfo?.total_count && paginationInfo?.total_count > 0 ? (
|
||||||
@@ -62,7 +65,7 @@ export const WorkspaceDraftHeader = observer(() => {
|
|||||||
onClick={() => setIsDraftIssueModalOpen(true)}
|
onClick={() => setIsDraftIssueModalOpen(true)}
|
||||||
disabled={!isAuthorizedUser}
|
disabled={!isAuthorizedUser}
|
||||||
>
|
>
|
||||||
Draft<span className="hidden sm:inline-block"> an issue</span>
|
{t("workspace_draft_issues.draft_an_issue")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Header.RightItem>
|
</Header.RightItem>
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import { Home } from "lucide-react";
|
|||||||
import githubBlackImage from "/public/logos/github-black.png";
|
import githubBlackImage from "/public/logos/github-black.png";
|
||||||
import githubWhiteImage from "/public/logos/github-white.png";
|
import githubWhiteImage from "/public/logos/github-white.png";
|
||||||
// ui
|
// ui
|
||||||
|
import { GITHUB_REDIRECTED } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Breadcrumbs, Header } from "@plane/ui";
|
import { Breadcrumbs, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
// constants
|
// constants
|
||||||
import { GITHUB_REDIRECTED } from "@/constants/event-tracker";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { useEventTracker } from "@/hooks/store";
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
// hooks
|
// hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -28,7 +30,9 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
|
link={
|
||||||
|
<BreadcrumbLink label={t("home.title")} icon={<Home className="h-4 w-4 text-custom-text-300" />} />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +55,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
width={16}
|
width={16}
|
||||||
alt="GitHub Logo"
|
alt="GitHub Logo"
|
||||||
/>
|
/>
|
||||||
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
|
<span className="hidden text-xs font-medium sm:hidden md:block">{t("home.star_us_on_github")}</span>
|
||||||
</a>
|
</a>
|
||||||
</Header.RightItem>
|
</Header.RightItem>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// plane imports
|
||||||
|
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// components
|
// components
|
||||||
import { LogoSpinner } from "@/components/common";
|
import { LogoSpinner } from "@/components/common";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { SimpleEmptyState } from "@/components/empty-state";
|
||||||
import { InboxContentRoot } from "@/components/inbox";
|
import { InboxContentRoot } from "@/components/inbox";
|
||||||
import { IssuePeekOverview } from "@/components/issues";
|
import { IssuePeekOverview } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||||
|
|
||||||
const WorkspaceDashboardPage = observer(() => {
|
const WorkspaceDashboardPage = observer(() => {
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
|
// plane hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// hooks
|
// hooks
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const {
|
const {
|
||||||
@@ -31,11 +34,14 @@ const WorkspaceDashboardPage = observer(() => {
|
|||||||
const { fetchUserProjectInfo } = useUserPermissions();
|
const { fetchUserProjectInfo } = useUserPermissions();
|
||||||
const { setPeekIssue } = useIssueDetail();
|
const { setPeekIssue } = useIssueDetail();
|
||||||
// derived values
|
// derived values
|
||||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined;
|
const pageTitle = currentWorkspace?.name
|
||||||
|
? t("notification.page_label", { workspace: currentWorkspace?.name })
|
||||||
|
: undefined;
|
||||||
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
||||||
notificationLiteByNotificationId(currentSelectedNotificationId);
|
notificationLiteByNotificationId(currentSelectedNotificationId);
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
|
||||||
|
|
||||||
// fetching workspace issue properties
|
// fetching workspace work item properties
|
||||||
useWorkspaceIssueProperties(workspaceSlug);
|
useWorkspaceIssueProperties(workspaceSlug);
|
||||||
|
|
||||||
// fetch workspace notifications
|
// fetch workspace notifications
|
||||||
@@ -82,7 +88,7 @@ const WorkspaceDashboardPage = observer(() => {
|
|||||||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||||
{!currentSelectedNotificationId ? (
|
{!currentSelectedNotificationId ? (
|
||||||
<div className="w-full h-screen flex justify-center items-center">
|
<div className="w-full h-screen flex justify-center items-center">
|
||||||
<EmptyState type={EmptyStateType.NOTIFICATION_DETAIL_EMPTY_STATE} layout="screen-simple" />
|
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// components
|
// components
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
|
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
|
||||||
import { WorkspaceHomeView } from "@/components/home";
|
import { WorkspaceHomeView } from "@/components/home";
|
||||||
// hooks
|
// hooks
|
||||||
@@ -11,8 +12,9 @@ import { WorkspaceDashboardHeader } from "./header";
|
|||||||
|
|
||||||
const WorkspaceDashboardPage = observer(() => {
|
const WorkspaceDashboardPage = observer(() => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
|
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - ${t("home.title")}` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// ui
|
// ui
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
@@ -10,7 +12,6 @@ import { DownloadActivityButton, WorkspaceActivityListPage } from "@/components/
|
|||||||
// hooks
|
// hooks
|
||||||
import { useUserPermissions } from "@/hooks/store";
|
import { useUserPermissions } from "@/hooks/store";
|
||||||
// plane-web constants
|
// plane-web constants
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const PER_PAGE = 100;
|
const PER_PAGE = 100;
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ const ProfileActivityPage = observer(() => {
|
|||||||
const [resultsCount, setResultsCount] = useState(0);
|
const [resultsCount, setResultsCount] = useState(0);
|
||||||
// router
|
// router
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
//hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ const ProfileActivityPage = observer(() => {
|
|||||||
<PageHead title="Profile - Activity" />
|
<PageHead title="Profile - Activity" />
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden py-5">
|
<div className="flex h-full w-full flex-col overflow-hidden py-5">
|
||||||
<div className="flex items-center justify-between gap-2 px-5 md:px-9">
|
<div className="flex items-center justify-between gap-2 px-5 md:px-9">
|
||||||
<h3 className="text-lg font-medium">Recent activity</h3>
|
<h3 className="text-lg font-medium">{t("profile.stats.recent_activity.title")}</h3>
|
||||||
{canDownloadActivity && <DownloadActivityButton />}
|
{canDownloadActivity && <DownloadActivityButton />}
|
||||||
</div>
|
</div>
|
||||||
<div className="vertical-scrollbar scrollbar-md flex h-full flex-col overflow-y-auto px-5 md:px-9">
|
<div className="vertical-scrollbar scrollbar-md flex h-full flex-col overflow-y-auto px-5 md:px-9">
|
||||||
@@ -58,7 +61,7 @@ const ProfileActivityPage = observer(() => {
|
|||||||
{pageCount < totalPages && resultsCount !== 0 && (
|
{pageCount < totalPages && resultsCount !== 0 && (
|
||||||
<div className="flex w-full items-center justify-center text-xs">
|
<div className="flex w-full items-center justify-center text-xs">
|
||||||
<Button variant="accent-primary" size="sm" onClick={handleLoadMore}>
|
<Button variant="accent-primary" size="sm" onClick={handleLoadMore}>
|
||||||
Load more
|
{t("common.load_more")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import { observer } from "mobx-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { ChevronDown, PanelRight } from "lucide-react";
|
import { ChevronDown, PanelRight } from "lucide-react";
|
||||||
|
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IUserProfileProjectSegregation } from "@plane/types";
|
import { IUserProfileProjectSegregation } from "@plane/types";
|
||||||
import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui";
|
import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
// components
|
// components
|
||||||
import { ProfileIssuesFilter } from "@/components/profile";
|
import { ProfileIssuesFilter } from "@/components/profile";
|
||||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store";
|
import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
type TUserProfileHeader = {
|
type TUserProfileHeader = {
|
||||||
userProjectsData: IUserProfileProjectSegregation | undefined;
|
userProjectsData: IUserProfileProjectSegregation | undefined;
|
||||||
@@ -30,6 +30,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||||||
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
|
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||||
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
const isAuthorized = allowPermissions(
|
const isAuthorized = allowPermissions(
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
@@ -44,7 +45,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||||||
|
|
||||||
const isCurrentUser = currentUser?.id === userId;
|
const isCurrentUser = currentUser?.id === userId;
|
||||||
|
|
||||||
const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`;
|
const breadcrumbLabel = isCurrentUser ? t("profile.page_label") : `${userName} ${t("profile.work")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header>
|
<Header>
|
||||||
@@ -86,7 +87,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
|||||||
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
|
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
|
||||||
className="w-full text-custom-text-300"
|
className="w-full text-custom-text-300"
|
||||||
>
|
>
|
||||||
{tab.label}
|
{t(tab.i18n_label)}
|
||||||
</Link>
|
</Link>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ import { observer } from "mobx-react";
|
|||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
|
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||||
import { ProfileSidebar } from "@/components/profile";
|
import { ProfileSidebar } from "@/components/profile";
|
||||||
// constants
|
// constants
|
||||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys";
|
import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys";
|
||||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useUserPermissions } from "@/hooks/store";
|
import { useUserPermissions } from "@/hooks/store";
|
||||||
import useSize from "@/hooks/use-window-size";
|
import useSize from "@/hooks/use-window-size";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
// local components
|
// local components
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
import { UserProfileHeader } from "./header";
|
import { UserProfileHeader } from "./header";
|
||||||
@@ -66,7 +65,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
|||||||
<AppHeader
|
<AppHeader
|
||||||
header={
|
header={
|
||||||
<UserProfileHeader
|
<UserProfileHeader
|
||||||
type={currentTab?.label}
|
type={currentTab?.i18n_label}
|
||||||
userProjectsData={userProjectsData}
|
userProjectsData={userProjectsData}
|
||||||
showProfileIssuesFilter={isIssuesTab}
|
showProfileIssuesFilter={isIssuesTab}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,21 +6,30 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_LAYOUTS,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
} from "@plane/constants";
|
||||||
|
// plane i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useLabel } from "@/hooks/store";
|
import { useIssues, useLabel } from "@/hooks/store";
|
||||||
|
|
||||||
export const ProfileIssuesMobileHeader = observer(() => {
|
export const ProfileIssuesMobileHeader = observer(() => {
|
||||||
|
// plane i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, userId } = useParams();
|
const { workspaceSlug, userId } = useParams();
|
||||||
// store hook
|
// store hook
|
||||||
@@ -112,7 +121,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
customButton={
|
customButton={
|
||||||
<div className="flex flex-center text-sm text-custom-text-200">
|
<div className="flex flex-center text-sm text-custom-text-200">
|
||||||
Layout
|
{t("common.layout")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -129,19 +138,19 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<layout.icon className="h-3 w-3" />
|
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Filters"
|
title={t("common.filters")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<div className="flex flex-center text-sm text-custom-text-200">
|
<div className="flex flex-center text-sm text-custom-text-200">
|
||||||
Filters
|
{t("common.filters")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -149,7 +158,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||||||
>
|
>
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
@@ -163,18 +172,18 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Display"
|
title={t("common.display")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<div className="flex flex-center text-sm text-custom-text-200">
|
<div className="flex flex-center text-sm text-custom-text-200">
|
||||||
Display
|
{t("common.display")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import React from "react";
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
|
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
// constants
|
// constants
|
||||||
import { Header, EHeaderVariant } from "@plane/ui";
|
import { Header, EHeaderVariant } from "@plane/ui";
|
||||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isAuthorized: boolean;
|
isAuthorized: boolean;
|
||||||
@@ -33,7 +33,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
|||||||
: "border-transparent"
|
: "border-transparent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t(tab.label)}
|
{t(tab.i18n_label)}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// types
|
// types
|
||||||
|
import { GROUP_CHOICES } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { ContentWrapper } from "@plane/ui";
|
import { ContentWrapper } from "@plane/ui";
|
||||||
@@ -16,7 +18,6 @@ import {
|
|||||||
} from "@/components/profile";
|
} from "@/components/profile";
|
||||||
// constants
|
// constants
|
||||||
import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
|
import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
|
||||||
import { GROUP_CHOICES } from "@/constants/project";
|
|
||||||
// services
|
// services
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ const userService = new UserService();
|
|||||||
export default function ProfileOverviewPage() {
|
export default function ProfileOverviewPage() {
|
||||||
const { workspaceSlug, userId } = useParams();
|
const { workspaceSlug, userId } = useParams();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
const { data: userProfile } = useSWR(
|
const { data: userProfile } = useSWR(
|
||||||
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
||||||
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
||||||
@@ -40,7 +42,7 @@ export default function ProfileOverviewPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHead title="Your work" />
|
<PageHead title={t("profile.page_label")} />
|
||||||
<ContentWrapper className="space-y-7">
|
<ContentWrapper className="space-y-7">
|
||||||
<ProfileStats userProfile={userProfile} />
|
<ProfileStats userProfile={userProfile} />
|
||||||
<ProfileWorkload stateDistribution={stateDistribution} />
|
<ProfileWorkload stateDistribution={stateDistribution} />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const PROJECT_ARCHIVES_BREADCRUMB_LIST: {
|
|||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
issues: {
|
issues: {
|
||||||
label: "Issues",
|
label: "Work items",
|
||||||
href: "/issues",
|
href: "/issues",
|
||||||
icon: LayersIcon,
|
icon: LayersIcon,
|
||||||
},
|
},
|
||||||
@@ -92,7 +92,7 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
|
|||||||
{activeTab === "issues" && issueCount && issueCount > 0 ? (
|
{activeTab === "issues" && issueCount && issueCount > 0 ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's archived`}
|
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's archived`}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
>
|
>
|
||||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => {
|
|||||||
link={
|
link={
|
||||||
<BreadcrumbLink
|
<BreadcrumbLink
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
|
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
|
||||||
label="Issues"
|
label="Work items"
|
||||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const ProjectArchivedIssuesPage = observer(() => {
|
|||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
// derived values
|
// derived values
|
||||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||||
const pageTitle = project?.name && `${project?.name} - Archived issues`;
|
const pageTitle = project?.name && `${project?.name} - Archived work items`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,7 +7,16 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { ArrowRight, PanelRight } from "lucide-react";
|
import { ArrowRight, PanelRight } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
EUserPermissions,
|
||||||
|
EUserPermissionsLevel,
|
||||||
|
} from "@plane/constants";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
@@ -16,8 +25,6 @@ import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from "
|
|||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
@@ -39,7 +46,6 @@ import useLocalStorage from "@/hooks/use-local-storage";
|
|||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web
|
// plane web
|
||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
||||||
// router
|
// router
|
||||||
@@ -72,6 +78,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
cycleId: string;
|
cycleId: string;
|
||||||
};
|
};
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { issueFilters, updateFilters },
|
||||||
@@ -184,7 +192,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
type="text"
|
type="text"
|
||||||
link={
|
link={
|
||||||
<BreadcrumbLink
|
<BreadcrumbLink
|
||||||
label="Cycles"
|
label={t("common.cycles")}
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
||||||
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
||||||
/>
|
/>
|
||||||
@@ -203,7 +211,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
tooltipContent={`There are ${issuesCount} ${
|
tooltipContent={`There are ${issuesCount} ${
|
||||||
issuesCount > 1 ? "issues" : "issue"
|
issuesCount > 1 ? "work items" : "work item"
|
||||||
} in this cycle`}
|
} in this cycle`}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
>
|
>
|
||||||
@@ -239,7 +247,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
selectedLayout={activeLayout}
|
selectedLayout={activeLayout}
|
||||||
/>
|
/>
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Filters"
|
title={t("common.filters")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||||
>
|
>
|
||||||
@@ -247,7 +255,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -258,10 +266,10 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -276,18 +284,18 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
{canUserCreateIssue && (
|
{canUserCreateIssue && (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||||
Analytics
|
{t("common.analytics")}
|
||||||
</Button>
|
</Button>
|
||||||
{!isCompletedCycle && (
|
{!isCompletedCycle && (
|
||||||
<Button
|
<Button
|
||||||
className="h-full self-start"
|
className="h-full self-start"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Cycle issues page");
|
setTrackElement("Cycle work items page");
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Add issue
|
{t("issue.add.label")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,32 +5,42 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_LAYOUTS,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
} from "@plane/constants";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
|
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
|
||||||
|
|
||||||
export const CycleIssuesMobileHeader = () => {
|
export const CycleIssuesMobileHeader = () => {
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
const { getCycleById } = useCycle();
|
const { getCycleById } = useCycle();
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ key: "list", title: "List", icon: List },
|
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||||
{ key: "kanban", title: "Board", icon: Kanban },
|
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||||
];
|
];
|
||||||
|
|
||||||
const { workspaceSlug, projectId, cycleId } = useParams();
|
const { workspaceSlug, projectId, cycleId } = useParams();
|
||||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const {
|
const {
|
||||||
@@ -123,7 +133,9 @@ export const CycleIssuesMobileHeader = () => {
|
|||||||
maxHeight={"md"}
|
maxHeight={"md"}
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
customButton={
|
||||||
|
<span className="flex flex-grow justify-center text-custom-text-200 text-sm">{t("common.layout")}</span>
|
||||||
|
}
|
||||||
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
closeOnSelect
|
closeOnSelect
|
||||||
>
|
>
|
||||||
@@ -135,18 +147,18 @@ export const CycleIssuesMobileHeader = () => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<layout.icon className="w-3 h-3" />
|
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="w-3 h-3" />
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Filters"
|
title={t("common.filters")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
Filters
|
{t("common.filters")}
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -156,7 +168,7 @@ export const CycleIssuesMobileHeader = () => {
|
|||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -170,18 +182,18 @@ export const CycleIssuesMobileHeader = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Display"
|
title={t("common.display")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
Display
|
{t("common.display")}
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -198,7 +210,7 @@ export const CycleIssuesMobileHeader = () => {
|
|||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => setAnalyticsModal(true)}
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
||||||
>
|
>
|
||||||
Analytics
|
{t("common.analytics")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// ui
|
// ui
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
@@ -13,7 +15,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||||||
// plane web
|
// plane web
|
||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
// constants
|
// constants
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
export const CyclesListHeader: FC = observer(() => {
|
export const CyclesListHeader: FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@@ -23,6 +24,7 @@ export const CyclesListHeader: FC = observer(() => {
|
|||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement } = useEventTracker();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const canUserCreateCycle = allowPermissions(
|
const canUserCreateCycle = allowPermissions(
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
@@ -36,7 +38,12 @@ export const CyclesListHeader: FC = observer(() => {
|
|||||||
<ProjectBreadcrumb />
|
<ProjectBreadcrumb />
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={<BreadcrumbLink label="Cycles" icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />} />}
|
link={
|
||||||
|
<BreadcrumbLink
|
||||||
|
label={t("cycle.label", { count: 2 })}
|
||||||
|
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</Header.LeftItem>
|
</Header.LeftItem>
|
||||||
@@ -51,7 +58,8 @@ export const CyclesListHeader: FC = observer(() => {
|
|||||||
toggleCreateCycleModal(true);
|
toggleCreateCycleModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="hidden sm:block">Add</div> Cycle
|
<div className="sm:hidden block">{t("add")}</div>
|
||||||
|
<div className="hidden sm:block">{t("project_cycles.add_cycle")}</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Header.RightItem>
|
</Header.RightItem>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,20 +3,22 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// types
|
// plane imports
|
||||||
|
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TCycleFilters } from "@plane/types";
|
import { TCycleFilters } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { Header, EHeaderVariant } from "@plane/ui";
|
import { Header, EHeaderVariant } from "@plane/ui";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles";
|
import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { CycleModuleListLayout } from "@/components/ui";
|
import { CycleModuleListLayout } from "@/components/ui";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useCycle, useProject, useCycleFilter } from "@/hooks/store";
|
import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||||
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
const ProjectCyclesPage = observer(() => {
|
const ProjectCyclesPage = observer(() => {
|
||||||
// states
|
// states
|
||||||
@@ -26,13 +28,23 @@ const ProjectCyclesPage = observer(() => {
|
|||||||
const { currentProjectCycleIds, loader } = useCycle();
|
const { currentProjectCycleIds, loader } = useCycle();
|
||||||
const { getProjectById, currentProjectDetails } = useProject();
|
const { getProjectById, currentProjectDetails } = useProject();
|
||||||
// router
|
// router
|
||||||
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
// plane hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// cycle filters hook
|
// cycle filters hook
|
||||||
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
|
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const totalCycles = currentProjectCycleIds?.length ?? 0;
|
const totalCycles = currentProjectCycleIds?.length ?? 0;
|
||||||
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
|
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
|
||||||
const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined;
|
const pageTitle = project?.name ? `${project?.name} - ${t("cycles.label", { count: 2 })}` : undefined;
|
||||||
|
const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||||
|
const hasMemberLevelPermission = allowPermissions(
|
||||||
|
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||||
|
EUserPermissionsLevel.PROJECT
|
||||||
|
);
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" });
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
|
const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
@@ -50,9 +62,17 @@ const ProjectCyclesPage = observer(() => {
|
|||||||
if (currentProjectDetails?.cycle_view === false)
|
if (currentProjectDetails?.cycle_view === false)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.DISABLED_PROJECT_CYCLE}
|
title={t("disabled_project.empty_state.cycle.title")}
|
||||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
description={t("disabled_project.empty_state.cycle.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
primaryButton={{
|
||||||
|
text: t("disabled_project.empty_state.cycle.primary_button.text"),
|
||||||
|
onClick: () => {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||||
|
},
|
||||||
|
disabled: !hasAdminLevelPermission,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -71,12 +91,22 @@ const ProjectCyclesPage = observer(() => {
|
|||||||
/>
|
/>
|
||||||
{totalCycles === 0 ? (
|
{totalCycles === 0 ? (
|
||||||
<div className="h-full place-items-center">
|
<div className="h-full place-items-center">
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.PROJECT_CYCLES}
|
title={t("project_cycles.empty_state.general.title")}
|
||||||
primaryButtonOnClick={() => {
|
description={t("project_cycles.empty_state.general.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
customPrimaryButton={
|
||||||
|
<ComicBoxButton
|
||||||
|
label={t("project_cycles.empty_state.general.primary_button.text")}
|
||||||
|
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
||||||
|
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
||||||
|
onClick={() => {
|
||||||
setTrackElement("Cycle empty state");
|
setTrackElement("Cycle empty state");
|
||||||
setCreateModal(true);
|
setCreateModal(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!hasMemberLevelPermission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { FC, useCallback } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
@@ -12,8 +14,6 @@ import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
@@ -24,6 +24,8 @@ import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
|||||||
|
|
||||||
// FIXME: Deprecated. Remove it
|
// FIXME: Deprecated. Remove it
|
||||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||||
// store hooks
|
// store hooks
|
||||||
@@ -96,14 +98,17 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={
|
link={
|
||||||
<BreadcrumbLink label="Draft Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
|
<BreadcrumbLink
|
||||||
|
label="Draft work items"
|
||||||
|
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
{issueCount && issueCount > 0 ? (
|
{issueCount && issueCount > 0 ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's draft`}
|
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's draft`}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
>
|
>
|
||||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||||
@@ -119,14 +124,18 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||||||
onChange={(layout) => handleLayoutChange(layout)}
|
onChange={(layout) => handleLayoutChange(layout)}
|
||||||
selectedLayout={activeLayout}
|
selectedLayout={activeLayout}
|
||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isIssueFilterActive(issueFilters)}>
|
<FiltersDropdown
|
||||||
|
title={t("common.filters")}
|
||||||
|
placement="bottom-end"
|
||||||
|
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||||
|
>
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels}
|
labels={projectLabels}
|
||||||
memberIds={projectMemberIds ?? undefined}
|
memberIds={projectMemberIds ?? undefined}
|
||||||
@@ -135,10 +144,10 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const ProjectDraftIssuesPage = observer(() => {
|
|||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
// derived values
|
// derived values
|
||||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||||
const pageTitle = project?.name ? `${project?.name} - Draft Issues` : undefined;
|
const pageTitle = project?.name ? `${project?.name} - Draft work items` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -30,7 +30,7 @@ const ProjectDraftIssuesPage = observer(() => {
|
|||||||
className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
|
className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs"
|
||||||
>
|
>
|
||||||
<PenSquare className="h-4 w-4" />
|
<PenSquare className="h-4 w-4" />
|
||||||
<span>Draft Issues</span>
|
<span>Draft work items</span>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,41 +2,62 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// components
|
// components
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
|
import { EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { EUserProjectRoles } from "@plane/constants/src/user";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { InboxIssueRoot } from "@/components/inbox";
|
import { InboxIssueRoot } from "@/components/inbox";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
const ProjectInboxPage = observer(() => {
|
const ProjectInboxPage = observer(() => {
|
||||||
/// router
|
/// router
|
||||||
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const navigationTab = searchParams.get("currentTab");
|
const navigationTab = searchParams.get("currentTab");
|
||||||
const inboxIssueId = searchParams.get("inboxIssueId");
|
const inboxIssueId = searchParams.get("inboxIssueId");
|
||||||
|
// plane hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// hooks
|
// hooks
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
// derived values
|
||||||
|
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" });
|
||||||
|
|
||||||
// No access to inbox
|
// No access to inbox
|
||||||
if (currentProjectDetails?.inbox_view === false)
|
if (currentProjectDetails?.inbox_view === false)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.DISABLED_PROJECT_INBOX}
|
title={t("disabled_project.empty_state.inbox.title")}
|
||||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
description={t("disabled_project.empty_state.inbox.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
primaryButton={{
|
||||||
|
text: t("disabled_project.empty_state.inbox.primary_button.text"),
|
||||||
|
onClick: () => {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||||
|
},
|
||||||
|
disabled: !canPerformEmptyStateActions,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Intake` : "Plane - Intake";
|
const pageTitle = currentProjectDetails?.name
|
||||||
|
? t("inbox_issue.page_label", {
|
||||||
|
workspace: currentProjectDetails?.name,
|
||||||
|
})
|
||||||
|
: t("inbox_issue.page_label", {
|
||||||
|
workspace: "Plane",
|
||||||
|
});
|
||||||
|
|
||||||
const currentNavigationTab = navigationTab
|
const currentNavigationTab = navigationTab
|
||||||
? navigationTab === "open"
|
? navigationTab === "open"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@@ -19,6 +21,8 @@ import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
|
|||||||
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
|
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
|
||||||
|
|
||||||
const IssueDetailsPage = observer(() => {
|
const IssueDetailsPage = observer(() => {
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = useParams();
|
const { workspaceSlug, projectId, issueId } = useParams();
|
||||||
@@ -31,7 +35,7 @@ const IssueDetailsPage = observer(() => {
|
|||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
|
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
|
||||||
// fetching issue details
|
// fetching work item details
|
||||||
const { isLoading, error } = useSWR(
|
const { isLoading, error } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null,
|
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
@@ -64,10 +68,10 @@ const IssueDetailsPage = observer(() => {
|
|||||||
{error ? (
|
{error ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
|
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
|
||||||
title="Issue does not exist"
|
title={t("issue.empty_state.issue_detail.title")}
|
||||||
description="The issue you are looking for does not exist, has been archived, or has been deleted."
|
description={t("issue.empty_state.issue_detail.description")}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: "View other issues",
|
text: t("issue.empty_state.issue_detail.primary_button.text"),
|
||||||
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/issues`),
|
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/issues`),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, LayersIcon, Header } from "@plane/ui";
|
import { Breadcrumbs, LayersIcon, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@@ -14,6 +16,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
|
|
||||||
export const ProjectIssueDetailsHeader = observer(() => {
|
export const ProjectIssueDetailsHeader = observer(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
// router
|
// router
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = useParams();
|
const { workspaceSlug, projectId, issueId } = useParams();
|
||||||
@@ -37,7 +40,7 @@ export const ProjectIssueDetailsHeader = observer(() => {
|
|||||||
link={
|
link={
|
||||||
<BreadcrumbLink
|
<BreadcrumbLink
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||||
label="Issues"
|
label={t("issue.label", { count: 2 })} // count is for pluralization
|
||||||
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,39 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_LAYOUTS,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
} from "@plane/constants";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
import {
|
||||||
// constants
|
DisplayFiltersSelection,
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
FilterSelection,
|
||||||
|
FiltersDropdown,
|
||||||
|
IssueLayoutIcon,
|
||||||
|
} from "@/components/issues/issue-layouts";
|
||||||
// helpers
|
// helpers
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||||
|
|
||||||
export const ProjectIssuesMobileHeader = observer(() => {
|
export const ProjectIssuesMobileHeader = observer(() => {
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ key: "list", title: "List", icon: List },
|
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||||
{ key: "kanban", title: "Board", icon: Kanban },
|
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||||
];
|
];
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
const { workspaceSlug, projectId } = useParams() as {
|
const { workspaceSlug, projectId } = useParams() as {
|
||||||
@@ -104,7 +117,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
customButton={
|
customButton={
|
||||||
<div className="flex flex-start text-sm text-custom-text-200">
|
<div className="flex flex-start text-sm text-custom-text-200">
|
||||||
Layout
|
{t("common.layout")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -119,18 +132,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<layout.icon className="h-3 w-3" />
|
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Filters"
|
title={t("common.filters")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<span className="flex items-center text-sm text-custom-text-200">
|
<span className="flex items-center text-sm text-custom-text-200">
|
||||||
Filters
|
{t("common.filters")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -142,7 +155,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels}
|
labels={projectLabels}
|
||||||
memberIds={projectMemberIds ?? undefined}
|
memberIds={projectMemberIds ?? undefined}
|
||||||
@@ -154,18 +167,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||||
<FiltersDropdown
|
<FiltersDropdown
|
||||||
title="Display"
|
title={t("common.display")}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
menuButton={
|
menuButton={
|
||||||
<span className="flex items-center text-sm text-custom-text-200">
|
<span className="flex items-center text-sm text-custom-text-200">
|
||||||
Display
|
{t("common.display")}
|
||||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -181,7 +194,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
|||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => setAnalyticsModal(true)}
|
||||||
className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200"
|
className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200"
|
||||||
>
|
>
|
||||||
Analytics
|
{t("common.analytics")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
// i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// components
|
// components
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { ProjectLayoutRoot } from "@/components/issues";
|
import { ProjectLayoutRoot } from "@/components/issues";
|
||||||
@@ -11,6 +13,8 @@ import { useProject } from "@/hooks/store";
|
|||||||
|
|
||||||
const ProjectIssuesPage = observer(() => {
|
const ProjectIssuesPage = observer(() => {
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
|
// i18n
|
||||||
|
const { t } = useTranslation();
|
||||||
// store
|
// store
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
|
|
||||||
@@ -20,13 +24,15 @@ const ProjectIssuesPage = observer(() => {
|
|||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const project = getProjectById(projectId.toString());
|
const project = getProjectById(projectId.toString());
|
||||||
const pageTitle = project?.name ? `${project?.name} - Issues` : undefined;
|
const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHead title={pageTitle} />
|
<PageHead title={pageTitle} />
|
||||||
<Head>
|
<Head>
|
||||||
<title>{project?.name} - Issues</title>
|
<title>
|
||||||
|
{project?.name} - {t("issue.label", { count: 2 })}
|
||||||
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<ProjectLayoutRoot />
|
<ProjectLayoutRoot />
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { ArrowRight, PanelRight } from "lucide-react";
|
import { ArrowRight, PanelRight } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssuesStoreType, EIssueFilterType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssuesStoreType,
|
||||||
|
EIssueFilterType,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
EUserPermissions,
|
||||||
|
EUserPermissionsLevel,
|
||||||
|
} from "@plane/constants";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
@@ -16,8 +23,6 @@ import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@pla
|
|||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
// constants
|
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
@@ -40,7 +45,6 @@ import useLocalStorage from "@/hooks/use-local-storage";
|
|||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web
|
// plane web
|
||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
||||||
// router
|
// router
|
||||||
@@ -202,7 +206,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
tooltipContent={`There are ${issuesCount} ${
|
tooltipContent={`There are ${issuesCount} ${
|
||||||
issuesCount > 1 ? "issues" : "issue"
|
issuesCount > 1 ? "work items" : "work item"
|
||||||
} in this module`}
|
} in this module`}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
>
|
>
|
||||||
@@ -247,7 +251,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels}
|
labels={projectLabels}
|
||||||
memberIds={projectMemberIds ?? undefined}
|
memberIds={projectMemberIds ?? undefined}
|
||||||
@@ -259,7 +263,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
<FiltersDropdown title="Display" placement="bottom-end">
|
<FiltersDropdown title="Display" placement="bottom-end">
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
@@ -285,12 +289,12 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
<Button
|
<Button
|
||||||
className="hidden sm:flex"
|
className="hidden sm:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Module issues page");
|
setTrackElement("Module work items page");
|
||||||
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
|
||||||
}}
|
}}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Add issue
|
Add work item
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,16 +6,27 @@ import { useParams } from "next/navigation";
|
|||||||
// icons
|
// icons
|
||||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
import {
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_LAYOUTS,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||||
|
} from "@plane/constants";
|
||||||
|
// plane i18n
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
import {
|
||||||
// constants
|
DisplayFiltersSelection,
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
FilterSelection,
|
||||||
|
FiltersDropdown,
|
||||||
|
IssueLayoutIcon,
|
||||||
|
} from "@/components/issues/issue-layouts";
|
||||||
// helpers
|
// helpers
|
||||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
@@ -25,10 +36,11 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { getModuleById } = useModule();
|
const { getModuleById } = useModule();
|
||||||
|
const { t } = useTranslation();
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ key: "list", title: "List", icon: List },
|
{ key: "list", i18n_title: "issue.layouts.list", icon: List },
|
||||||
{ key: "kanban", title: "Board", icon: Kanban },
|
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: Kanban },
|
||||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar },
|
||||||
];
|
];
|
||||||
const { workspaceSlug, projectId, moduleId } = useParams() as {
|
const { workspaceSlug, projectId, moduleId } = useParams() as {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@@ -116,8 +128,8 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<layout.icon className="h-3 w-3" />
|
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
@@ -139,7 +151,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
labels={projectLabels}
|
labels={projectLabels}
|
||||||
memberIds={projectMemberIds ?? undefined}
|
memberIds={projectMemberIds ?? undefined}
|
||||||
@@ -162,7 +174,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
|||||||
>
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||||
}
|
}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
// plane imports
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@@ -12,7 +15,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||||||
// plane web
|
// plane web
|
||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
// constants
|
// constants
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
export const ModulesListHeader: React.FC = observer(() => {
|
export const ModulesListHeader: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@@ -24,6 +26,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||||||
|
|
||||||
const { loader } = useProject();
|
const { loader } = useProject();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
const canUserCreateModule = allowPermissions(
|
const canUserCreateModule = allowPermissions(
|
||||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||||
@@ -38,7 +42,9 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||||||
<ProjectBreadcrumb />
|
<ProjectBreadcrumb />
|
||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={<BreadcrumbLink label="Modules" icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />} />}
|
link={
|
||||||
|
<BreadcrumbLink label={t("modules")} icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />} />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +60,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||||||
toggleCreateModuleModal(true);
|
toggleCreateModuleModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="hidden sm:block">Add</div> Module
|
<div className="sm:hidden block">{t("add")}</div>
|
||||||
|
<div className="hidden sm:block">{t("project_module.add_module")}</div>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|||||||
@@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
|
import { MODULE_VIEW_LAYOUTS } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { CustomMenu, Row } from "@plane/ui";
|
import { CustomMenu, Row } from "@plane/ui";
|
||||||
import { MODULE_VIEW_LAYOUTS } from "@/constants/module";
|
import { ModuleLayoutIcon } from "@/components/modules";
|
||||||
import { useModuleFilter, useProject } from "@/hooks/store";
|
import { useModuleFilter, useProject } from "@/hooks/store";
|
||||||
|
|
||||||
export const ModulesListMobileHeader = observer(() => {
|
export const ModulesListMobileHeader = observer(() => {
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { updateDisplayFilters } = useModuleFilter();
|
const { updateDisplayFilters } = useModuleFilter();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-start md:hidden">
|
<div className="flex justify-start md:hidden">
|
||||||
@@ -34,8 +37,8 @@ export const ModulesListMobileHeader = observer(() => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<layout.icon className="w-3 h-3" />
|
<ModuleLayoutIcon layoutType={layout.key} />
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -4,27 +4,36 @@ import { useCallback } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// types
|
// types
|
||||||
|
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TModuleFilters } from "@plane/types";
|
import { TModuleFilters } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
|
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useModuleFilter, useProject } from "@/hooks/store";
|
import { useModuleFilter, useProject, useUserPermissions } from "@/hooks/store";
|
||||||
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
const ProjectModulesPage = observer(() => {
|
const ProjectModulesPage = observer(() => {
|
||||||
|
// router
|
||||||
|
const router = useAppRouter();
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
// plane hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// store
|
// store
|
||||||
const { getProjectById, currentProjectDetails } = useProject();
|
const { getProjectById, currentProjectDetails } = useProject();
|
||||||
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
|
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
|
||||||
useModuleFilter();
|
useModuleFilter();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||||
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
|
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
|
||||||
|
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" });
|
||||||
|
|
||||||
const handleRemoveFilter = useCallback(
|
const handleRemoveFilter = useCallback(
|
||||||
(key: keyof TModuleFilters, value: string | null) => {
|
(key: keyof TModuleFilters, value: string | null) => {
|
||||||
@@ -45,9 +54,17 @@ const ProjectModulesPage = observer(() => {
|
|||||||
if (currentProjectDetails?.module_view === false)
|
if (currentProjectDetails?.module_view === false)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.DISABLED_PROJECT_MODULE}
|
title={t("disabled_project.empty_state.module.title")}
|
||||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
description={t("disabled_project.empty_state.module.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
primaryButton={{
|
||||||
|
text: t("disabled_project.empty_state.module.primary_button.text"),
|
||||||
|
onClick: () => {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||||
|
},
|
||||||
|
disabled: !canPerformEmptyStateActions,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { useState } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { FileText } from "lucide-react";
|
import { FileText } from "lucide-react";
|
||||||
|
// constants
|
||||||
|
import { EPageAccess } from "@plane/constants";
|
||||||
// plane types
|
// plane types
|
||||||
import { TPage } from "@plane/types";
|
import { TPage } from "@plane/types";
|
||||||
// plane ui
|
// plane ui
|
||||||
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
// constants
|
|
||||||
import { EPageAccess } from "@/constants/page";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject, useProjectPages } from "@/hooks/store";
|
import { useEventTracker, useProject, useProjectPages } from "@/hooks/store";
|
||||||
// plane web
|
// plane web
|
||||||
|
|||||||
@@ -2,27 +2,35 @@
|
|||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
// types
|
// plane imports
|
||||||
|
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TPageNavigationTabs } from "@plane/types";
|
import { TPageNavigationTabs } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { DetailedEmptyState } from "@/components/empty-state";
|
||||||
import { PagesListRoot, PagesListView } from "@/components/pages";
|
import { PagesListRoot, PagesListView } from "@/components/pages";
|
||||||
// constants
|
|
||||||
import { EmptyStateType } from "@/constants/empty-state";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||||
|
|
||||||
const ProjectPagesPage = observer(() => {
|
const ProjectPagesPage = observer(() => {
|
||||||
// router
|
// router
|
||||||
|
const router = useAppRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const type = searchParams.get("type");
|
const type = searchParams.get("type");
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
// plane hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getProjectById, currentProjectDetails } = useProject();
|
const { getProjectById, currentProjectDetails } = useProject();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
// derived values
|
// derived values
|
||||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||||
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
||||||
|
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||||
|
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" });
|
||||||
|
|
||||||
const currentPageType = (): TPageNavigationTabs => {
|
const currentPageType = (): TPageNavigationTabs => {
|
||||||
const pageType = type?.toString();
|
const pageType = type?.toString();
|
||||||
@@ -37,9 +45,17 @@ const ProjectPagesPage = observer(() => {
|
|||||||
if (currentProjectDetails?.page_view === false)
|
if (currentProjectDetails?.page_view === false)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<EmptyState
|
<DetailedEmptyState
|
||||||
type={EmptyStateType.DISABLED_PROJECT_PAGE}
|
title={t("disabled_project.empty_state.page.title")}
|
||||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
description={t("disabled_project.empty_state.page.description")}
|
||||||
|
assetPath={resolvedPath}
|
||||||
|
primaryButton={{
|
||||||
|
text: t("disabled_project.empty_state.page.primary_button.text"),
|
||||||
|
onClick: () => {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||||
|
},
|
||||||
|
disabled: !canPerformEmptyStateActions,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IProject } from "@plane/types";
|
import { IProject } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
@@ -12,7 +14,6 @@ import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automat
|
|||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const AutomationSettingsPage = observer(() => {
|
const AutomationSettingsPage = observer(() => {
|
||||||
// router
|
// router
|
||||||
@@ -21,6 +22,8 @@ const AutomationSettingsPage = observer(() => {
|
|||||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||||
const { currentProjectDetails: projectDetails, updateProject } = useProject();
|
const { currentProjectDetails: projectDetails, updateProject } = useProject();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ const AutomationSettingsPage = observer(() => {
|
|||||||
<PageHead title={pageTitle} />
|
<PageHead title={pageTitle} />
|
||||||
<section className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
<section className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
||||||
<div className="flex flex-col items-start border-b border-custom-border-100 pb-3.5">
|
<div className="flex flex-col items-start border-b border-custom-border-100 pb-3.5">
|
||||||
<h3 className="text-xl font-medium leading-normal">Automations</h3>
|
<h3 className="text-xl font-medium leading-normal">{t("project_settings.automations.label")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<AutoArchiveAutomation handleChange={handleChange} />
|
<AutoArchiveAutomation handleChange={handleChange} />
|
||||||
<AutoCloseAutomation handleChange={handleChange} />
|
<AutoCloseAutomation handleChange={handleChange} />
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { EstimateRoot } from "@/components/estimates";
|
import { EstimateRoot } from "@/components/estimates";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const EstimatesSettingsPage = observer(() => {
|
const EstimatesSettingsPage = observer(() => {
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { ProjectFeaturesList } from "@/components/project";
|
import { ProjectFeaturesList } from "@/components/project";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const FeaturesSettingsPage = observer(() => {
|
const FeaturesSettingsPage = observer(() => {
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// ui
|
// ui
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
|
import { EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Breadcrumbs, CustomMenu, Header } from "@plane/ui";
|
import { Breadcrumbs, CustomMenu, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink } from "@/components/common";
|
||||||
@@ -14,7 +16,6 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||||||
// plane web
|
// plane web
|
||||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||||
import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project";
|
import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project";
|
||||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
export const ProjectSettingHeader: FC = observer(() => {
|
export const ProjectSettingHeader: FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@@ -24,6 +25,8 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { loader } = useProject();
|
const { loader } = useProject();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header>
|
<Header>
|
||||||
<Header.LeftItem>
|
<Header.LeftItem>
|
||||||
@@ -65,7 +68,7 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||||||
key={item.key}
|
key={item.key}
|
||||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
|
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
|
||||||
>
|
>
|
||||||
{item.label}
|
{t(item.i18n_label)}
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|||||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// components
|
// components
|
||||||
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { ProjectSettingsLabelList } from "@/components/labels";
|
import { ProjectSettingsLabelList } from "@/components/labels";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
|
|
||||||
const LabelsSettingsPage = observer(() => {
|
const LabelsSettingsPage = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user