mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
13 Commits
enum-to-ob
...
chore-time
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f56caa414 | ||
|
|
ca02b0d10e | ||
|
|
eaf8ce0ed2 | ||
|
|
a4450d34b4 | ||
|
|
7f19caf5dc | ||
|
|
3ec71d6607 | ||
|
|
571c3f6212 | ||
|
|
9a3fb8b9ee | ||
|
|
385be31ff1 | ||
|
|
2e022a4ed7 | ||
|
|
0e110f3fd8 | ||
|
|
9093b41132 | ||
|
|
ad2dc6759a |
@@ -2,3 +2,56 @@ export enum E_SORT_ORDER {
|
||||
ASC = "asc",
|
||||
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",
|
||||
},
|
||||
];
|
||||
@@ -11,6 +11,12 @@ export * from "./issue";
|
||||
export * from "./metadata";
|
||||
export * from "./state";
|
||||
export * from "./swr";
|
||||
export * from "./tab-indices";
|
||||
export * from "./user";
|
||||
export * from "./workspace";
|
||||
export * from "./stickies";
|
||||
export * from "./project";
|
||||
export * from "./views";
|
||||
export * from "./inbox";
|
||||
export * from "./profile";
|
||||
export * from "./workspace-drafts";
|
||||
|
||||
@@ -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);
|
||||
@@ -1,48 +1,38 @@
|
||||
import React from "react";
|
||||
// icons
|
||||
import { Activity, Bell, CircleUser, KeyRound, LucideProps, Settings2 } from "lucide-react";
|
||||
|
||||
export const PROFILE_ACTION_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
href: string;
|
||||
highlight: (pathname: string) => boolean;
|
||||
Icon: React.FC<LucideProps>;
|
||||
}[] = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "Profile",
|
||||
i18n_label: "profile.actions.profile",
|
||||
href: `/profile`,
|
||||
highlight: (pathname: string) => pathname === "/profile/",
|
||||
Icon: CircleUser,
|
||||
},
|
||||
{
|
||||
key: "security",
|
||||
label: "Security",
|
||||
i18n_label: "profile.actions.security",
|
||||
href: `/profile/security`,
|
||||
highlight: (pathname: string) => pathname === "/profile/security/",
|
||||
Icon: KeyRound,
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
label: "Activity",
|
||||
i18n_label: "profile.actions.activity",
|
||||
href: `/profile/activity`,
|
||||
highlight: (pathname: string) => pathname === "/profile/activity/",
|
||||
Icon: Activity,
|
||||
},
|
||||
{
|
||||
key: "appearance",
|
||||
label: "Appearance",
|
||||
i18n_label: "profile.actions.appearance",
|
||||
href: `/profile/appearance`,
|
||||
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
|
||||
Icon: Settings2,
|
||||
},
|
||||
{
|
||||
key: "notifications",
|
||||
label: "Notifications",
|
||||
i18n_label: "profile.actions.notifications",
|
||||
href: `/profile/notifications`,
|
||||
highlight: (pathname: string) => pathname === "/profile/notifications/",
|
||||
Icon: Bell,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -50,7 +40,7 @@ export const PROFILE_VIEWER_TAB = [
|
||||
{
|
||||
key: "summary",
|
||||
route: "",
|
||||
label: "Summary",
|
||||
i18n_label: "profile.tabs.summary",
|
||||
selected: "/",
|
||||
},
|
||||
];
|
||||
@@ -59,24 +49,25 @@ export const PROFILE_ADMINS_TAB = [
|
||||
{
|
||||
key: "assigned",
|
||||
route: "assigned",
|
||||
label: "Assigned",
|
||||
i18n_label: "profile.tabs.assigned",
|
||||
selected: "/assigned/",
|
||||
},
|
||||
{
|
||||
key: "created",
|
||||
route: "created",
|
||||
label: "Created",
|
||||
i18n_label: "profile.tabs.created",
|
||||
selected: "/created/",
|
||||
},
|
||||
{
|
||||
key: "subscribed",
|
||||
route: "subscribed",
|
||||
label: "Subscribed",
|
||||
i18n_label: "profile.tabs.subscribed",
|
||||
selected: "/subscribed/",
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
route: "activity",
|
||||
label: "Activity",
|
||||
i18n_label: "profile.tabs.activity",
|
||||
selected: "/activity/",
|
||||
},
|
||||
];
|
||||
@@ -1,41 +1,59 @@
|
||||
// icons
|
||||
import { Globe2, Lock, LucideIcon } from "lucide-react";
|
||||
import { TProjectAppliedDisplayFilterKeys, TProjectOrderByOptions } from "@plane/types";
|
||||
import {
|
||||
TProjectAppliedDisplayFilterKeys,
|
||||
TProjectOrderByOptions,
|
||||
} from "@plane/types";
|
||||
|
||||
export const NETWORK_CHOICES: {
|
||||
key: 0 | 2;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
}[] = [
|
||||
{
|
||||
key: 0,
|
||||
label: "Private",
|
||||
description: "Accessible only by invite",
|
||||
i18n_label: "workspace_projects.network.private.title",
|
||||
description: "workspace_projects.network.private.description", //"Accessible only by invite",
|
||||
icon: Lock,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
label: "Public",
|
||||
description: "Anyone in the workspace except Guests can join",
|
||||
i18n_label: "workspace_projects.network.public.title",
|
||||
description: "workspace_projects.network.public.description", //"Anyone in the workspace except Guests can join",
|
||||
icon: Globe2,
|
||||
},
|
||||
];
|
||||
|
||||
export const GROUP_CHOICES = {
|
||||
backlog: "Backlog",
|
||||
unstarted: "Unstarted",
|
||||
started: "Started",
|
||||
completed: "Completed",
|
||||
cancelled: "Cancelled",
|
||||
backlog: {
|
||||
key: "backlog",
|
||||
i18n_label: "workspace_projects.state.backlog",
|
||||
},
|
||||
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 = [
|
||||
{ label: "1 month", value: 1 },
|
||||
{ label: "3 months", value: 3 },
|
||||
{ label: "6 months", value: 6 },
|
||||
{ label: "9 months", value: 9 },
|
||||
{ label: "12 months", value: 12 },
|
||||
{ i18n_label: "common.months_count", value: 1 },
|
||||
{ i18n_label: "common.months_count", value: 3 },
|
||||
{ i18n_label: "common.months_count", value: 6 },
|
||||
{ i18n_label: "common.months_count", value: 9 },
|
||||
{ i18n_label: "common.months_count", value: 12 },
|
||||
];
|
||||
|
||||
export const PROJECT_UNSPLASH_COVERS = [
|
||||
@@ -59,55 +77,55 @@ export const PROJECT_UNSPLASH_COVERS = [
|
||||
|
||||
export const PROJECT_ORDER_BY_OPTIONS: {
|
||||
key: TProjectOrderByOptions;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "sort_order",
|
||||
label: "Manual",
|
||||
i18n_label: "workspace_projects.sort.manual",
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
i18n_label: "workspace_projects.sort.name",
|
||||
},
|
||||
{
|
||||
key: "created_at",
|
||||
label: "Created date",
|
||||
i18n_label: "workspace_projects.sort.created_at",
|
||||
},
|
||||
{
|
||||
key: "members_length",
|
||||
label: "Number of members",
|
||||
i18n_label: "workspace_projects.sort.members_length",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_DISPLAY_FILTER_OPTIONS: {
|
||||
key: TProjectAppliedDisplayFilterKeys;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "my_projects",
|
||||
label: "My projects",
|
||||
i18n_label: "workspace_projects.scope.my_projects",
|
||||
},
|
||||
{
|
||||
key: "archived_projects",
|
||||
label: "Archived",
|
||||
i18n_label: "workspace_projects.scope.archived_projects",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_ERROR_MESSAGES = {
|
||||
permissionError: {
|
||||
title: "You don't have permission to perform this action.",
|
||||
message: undefined,
|
||||
i18n_title: "workspace_projects.error.permission",
|
||||
i18n_message: undefined,
|
||||
},
|
||||
cycleDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete cycle",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.cycle_delete",
|
||||
},
|
||||
moduleDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete module",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.module_delete",
|
||||
},
|
||||
issueDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete issue",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.issue_delete",
|
||||
},
|
||||
};
|
||||
@@ -54,7 +54,14 @@ export const PROJECT_CREATE_TAB_INDICES = [
|
||||
"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 = [
|
||||
"name",
|
||||
@@ -67,9 +74,21 @@ export const PROJECT_MODULE_TAB_INDICES = [
|
||||
"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 {
|
||||
ISSUE_FORM = "issue-form",
|
||||
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: "project_view.access.public" },
|
||||
{ key: EViewAccess.PRIVATE, i18n_label: "project_view.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 = [
|
||||
"Just myself", // TODO: translate
|
||||
"2-10",
|
||||
@@ -74,3 +77,182 @@ export const RESTRICTED_URLS = [
|
||||
"instances",
|
||||
"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.webhooks.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",
|
||||
},
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"add_members": "Ajouter des membres",
|
||||
"remove_members": "Supprimer des membres",
|
||||
"add": "Ajouter",
|
||||
"adding": "Ajout",
|
||||
"remove": "Supprimer",
|
||||
"add_new": "Ajouter un nouveau",
|
||||
"remove_selected": "Supprimer la sélection",
|
||||
@@ -38,7 +39,6 @@
|
||||
"deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées.",
|
||||
"profile_settings": "Paramètres du profil",
|
||||
"your_account": "Votre compte",
|
||||
"profile": "Profil",
|
||||
"security": " Sécurité",
|
||||
"activity": "Activité",
|
||||
"appearance": "Apparence",
|
||||
@@ -154,11 +154,9 @@
|
||||
"stay_ahead_of_blockers_description": "Repérez les défis d'un projet à l'autre et identifiez les dépendances entre cycles qui ne sont pas évidentes depuis d'autres vues.",
|
||||
"analytics": "Analyse",
|
||||
"workspace_invites": "Invitations de l'espace de travail",
|
||||
"workspace_settings": "Paramètres de l'espace de travail",
|
||||
"enter_god_mode": "Entrer en mode dieu",
|
||||
"workspace_logo": "Logo de l'espace de travail",
|
||||
"new_issue": "Nouveau problème",
|
||||
"home": "Accueil",
|
||||
"your_work": "Votre travail",
|
||||
"drafts": "Brouillons",
|
||||
"projects": "Projets",
|
||||
@@ -316,5 +314,969 @@
|
||||
"remove_parent_issue": "Supprimer le problème parent",
|
||||
"add_parent": "Ajouter un parent",
|
||||
"loading_members": "Chargement des membres...",
|
||||
"inbox": "Boîte de réception"
|
||||
"view_link_copied_to_clipboard": "Lien de vue copié dans le presse-papiers.",
|
||||
"required": "Requis",
|
||||
"optional": "Optionnel",
|
||||
"Cancel": "Annuler",
|
||||
"edit": "Modifier",
|
||||
"open_in_new_tab": "Ouvrir dans un nouvel onglet",
|
||||
"delete": "Supprimer",
|
||||
"good": "Bonjour",
|
||||
"morning": "matin",
|
||||
"afternoon": "après-midi",
|
||||
"evening": "soir",
|
||||
"show_all": "Tout afficher",
|
||||
"show_less": "Afficher moins",
|
||||
"no_data_yet": "Pas encore de données",
|
||||
"connections": "Connexions",
|
||||
|
||||
"project_view": {
|
||||
"access": {
|
||||
"public": "Public",
|
||||
"private": "Privé"
|
||||
},
|
||||
"sort_by": {
|
||||
"created_at": "Date de création",
|
||||
"updated_at": "Date de modification",
|
||||
"name": "Nom"
|
||||
}
|
||||
},
|
||||
|
||||
"toast": {
|
||||
"success": "Succès !",
|
||||
"error": "Erreur !"
|
||||
},
|
||||
|
||||
"home": {
|
||||
"empty": {
|
||||
"create_project": {
|
||||
"title": "Créer un projet",
|
||||
"description": "La plupart des choses commencent par un projet dans Plane.",
|
||||
"cta": "Commencer"
|
||||
},
|
||||
"invite_team": {
|
||||
"title": "Inviter votre équipe",
|
||||
"description": "Construisez, déployez et gérez avec vos collègues.",
|
||||
"cta": "Les faire entrer"
|
||||
},
|
||||
"configure_workspace": {
|
||||
"title": "Configurez votre espace de travail",
|
||||
"description": "Activez ou désactivez des fonctionnalités ou allez plus loin.",
|
||||
"cta": "Configurer cet espace"
|
||||
},
|
||||
"personalize_account": {
|
||||
"title": "Faites de Plane le vôtre",
|
||||
"description": "Choisissez votre photo, vos couleurs et plus encore.",
|
||||
"cta": "Personnaliser maintenant"
|
||||
},
|
||||
"widgets": {
|
||||
"title": "C'est calme sans widgets, activez-les",
|
||||
"description": "Il semble que tous vos widgets soient désactivés. Activez-les maintenant pour améliorer votre expérience !",
|
||||
"primary_button": {
|
||||
"text": "Gérer les widgets"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quick_links": {
|
||||
"empty": "Enregistrez des liens vers des éléments de travail que vous souhaitez avoir à portée de main.",
|
||||
"add": "Ajouter un lien rapide",
|
||||
"title": "Lien rapide",
|
||||
"title_plural": "Liens rapides",
|
||||
"toasts": {
|
||||
"created": {
|
||||
"title": "Lien créé",
|
||||
"message": "Le lien a été créé avec succès"
|
||||
},
|
||||
"not_created": {
|
||||
"title": "Lien non créé",
|
||||
"message": "Le lien n'a pas pu être créé"
|
||||
},
|
||||
"updated": {
|
||||
"title": "Lien mis à jour",
|
||||
"message": "Le lien a été mis à jour avec succès"
|
||||
},
|
||||
"not_updated": {
|
||||
"title": "Lien non mis à jour",
|
||||
"message": "Le lien n'a pas pu être mis à jour"
|
||||
},
|
||||
"removed": {
|
||||
"title": "Lien supprimé",
|
||||
"message": "Le lien a été supprimé avec succès"
|
||||
},
|
||||
"not_removed": {
|
||||
"title": "Lien non supprimé",
|
||||
"message": "Le lien n'a pas pu être supprimé"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recents": {
|
||||
"title": "Récents",
|
||||
"empty": {
|
||||
"project": "Vos projets récents apparaîtront ici une fois que vous en aurez visité un.",
|
||||
"page": "Vos pages récentes apparaîtront ici une fois que vous en aurez visité une.",
|
||||
"issue": "Vos problèmes récents apparaîtront ici une fois que vous en aurez visité un.",
|
||||
"default": "Vous n'avez pas encore d'éléments récents."
|
||||
},
|
||||
"filters": {
|
||||
"all": "Tous les éléments",
|
||||
"projects": "Projets",
|
||||
"pages": "Pages",
|
||||
"issues": "Problèmes"
|
||||
}
|
||||
},
|
||||
"my_stickies": {
|
||||
"title": "Vos notes"
|
||||
},
|
||||
"new_at_plane": {
|
||||
"title": "Nouveau chez Plane"
|
||||
},
|
||||
"quick_tutorial": {
|
||||
"title": "Tutoriel rapide"
|
||||
},
|
||||
"widget": {
|
||||
"reordered_successfully": "Widget réorganisé avec succès.",
|
||||
"reordering_failed": "Une erreur s'est produite lors de la réorganisation du widget."
|
||||
},
|
||||
"manage_widgets": "Gérer les widgets",
|
||||
"title": "Accueil",
|
||||
"star_us_on_github": "Étoilez-nous sur GitHub"
|
||||
},
|
||||
"link": {
|
||||
"modal": {
|
||||
"url": {
|
||||
"text": "URL",
|
||||
"required": "L'URL n'est pas valide",
|
||||
"placeholder": "Tapez ou collez une URL"
|
||||
},
|
||||
"title": {
|
||||
"text": "Titre d'affichage",
|
||||
"placeholder": "Comment souhaitez-vous voir ce lien"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"common": {
|
||||
"all": "Tout",
|
||||
"states": "États",
|
||||
"state": "État",
|
||||
"state_groups": "Groupes d'états",
|
||||
"priority": "Priorité",
|
||||
"team_project": "Projet d'équipe",
|
||||
"project": "Projet",
|
||||
"cycle": "Cycle",
|
||||
"cycles": "Cycles",
|
||||
"module": "Module",
|
||||
"modules": "Modules",
|
||||
"labels": "Étiquettes",
|
||||
"assignees": "Assignés",
|
||||
"assignee": "Assigné",
|
||||
"created_by": "Créé par",
|
||||
"none": "Aucun",
|
||||
"link": "Lien",
|
||||
"estimate": "Estimation",
|
||||
"layout": "Disposition",
|
||||
"filters": "Filtres",
|
||||
"display": "Affichage",
|
||||
"load_more": "Charger plus",
|
||||
"no_matches_found": "Aucun résultat trouvé",
|
||||
"activity": "Activité",
|
||||
"analytics": "Analyses",
|
||||
"success": "Succès",
|
||||
"error": "Erreur",
|
||||
"group_by": "Grouper par",
|
||||
"search": "Rechercher",
|
||||
"epic": "Épopée",
|
||||
"issue": "Problème",
|
||||
"warning": "Avertissement",
|
||||
"updating": "Mise à jour",
|
||||
"update": "Mettre à jour",
|
||||
"creating": "Création",
|
||||
"cancel": "Annuler",
|
||||
"description": "Description",
|
||||
"title": "Titre",
|
||||
"order_by": {
|
||||
"label": "Trier par",
|
||||
"manual": "Manuel",
|
||||
"last_created": "Dernière création",
|
||||
"last_updated": "Dernière mise à jour",
|
||||
"start_date": "Date de début",
|
||||
"due_date": "Date d'échéance",
|
||||
"asc": "Croissant",
|
||||
"desc": "Décroissant"
|
||||
},
|
||||
"sort": {
|
||||
"asc": "Croissant",
|
||||
"desc": "Décroissant"
|
||||
},
|
||||
"comments": "Commentaires",
|
||||
"updates": "Mises à jour"
|
||||
},
|
||||
|
||||
"form": {
|
||||
"title": {
|
||||
"required": "Le titre est requis",
|
||||
"max_length": "Le titre ne doit pas dépasser {length} caractères"
|
||||
}
|
||||
},
|
||||
|
||||
"entity": {
|
||||
"grouping_title": "Groupement de {entity}",
|
||||
"priority": "{entity}",
|
||||
"all": "Tous les {entity}",
|
||||
"drop_here_to_move": "Déposer ici pour déplacer {entity}"
|
||||
},
|
||||
|
||||
"epic": {
|
||||
"all": "Toutes les épopées",
|
||||
"label": "{count, plural, one {Épopée} other {Épopées}}"
|
||||
},
|
||||
|
||||
"issue": {
|
||||
"label": "{count, plural, one {Problème} other {Problèmes}}",
|
||||
"all": "Tous les problèmes",
|
||||
"add": "Ajouter un problème",
|
||||
"priority": {
|
||||
"urgent": "Urgent",
|
||||
"high": "Haute",
|
||||
"medium": "Moyenne",
|
||||
"low": "Basse"
|
||||
},
|
||||
"display": {
|
||||
"properties": {
|
||||
"label": "Propriétés d'affichage",
|
||||
"id": "ID",
|
||||
"issue_type": "Type de problème",
|
||||
"sub_issue_count": "Nombre de sous-problèmes",
|
||||
"attachment_count": "Nombre de pièces jointes"
|
||||
},
|
||||
"extra": {
|
||||
"show_sub_issues": "Afficher les sous-problèmes",
|
||||
"show_empty_groups": "Afficher les groupes vides"
|
||||
}
|
||||
},
|
||||
"layouts": {
|
||||
"ordered_by_label": "Cette disposition est triée par",
|
||||
"list": "Liste",
|
||||
"kanban": "Tableau",
|
||||
"calendar": "Calendrier",
|
||||
"spreadsheet": "Feuille de calcul",
|
||||
"gantt": "Chronologie",
|
||||
"title": {
|
||||
"list": "Disposition en liste",
|
||||
"kanban": "Disposition en tableau",
|
||||
"calendar": "Disposition en calendrier",
|
||||
"spreadsheet": "Disposition en feuille de calcul",
|
||||
"gantt": "Disposition en chronologie"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"active": "Actif",
|
||||
"backlog": "Backlog"
|
||||
},
|
||||
"comments": {
|
||||
"create": {
|
||||
"success": "Commentaire créé avec succès",
|
||||
"error": "Échec de la création du commentaire. Veuillez réessayer plus tard."
|
||||
},
|
||||
"update": {
|
||||
"success": "Commentaire mis à jour avec succès",
|
||||
"error": "Échec de la mise à jour du commentaire. Veuillez réessayer plus tard."
|
||||
},
|
||||
"remove": {
|
||||
"success": "Commentaire supprimé avec succès",
|
||||
"error": "Échec de la suppression du commentaire. Veuillez réessayer plus tard."
|
||||
},
|
||||
"upload": {
|
||||
"error": "Échec du téléchargement du fichier. Veuillez réessayer plus tard."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"view": {
|
||||
"create": {
|
||||
"label": "Créer une vue"
|
||||
},
|
||||
"update": {
|
||||
"label": "Mettre à jour la vue"
|
||||
}
|
||||
},
|
||||
|
||||
"inbox_issue": {
|
||||
"status": {
|
||||
"pending": {
|
||||
"title": "En attente",
|
||||
"description": "En attente"
|
||||
},
|
||||
"declined": {
|
||||
"title": "Refusé",
|
||||
"description": "Refusé"
|
||||
},
|
||||
"snoozed": {
|
||||
"title": "Reporté",
|
||||
"description": "{days, plural, one{# jour} other{# jours}} restants"
|
||||
},
|
||||
"accepted": {
|
||||
"title": "Accepté",
|
||||
"description": "Accepté"
|
||||
},
|
||||
"duplicate": {
|
||||
"title": "Doublon",
|
||||
"description": "Doublon"
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"in-app": "dans l'application"
|
||||
},
|
||||
"order_by": {
|
||||
"created_at": "Créé le",
|
||||
"updated_at": "Mis à jour le",
|
||||
"id": "Identifiant"
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_dashboard": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Vue d'ensemble de vos projets, activités et métriques",
|
||||
"description": "Bienvenue sur Plane, nous sommes ravis de vous accueillir. Créez votre premier projet et suivez vos tâches, et cette page se transformera en un espace qui vous aidera à progresser. Les administrateurs verront également des éléments pour aider leur équipe à progresser.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre premier projet",
|
||||
"comic": {
|
||||
"title": "Tout commence par un projet sur Plane",
|
||||
"description": "Un projet peut être une feuille de route produit, une campagne marketing ou le lancement d'une nouvelle voiture."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_analytics": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Suivez les progrès, les charges de travail et les allocations. Repérez les tendances, éliminez les obstacles et accélérez le travail",
|
||||
"description": "Visualisez l'étendue par rapport à la demande, les estimations et l'expansion des objectifs. Obtenez des performances par membre et par équipe, et assurez-vous que votre projet respecte les délais.",
|
||||
"primary_button": {
|
||||
"text": "Commencez votre premier projet",
|
||||
"comic": {
|
||||
"title": "Les analyses fonctionnent mieux avec Cycles + Modules",
|
||||
"description": "D'abord, cadrez vos tâches dans des Cycles et, si possible, regroupez les tâches qui s'étendent sur plus d'un cycle dans des Modules. Consultez-les dans la navigation à gauche."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_projects": {
|
||||
"network": {
|
||||
"private": {
|
||||
"title": "Privé",
|
||||
"description": "Accessible uniquement sur invitation"
|
||||
},
|
||||
"public": {
|
||||
"title": "Public",
|
||||
"description": "Tout le monde dans l'espace de travail sauf les invités peut rejoindre"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"permission": "Vous n'avez pas la permission d'effectuer cette action.",
|
||||
"cycle_delete": "Échec de la suppression du cycle",
|
||||
"module_delete": "Échec de la suppression du module",
|
||||
"issue_delete": "Échec de la suppression du problème"
|
||||
},
|
||||
"state": {
|
||||
"backlog": "Backlog",
|
||||
"unstarted": "Non démarré",
|
||||
"started": "Démarré",
|
||||
"completed": "Terminé",
|
||||
"cancelled": "Annulé"
|
||||
},
|
||||
"sort": {
|
||||
"manual": "Manuel",
|
||||
"name": "Nom",
|
||||
"created_at": "Date de création",
|
||||
"members_length": "Nombre de membres"
|
||||
},
|
||||
"scope": {
|
||||
"my_projects": "Mes projets",
|
||||
"archived_projects": "Archivés"
|
||||
},
|
||||
"common": {
|
||||
"months_count": "{months, plural, one{# mois} other{# mois}}"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Aucun projet actif",
|
||||
"description": "Considérez chaque projet comme le parent des travaux orientés vers un objectif. Les projets sont l'endroit où vivent les tâches, les Cycles et les Modules, et avec vos collègues, ils vous aident à atteindre cet objectif. Créez un nouveau projet ou filtrez les projets archivés.",
|
||||
"primary_button": {
|
||||
"text": "Commencez votre premier projet",
|
||||
"comic": {
|
||||
"title": "Tout commence par un projet sur Plane",
|
||||
"description": "Un projet peut être une feuille de route produit, une campagne marketing ou le lancement d'une nouvelle voiture."
|
||||
}
|
||||
}
|
||||
},
|
||||
"no_projects": {
|
||||
"title": "Aucun projet",
|
||||
"description": "Pour créer des tâches ou gérer votre travail, vous devez créer un projet ou en faire partie.",
|
||||
"primary_button": {
|
||||
"text": "Commencez votre premier projet",
|
||||
"comic": {
|
||||
"title": "Tout commence par un projet sur Plane",
|
||||
"description": "Un projet peut être une feuille de route produit, une campagne marketing ou le lancement d'une nouvelle voiture."
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"title": "Aucun projet correspondant",
|
||||
"description": "Aucun projet détecté avec les critères correspondants. \n Créez un nouveau projet à la place."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_issues": {
|
||||
"empty_state": {
|
||||
"all-issues": {
|
||||
"title": "Aucune tâche dans le projet",
|
||||
"description": "Premier projet terminé ! Maintenant, divisez votre travail en morceaux traçables avec des tâches. Allons-y !",
|
||||
"primary_button": {
|
||||
"text": "Créer une nouvelle tâche"
|
||||
}
|
||||
},
|
||||
"assigned": {
|
||||
"title": "Aucune tâche assignée",
|
||||
"description": "Les tâches qui vous sont assignées peuvent être suivies ici.",
|
||||
"primary_button": {
|
||||
"text": "Créer une nouvelle tâche"
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"title": "Aucune tâche créée",
|
||||
"description": "Toutes les tâches que vous avez créées se trouvent ici. Suivez-les directement ici.",
|
||||
"primary_button": {
|
||||
"text": "Créer une nouvelle tâche"
|
||||
}
|
||||
},
|
||||
"subscribed": {
|
||||
"title": "Aucune tâche suivie",
|
||||
"description": "Abonnez-vous aux tâches qui vous intéressent, suivez-les toutes ici."
|
||||
},
|
||||
"custom-view": {
|
||||
"title": "Aucune tâche trouvée",
|
||||
"description": "Les tâches correspondant aux filtres sont affichées ici. Suivez-les toutes ici."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_settings": {
|
||||
"label": "Paramètres de l'espace de travail",
|
||||
"settings": {
|
||||
"general": {
|
||||
"title": "Général"
|
||||
},
|
||||
"members": {
|
||||
"title": "Membres"
|
||||
},
|
||||
"billing-and-plans": {
|
||||
"title": "Facturation et plans"
|
||||
},
|
||||
"exports": {
|
||||
"title": "Exportations"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks"
|
||||
},
|
||||
"api-tokens": {
|
||||
"title": "Jetons API"
|
||||
}
|
||||
},
|
||||
"empty_state": {
|
||||
"api_tokens": {
|
||||
"title": "Aucun jeton API créé",
|
||||
"description": "Les API Plane peuvent être utilisées pour intégrer vos données dans Plane avec n'importe quel système externe. Créez un jeton pour commencer."
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Aucun webhook ajouté",
|
||||
"description": "Créez des webhooks pour recevoir des mises à jour en temps réel et automatiser des actions."
|
||||
},
|
||||
"exports": {
|
||||
"title": "Aucune exportation pour le moment",
|
||||
"description": "Chaque fois que vous exportez, une copie sera également disponible ici pour référence."
|
||||
},
|
||||
"imports": {
|
||||
"title": "Aucune importation pour le moment",
|
||||
"description": "Retrouvez toutes vos importations précédentes ici et téléchargez-les."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"profile": {
|
||||
"label": "Profil",
|
||||
"page_label": "Votre travail",
|
||||
"work": "Travail",
|
||||
"details": {
|
||||
"joined_on": "Inscrit le",
|
||||
"time_zone": "Fuseau horaire"
|
||||
},
|
||||
"stats": {
|
||||
"workload": "Charge de travail",
|
||||
"overview": "Aperçu",
|
||||
"created": "Problèmes créés",
|
||||
"assigned": "Problèmes assignés",
|
||||
"subscribed": "Problèmes suivis",
|
||||
"state_distribution": {
|
||||
"title": "Problèmes par état",
|
||||
"empty": "Créez des problèmes pour les visualiser par état dans le graphique pour une meilleure analyse."
|
||||
},
|
||||
"priority_distribution": {
|
||||
"title": "Problèmes par priorité",
|
||||
"empty": "Créez des problèmes pour les visualiser par priorité dans le graphique pour une meilleure analyse."
|
||||
},
|
||||
"recent_activity": {
|
||||
"title": "Activité récente",
|
||||
"empty": "Nous n'avons pas trouvé de données. Veuillez consulter vos entrées"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"profile": "Profil",
|
||||
"security": "Sécurité",
|
||||
"activity": "Activité",
|
||||
"appearance": "Apparence",
|
||||
"notifications": "Notifications"
|
||||
},
|
||||
"tabs": {
|
||||
"summary": "Résumé",
|
||||
"assigned": "Assignés",
|
||||
"created": "Créés",
|
||||
"subscribed": "Suivis",
|
||||
"activity": "Activité"
|
||||
},
|
||||
"empty_state": {
|
||||
"activity": {
|
||||
"title": "Aucune activité pour le moment",
|
||||
"description": "Commencez par créer une nouvelle tâche ! Ajoutez des détails et des propriétés à celle-ci. Explorez davantage Plane pour voir votre activité."
|
||||
},
|
||||
"assigned": {
|
||||
"title": "Aucune tâche assignée",
|
||||
"description": "Les tâches qui vous sont assignées peuvent être suivies ici."
|
||||
},
|
||||
"created": {
|
||||
"title": "Aucune tâche créée",
|
||||
"description": "Toutes les tâches que vous avez créées se trouvent ici. Suivez-les directement ici."
|
||||
},
|
||||
"subscribed": {
|
||||
"title": "Aucune tâche suivie",
|
||||
"description": "Abonnez-vous aux tâches qui vous intéressent, suivez-les toutes ici."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_settings": {
|
||||
"empty_state": {
|
||||
"labels": {
|
||||
"title": "Aucune étiquette pour le moment",
|
||||
"description": "Créez des étiquettes pour organiser et filtrer les tâches de votre projet."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_cycles": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Groupez et cadrez votre travail en cycles.",
|
||||
"description": "Décomposez le travail en périodes définies, travaillez à rebours à partir de votre date limite pour fixer des échéances, et réalisez des progrès tangibles en équipe.",
|
||||
"primary_button": {
|
||||
"text": "Définir votre premier cycle",
|
||||
"comic": {
|
||||
"title": "Les cycles sont des périodes répétitives.",
|
||||
"description": "Un sprint, une itération ou tout autre terme que vous utilisez pour le suivi hebdomadaire ou bi-hebdomadaire du travail est un cycle."
|
||||
}
|
||||
}
|
||||
},
|
||||
"no_issues": {
|
||||
"title": "Aucune tâche ajoutée au cycle",
|
||||
"description": "Ajoutez ou créez des tâches que vous souhaitez cadencer et livrer dans ce cycle",
|
||||
"primary_button": {
|
||||
"text": "Créer une nouvelle tâche"
|
||||
},
|
||||
"secondary_button": {
|
||||
"text": "Ajouter une tâche existante"
|
||||
}
|
||||
},
|
||||
"completed_no_issues": {
|
||||
"title": "Aucun problème dans le cycle",
|
||||
"description": "Aucun problème dans le cycle. Les problèmes ont été transférés ou sont cachés. Pour voir les problèmes cachés, le cas échéant, mettez à jour les propriétés d'affichage en conséquence."
|
||||
},
|
||||
"active": {
|
||||
"title": "Aucun cycle actif",
|
||||
"description": "Un cycle actif inclut toute période englobant la date d'aujourd'hui dans sa plage. Retrouvez ici les progrès et les détails du cycle actif."
|
||||
},
|
||||
"archived": {
|
||||
"title": "Aucun cycle archivé pour le moment",
|
||||
"description": "Pour organiser votre projet, archivez les cycles terminés. Retrouvez-les ici une fois archivés."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_issues": {
|
||||
"empty_state": {
|
||||
"no_issues": {
|
||||
"title": "Créez un problème et assignez-le à quelqu'un, même à vous-même",
|
||||
"description": "Considérez les problèmes comme des emplois, des tâches, des actions ou JTBD (travaux à accomplir). Nous aimons ça. Un problème et ses sous-problèmes sont généralement des actions basées sur le temps assignées aux membres de votre équipe. Votre équipe crée, assigne et termine des problèmes pour faire avancer votre projet vers son objectif.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre premier problème",
|
||||
"comic": {
|
||||
"title": "Les problèmes sont les éléments constitutifs de Plane.",
|
||||
"description": "Redessiner l'interface utilisateur de Plane, Rebrander l'entreprise ou Lancer le nouveau système d'injection de carburant sont des exemples de problèmes qui comportent probablement des sous-problèmes."
|
||||
}
|
||||
}
|
||||
},
|
||||
"no_archived_issues": {
|
||||
"title": "Aucun problème archivé pour l'instant",
|
||||
"description": "Manuellement ou via une automatisation, vous pouvez archiver les problèmes terminés ou annulés. Retrouvez-les ici une fois archivés.",
|
||||
"primary_button": {
|
||||
"text": "Configurer l'automatisation"
|
||||
}
|
||||
},
|
||||
"issues_empty_filter": {
|
||||
"title": "Aucun problème trouvé correspondant aux filtres appliqués",
|
||||
"secondary_button": {
|
||||
"text": "Effacer tous les filtres"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_module": {
|
||||
"empty_state": {
|
||||
"no_issues": {
|
||||
"title": "Aucun problème dans le module",
|
||||
"description": "Créez ou ajoutez des problèmes que vous souhaitez réaliser dans le cadre de ce module",
|
||||
"primary_button": {
|
||||
"text": "Créer un nouveau problème"
|
||||
},
|
||||
"secondary_button": {
|
||||
"text": "Ajouter un problème existant"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"title": "Associez les jalons de votre projet aux modules et suivez facilement le travail global.",
|
||||
"description": "Un groupe de problèmes appartenant à un parent logique et hiérarchique forme un module. Pensez-y comme un moyen de suivre le travail par jalons de projet. Ils ont leurs propres périodes, échéances et analyses pour vous aider à voir votre progression vers un jalon.",
|
||||
"primary_button": {
|
||||
"text": "Construisez votre premier module",
|
||||
"comic": {
|
||||
"title": "Les modules aident à regrouper le travail par hiérarchie.",
|
||||
"description": "Un module de panier, un module de châssis et un module d'entrepôt sont de bons exemples de ce regroupement."
|
||||
}
|
||||
}
|
||||
},
|
||||
"archived": {
|
||||
"title": "Aucun module archivé pour le moment",
|
||||
"description": "Pour organiser votre projet, archivez les modules terminés ou annulés. Retrouvez-les ici une fois archivés."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_views": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Enregistrez des vues filtrées pour votre projet. Créez-en autant que nécessaire",
|
||||
"description": "Les vues sont un ensemble de filtres enregistrés que vous utilisez fréquemment ou auxquels vous souhaitez accéder facilement. Tous vos collègues dans un projet peuvent voir les vues de chacun et choisir celle qui correspond le mieux à leurs besoins.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre première vue",
|
||||
"comic": {
|
||||
"title": "Les vues fonctionnent au-dessus des propriétés des problèmes.",
|
||||
"description": "Vous pouvez créer une vue à partir d'ici avec autant de propriétés ou de filtres que vous le souhaitez."
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"title": "Aucune vue correspondante",
|
||||
"description": "Aucune vue ne correspond aux critères de recherche. \n Créez une nouvelle vue à la place."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project_page": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Écrivez une note, un document ou une base de connaissances complète. Laissez Galileo, l'assistant IA de Plane, vous aider à démarrer",
|
||||
"description": "Les pages sont un espace pour capturer des idées dans Plane. Prenez des notes de réunion, formatez-les facilement, intégrez des problèmes, organisez-les en utilisant une bibliothèque de composants et gardez-les toutes dans le contexte de votre projet. Pour simplifier tout document, invoquez Galileo, l'IA de Plane, avec un raccourci ou en cliquant sur un bouton.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre première page"
|
||||
}
|
||||
},
|
||||
"private": {
|
||||
"title": "Aucune page privée pour le moment",
|
||||
"description": "Conservez vos pensées privées ici. Lorsque vous êtes prêt à les partager, votre équipe n'est qu'à un clic.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre première page"
|
||||
}
|
||||
},
|
||||
"public": {
|
||||
"title": "Aucune page publique pour le moment",
|
||||
"description": "Consultez ici les pages partagées avec tout le monde dans votre projet.",
|
||||
"primary_button": {
|
||||
"text": "Créez votre première page"
|
||||
}
|
||||
},
|
||||
"archived": {
|
||||
"title": "Aucune page archivée pour le moment",
|
||||
"description": "Archivez les pages qui ne sont pas sur votre radar. Accédez-y ici en cas de besoin."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"command_k": {
|
||||
"empty_state": {
|
||||
"search": {
|
||||
"title": "Aucun résultat trouvé"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"issue_relation": {
|
||||
"empty_state": {
|
||||
"search": {
|
||||
"title": "Aucun problème correspondant trouvé"
|
||||
},
|
||||
"general": {
|
||||
"title": "Aucun problème trouvé"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"issue_comment": {
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Pas encore de commentaires",
|
||||
"description": "Les commentaires peuvent être utilisés comme un espace de discussion et de suivi pour les problèmes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"notification": {
|
||||
"empty_state": {
|
||||
"detail": {
|
||||
"title": "Sélectionnez pour voir les détails."
|
||||
},
|
||||
"all": {
|
||||
"title": "Aucune tâche assignée",
|
||||
"description": "Les mises à jour des problèmes qui vous sont assignés peuvent être \n vues ici."
|
||||
},
|
||||
"mentions": {
|
||||
"title": "Aucune tâche assignée",
|
||||
"description": "Les mises à jour des problèmes qui vous sont assignés peuvent être \n vues ici."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"active_cycle": {
|
||||
"empty_state": {
|
||||
"progress": {
|
||||
"title": "Ajoutez des problèmes au cycle pour voir sa progression."
|
||||
},
|
||||
"chart": {
|
||||
"title": "Ajoutez des problèmes au cycle pour voir le graphique d'avancement."
|
||||
},
|
||||
"priority_issue": {
|
||||
"title": "Observez d'un coup d'œil les problèmes prioritaires traités dans le cycle."
|
||||
},
|
||||
"assignee": {
|
||||
"title": "Ajoutez des responsables aux problèmes pour voir la répartition du travail par responsable."
|
||||
},
|
||||
"label": {
|
||||
"title": "Ajoutez des étiquettes aux problèmes pour voir la répartition du travail par étiquette."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"disabled_project": {
|
||||
"empty_state": {
|
||||
"inbox": {
|
||||
"title": "La collecte n'est pas activée pour le projet.",
|
||||
"description": "La collecte vous aide à gérer les demandes entrantes pour votre projet et à les ajouter en tant que problèmes dans votre flux de travail. Activez la collecte dans les paramètres du projet pour gérer les demandes.",
|
||||
"primary_button": {
|
||||
"text": "Gérer les fonctionnalités"
|
||||
}
|
||||
},
|
||||
"cycle": {
|
||||
"title": "Les cycles ne sont pas activés pour ce projet.",
|
||||
"description": "Divisez le travail en segments limités dans le temps, travaillez à rebours depuis la date limite de votre projet pour fixer des dates et progressez concrètement en équipe. Activez la fonctionnalité des cycles pour votre projet afin de commencer à les utiliser.",
|
||||
"primary_button": {
|
||||
"text": "Gérer les fonctionnalités"
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"title": "Les modules ne sont pas activés pour le projet.",
|
||||
"description": "Les modules sont les éléments constitutifs de votre projet. Activez les modules dans les paramètres du projet pour commencer à les utiliser.",
|
||||
"primary_button": {
|
||||
"text": "Gérer les fonctionnalités"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"title": "Les pages ne sont pas activées pour le projet.",
|
||||
"description": "Les pages sont les éléments constitutifs de votre projet. Activez les pages dans les paramètres du projet pour commencer à les utiliser.",
|
||||
"primary_button": {
|
||||
"text": "Gérer les fonctionnalités"
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"title": "Les vues ne sont pas activées pour le projet.",
|
||||
"description": "Les vues sont les éléments constitutifs de votre projet. Activez les vues dans les paramètres du projet pour commencer à les utiliser.",
|
||||
"primary_button": {
|
||||
"text": "Gérer les fonctionnalités"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"inbox": {
|
||||
"label": "Boîte de réception",
|
||||
"empty_state": {
|
||||
"sidebar_open_tab": {
|
||||
"title": "Aucun problème ouvert",
|
||||
"description": "Trouvez ici les problèmes ouverts. Créez un nouveau problème."
|
||||
},
|
||||
"sidebar_closed_tab": {
|
||||
"title": "Aucun problème fermé",
|
||||
"description": "Tous les problèmes, qu'ils soient acceptés ou refusés, se trouvent ici."
|
||||
},
|
||||
"sidebar_filter": {
|
||||
"title": "Aucun problème correspondant",
|
||||
"description": "Aucun problème ne correspond au filtre appliqué dans l'entrée. Créez un nouveau problème."
|
||||
},
|
||||
"detail": {
|
||||
"title": "Sélectionnez un problème pour voir ses détails."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"workspace_draft_issues": {
|
||||
"empty_state": {
|
||||
"title": "Les problèmes à moitié rédigés, et bientôt, les commentaires apparaîtront ici.",
|
||||
"description": "Pour essayer cela, commencez à rédiger un problème et laissez-le en suspens ou créez votre premier brouillon ci-dessous. 😉",
|
||||
"primary_button": {
|
||||
"text": "Créez votre premier brouillon"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"stickies": {
|
||||
"title": "Vos pense-bêtes",
|
||||
"placeholder": "cliquez pour écrire ici",
|
||||
"all": "Tous les pense-bêtes",
|
||||
"no-data": "Notez une idée, capturez un éclair de génie ou enregistrez une réflexion. Ajoutez un pense-bête pour commencer.",
|
||||
"add": "Ajouter un pense-bête",
|
||||
"search_placeholder": "Rechercher par titre",
|
||||
"delete": "Supprimer le pense-bête",
|
||||
"delete_confirmation": "Êtes-vous sûr de vouloir supprimer ce pense-bête ?",
|
||||
"empty_state": {
|
||||
"simple": "Notez une idée, capturez un éclair de génie ou enregistrez une réflexion. Ajoutez un pense-bête pour commencer.",
|
||||
"general": {
|
||||
"title": "Les pense-bêtes sont des notes rapides et des tâches que vous prenez à la volée.",
|
||||
"description": "Capturez vos pensées et vos idées sans effort en créant des pense-bêtes accessibles à tout moment et de n'importe où.",
|
||||
"primary_button": {
|
||||
"text": "Ajouter un pense-bête"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Cela ne correspond à aucun de vos pense-bêtes.",
|
||||
"description": "Essayez un terme différent ou informez-nous\nsi vous êtes sûr que votre recherche est correcte.",
|
||||
"primary_button": {
|
||||
"text": "Ajouter un pense-bête"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"errors": {
|
||||
"wrong_name": "Le nom du pense-bête ne peut pas dépasser 100 caractères.",
|
||||
"already_exists": "Il existe déjà un pense-bête sans description"
|
||||
},
|
||||
"created": {
|
||||
"title": "Pense-bête créé",
|
||||
"message": "Le pense-bête a été créé avec succès"
|
||||
},
|
||||
"not_created": {
|
||||
"title": "Pense-bête non créé",
|
||||
"message": "Le pense-bête n'a pas pu être créé"
|
||||
},
|
||||
"updated": {
|
||||
"title": "Pense-bête mis à jour",
|
||||
"message": "Le pense-bête a été mis à jour avec succès"
|
||||
},
|
||||
"not_updated": {
|
||||
"title": "Pense-bête non mis à jour",
|
||||
"message": "Le pense-bête n'a pas pu être mis à jour"
|
||||
},
|
||||
"removed": {
|
||||
"title": "Pense-bête supprimé",
|
||||
"message": "Le pense-bête a été supprimé avec succès"
|
||||
},
|
||||
"not_removed": {
|
||||
"title": "Pense-bête non supprimé",
|
||||
"message": "Le pense-bête n'a pas pu être supprimé"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"role_details": {
|
||||
"guest": {
|
||||
"title": "Invité",
|
||||
"description": "Les membres externes des organisations peuvent être invités en tant qu'invités."
|
||||
},
|
||||
"member": {
|
||||
"title": "Membre",
|
||||
"description": "Capacité à lire, écrire, modifier et supprimer des entités dans les projets, cycles et modules"
|
||||
},
|
||||
"admin": {
|
||||
"title": "Administrateur",
|
||||
"description": "Toutes les autorisations définies sur vrai dans l'espace de travail."
|
||||
}
|
||||
},
|
||||
|
||||
"user_roles": {
|
||||
"product_or_project_manager": "Chef de Produit / Projet",
|
||||
"development_or_engineering": "Développement / Ingénierie",
|
||||
"founder_or_executive": "Fondateur / Dirigeant",
|
||||
"freelancer_or_consultant": "Freelance / Consultant",
|
||||
"marketing_or_growth": "Marketing / Croissance",
|
||||
"sales_or_business_development": "Ventes / Développement Commercial",
|
||||
"support_or_operations": "Support / Opérations",
|
||||
"student_or_professor": "Étudiant / Professeur",
|
||||
"human_or_resources": "Ressources Humaines",
|
||||
"other": "Autre"
|
||||
},
|
||||
|
||||
"importer": {
|
||||
"github": {
|
||||
"title": "Github",
|
||||
"description": "Importer des problèmes depuis les dépôts GitHub et les synchroniser."
|
||||
},
|
||||
"jira": {
|
||||
"title": "Jira",
|
||||
"description": "Importer des problèmes et des épopées depuis les projets Jira."
|
||||
}
|
||||
},
|
||||
|
||||
"exporter": {
|
||||
"csv": {
|
||||
"title": "CSV",
|
||||
"description": "Exporter les problèmes vers un fichier CSV."
|
||||
},
|
||||
"xlsx": {
|
||||
"title": "Excel",
|
||||
"description": "Exporter les problèmes vers un fichier Excel."
|
||||
},
|
||||
"json": {
|
||||
"title": "JSON",
|
||||
"description": "Exporter les problèmes vers un fichier JSON."
|
||||
}
|
||||
},
|
||||
|
||||
"default_global_view": {
|
||||
"all_issues": "Tous les problèmes",
|
||||
"assigned": "Assignés",
|
||||
"created": "Créés",
|
||||
"subscribed": "Abonnés"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -154,11 +154,50 @@
|
||||
"stay_ahead_of_blockers_description": "发现项目间的挑战,查看其他视图中不明显的周期依赖关系。",
|
||||
"analytics": "分析",
|
||||
"workspace_invites": "工作区邀请",
|
||||
"workspace_settings": "工作区设置",
|
||||
"workspace_settings": {
|
||||
"label": "工作区设置",
|
||||
"settings": {
|
||||
"general": {
|
||||
"title": "常规"
|
||||
},
|
||||
"members": {
|
||||
"title": "会员"
|
||||
},
|
||||
"billing-and-plans": {
|
||||
"title": "账单与计划"
|
||||
},
|
||||
"exports": {
|
||||
"title": "导出"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "网络钩子"
|
||||
},
|
||||
"api-tokens": {
|
||||
"title": "API 令牌"
|
||||
}
|
||||
},
|
||||
"empty_state": {
|
||||
"api_tokens": {
|
||||
"title": "尚未创建 API 令牌",
|
||||
"description": "Plane API 可用于将您在 Plane 中的数据与任何外部系统集成。创建令牌即可开始使用。"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "尚未添加网络钩子",
|
||||
"description": "创建网络钩子以接收实时更新并自动执行操作。"
|
||||
},
|
||||
"exports": {
|
||||
"title": "尚无导出记录",
|
||||
"description": "每次导出时,您都可以在此处找到参考副本。"
|
||||
},
|
||||
"imports": {
|
||||
"title": "尚无导入记录",
|
||||
"description": "在此处查找所有以前的导入记录并下载。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enter_god_mode": "进入上帝模式",
|
||||
"workspace_logo": "工作区徽标",
|
||||
"new_issue": "新建问题",
|
||||
"home": "首页",
|
||||
"your_work": "您的工作",
|
||||
"drafts": "草稿",
|
||||
"projects": "项目",
|
||||
@@ -222,7 +261,6 @@
|
||||
"join_the_project_to_rearrange": "加入项目以重新排序",
|
||||
"drag_to_rearrange": "拖动以重新排序",
|
||||
"congrats": "恭喜!",
|
||||
"project": "项目",
|
||||
"open_project": "打开项目",
|
||||
"issues": "问题",
|
||||
"cycles": "周期",
|
||||
@@ -315,5 +353,504 @@
|
||||
"remove_parent_issue": "移除父问题",
|
||||
"add_parent": "添加父问题",
|
||||
"loading_members": "正在加载成员...",
|
||||
"inbox": "收件箱"
|
||||
"inbox": "收件箱",
|
||||
"show_all": "显示全部",
|
||||
"show_less": "显示较少",
|
||||
"no_data_yet": "暂无数据",
|
||||
|
||||
"project_view": {
|
||||
"access": {
|
||||
"public": "公开",
|
||||
"private": "私密"
|
||||
},
|
||||
"sort_by": {
|
||||
"created_at": "创建时间",
|
||||
"updated_at": "更新时间",
|
||||
"name": "名称"
|
||||
}
|
||||
},
|
||||
|
||||
"toast": {
|
||||
"success": "成功!",
|
||||
"error": "错误!"
|
||||
},
|
||||
|
||||
"home": {
|
||||
"empty": {
|
||||
"create_project": {
|
||||
"title": "创建项目",
|
||||
"description": "在Plane中,大多数事情都从项目开始。",
|
||||
"cta": "开始使用"
|
||||
},
|
||||
"invite_team": {
|
||||
"title": "邀请团队",
|
||||
"description": "与同事一起构建、部署和管理。",
|
||||
"cta": "邀请成员"
|
||||
},
|
||||
"configure_workspace": {
|
||||
"title": "设置工作空间",
|
||||
"description": "开启或关闭功能,或进行更多设置。",
|
||||
"cta": "配置此工作空间"
|
||||
},
|
||||
"personalize_account": {
|
||||
"title": "让Plane更个性化",
|
||||
"description": "选择您的头像、颜色等更多设置。",
|
||||
"cta": "立即个性化"
|
||||
}
|
||||
},
|
||||
"quick_links": {
|
||||
"empty": "保存您想随手可得的工作相关链接。",
|
||||
"add": "添加快捷链接",
|
||||
"title": "快捷链接",
|
||||
"title_plural": "快捷链接",
|
||||
"toasts": {
|
||||
"created": {
|
||||
"title": "链接已创建",
|
||||
"message": "链接已成功创建"
|
||||
},
|
||||
"not_created": {
|
||||
"title": "链接未创建",
|
||||
"message": "无法创建链接"
|
||||
},
|
||||
"updated": {
|
||||
"title": "链接已更新",
|
||||
"message": "链接已成功更新"
|
||||
},
|
||||
"not_updated": {
|
||||
"title": "链接未更新",
|
||||
"message": "无法更新链接"
|
||||
},
|
||||
"removed": {
|
||||
"title": "链接已删除",
|
||||
"message": "链接已成功删除"
|
||||
},
|
||||
"not_removed": {
|
||||
"title": "链接未删除",
|
||||
"message": "无法删除链接"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recents": {
|
||||
"title": "最近",
|
||||
"empty": {
|
||||
"project": "访问项目后,您的最近项目将显示在这里。",
|
||||
"page": "访问页面后,您的最近页面将显示在这里。",
|
||||
"issue": "访问问题后,您的最近问题将显示在这里。",
|
||||
"default": "您还没有任何最近项目。"
|
||||
},
|
||||
"filters": {
|
||||
"all": "所有项目",
|
||||
"projects": "项目",
|
||||
"pages": "页面",
|
||||
"issues": "问题"
|
||||
}
|
||||
},
|
||||
"my_stickies": {
|
||||
"title": "您的便签"
|
||||
},
|
||||
"new_at_plane": {
|
||||
"title": "Plane 新功能"
|
||||
},
|
||||
"quick_tutorial": {
|
||||
"title": "快速教程"
|
||||
},
|
||||
"widget": {
|
||||
"reordered_successfully": "小部件重新排序成功。",
|
||||
"reordering_failed": "重新排序小部件时发生错误。"
|
||||
},
|
||||
"manage_widgets": "管理小部件",
|
||||
"title": "首页",
|
||||
"star_us_on_github": "在 GitHub 上为我们加星"
|
||||
},
|
||||
|
||||
"link": {
|
||||
"modal": {
|
||||
"url": {
|
||||
"text": "网址",
|
||||
"required": "网址无效",
|
||||
"placeholder": "输入或粘贴网址"
|
||||
},
|
||||
"title": {
|
||||
"text": "显示标题",
|
||||
"placeholder": "您希望将此链接显示为什么"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"all": "全部",
|
||||
"states": "状态",
|
||||
"state": "状态",
|
||||
"state_groups": "状态组",
|
||||
"priority": "优先级",
|
||||
"team_project": "团队项目",
|
||||
"project": "项目",
|
||||
"cycle": "周期",
|
||||
"cycles": "周期",
|
||||
"module": "模块",
|
||||
"modules": "模块",
|
||||
"labels": "标签",
|
||||
"assignees": "分配人",
|
||||
"assignee": "分配人",
|
||||
"created_by": "创建者",
|
||||
"none": "无",
|
||||
"link": "链接",
|
||||
"estimate": "估算",
|
||||
"layout": "布局",
|
||||
"filters": "筛选",
|
||||
"display": "显示",
|
||||
"load_more": "加载更多",
|
||||
"no_matches_found": "未找到匹配项",
|
||||
"activity": "活动",
|
||||
"analytics": "分析",
|
||||
"success": "成功",
|
||||
"error": "错误",
|
||||
"group_by": "分组方式",
|
||||
"search": "搜索",
|
||||
"epic": "史诗",
|
||||
"issue": "问题",
|
||||
"warning": "警告",
|
||||
"updating": "更新中",
|
||||
"update": "更新",
|
||||
"creating": "创建中",
|
||||
"cancel": "取消",
|
||||
"description": "描述",
|
||||
"title": "标题",
|
||||
"order_by": {
|
||||
"label": "排序方式",
|
||||
"manual": "手动",
|
||||
"last_created": "最近创建",
|
||||
"last_updated": "最近更新",
|
||||
"start_date": "开始日期",
|
||||
"due_date": "截止日期",
|
||||
"asc": "升序",
|
||||
"desc": "降序"
|
||||
},
|
||||
"sort": {
|
||||
"asc": "升序",
|
||||
"desc": "降序"
|
||||
},
|
||||
"comments": "评论",
|
||||
"updates": "更新"
|
||||
},
|
||||
"form": {
|
||||
"title": {
|
||||
"required": "标题为必填项",
|
||||
"max_length": "标题不能超过{length}个字符"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"grouping_title": "{entity}分组",
|
||||
"priority": "{entity}",
|
||||
"all": "所有{entity}",
|
||||
"drop_here_to_move": "拖放至此以移动{entity}"
|
||||
},
|
||||
"epic": {
|
||||
"all": "所有史诗",
|
||||
"label": "{count, plural, one {史诗} other {史诗}}"
|
||||
},
|
||||
"issue": {
|
||||
"label": "{count, plural, one {问题} other {问题}}",
|
||||
"all": "所有问题",
|
||||
"add": "添加问题",
|
||||
"priority": {
|
||||
"urgent": "紧急",
|
||||
"high": "高",
|
||||
"medium": "中",
|
||||
"low": "低"
|
||||
},
|
||||
"display": {
|
||||
"properties": {
|
||||
"label": "显示属性",
|
||||
"id": "ID",
|
||||
"issue_type": "问题类型",
|
||||
"sub_issue_count": "子问题数量",
|
||||
"attachment_count": "附件数量"
|
||||
},
|
||||
"extra": {
|
||||
"show_sub_issues": "显示子问题",
|
||||
"show_empty_groups": "显示空分组"
|
||||
}
|
||||
},
|
||||
"layouts": {
|
||||
"ordered_by_label": "当前排序方式",
|
||||
"list": "列表",
|
||||
"kanban": "看板",
|
||||
"calendar": "日历",
|
||||
"spreadsheet": "表格",
|
||||
"gantt": "时间线",
|
||||
"title": {
|
||||
"list": "列表视图",
|
||||
"kanban": "看板视图",
|
||||
"calendar": "日历视图",
|
||||
"spreadsheet": "表格视图",
|
||||
"gantt": "时间线视图"
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"active": "进行中",
|
||||
"backlog": "待办"
|
||||
},
|
||||
"comments": {
|
||||
"create": {
|
||||
"success": "评论创建成功",
|
||||
"error": "评论创建失败,请稍后重试"
|
||||
},
|
||||
"update": {
|
||||
"success": "评论更新成功",
|
||||
"error": "评论更新失败,请稍后重试"
|
||||
},
|
||||
"remove": {
|
||||
"success": "评论删除成功",
|
||||
"error": "评论删除失败,请稍后重试"
|
||||
},
|
||||
"upload": {
|
||||
"error": "资源上传失败,请稍后重试"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"project": {
|
||||
"label": "{count, plural, one {项目} other {项目}}"
|
||||
},
|
||||
|
||||
"view": {
|
||||
"create": {
|
||||
"label": "创建视图"
|
||||
},
|
||||
"update": {
|
||||
"label": "更新视图"
|
||||
}
|
||||
},
|
||||
|
||||
"inbox_issue": {
|
||||
"status": {
|
||||
"pending": {
|
||||
"title": "待处理",
|
||||
"description": "待处理"
|
||||
},
|
||||
"declined": {
|
||||
"title": "已拒绝",
|
||||
"description": "已拒绝"
|
||||
},
|
||||
"snoozed": {
|
||||
"title": "已暂停",
|
||||
"description": "还剩{days}天"
|
||||
},
|
||||
"accepted": {
|
||||
"title": "已接受",
|
||||
"description": "已接受"
|
||||
},
|
||||
"duplicate": {
|
||||
"title": "重复",
|
||||
"description": "重复"
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"in-app": "应用内"
|
||||
},
|
||||
"order_by": {
|
||||
"created_at": "创建时间",
|
||||
"updated_at": "更新时间",
|
||||
"id": "编号"
|
||||
}
|
||||
},
|
||||
|
||||
"user_profile": {
|
||||
"title": "你的工作",
|
||||
"work": "工作",
|
||||
"details": {
|
||||
"joined_on": "加入时间",
|
||||
"time_zone": "时区"
|
||||
},
|
||||
"stats": {
|
||||
"workload": "工作量",
|
||||
"overview": "概述",
|
||||
"created": "已创建问题",
|
||||
"assigned": "已分配问题",
|
||||
"subscribed": "已订阅问题",
|
||||
"state_distribution": {
|
||||
"title": "按状态显示的问题",
|
||||
"empty": "创建问题以在图表中按状态查看它们,以便更好地分析。"
|
||||
},
|
||||
"priority_distribution": {
|
||||
"title": "按优先级显示的问题",
|
||||
"empty": "创建问题以在图表中按优先级查看它们,以便更好地分析。"
|
||||
},
|
||||
"recent_activity": {
|
||||
"title": "最近活动",
|
||||
"empty": "我们找不到数据。请查看您的输入"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"profile": "个人资料",
|
||||
"security": "安全",
|
||||
"activity": "活动",
|
||||
"appearance": "外观",
|
||||
"notifications": "通知"
|
||||
},
|
||||
"tabs": {
|
||||
"summary": "摘要",
|
||||
"assigned": "已分配",
|
||||
"created": "已创建",
|
||||
"subscribed": "已订阅",
|
||||
"activity": "活动"
|
||||
}
|
||||
},
|
||||
|
||||
"role_details": {
|
||||
"guest": {
|
||||
"title": "访客",
|
||||
"description": "组织的外部成员可以被邀请为访客。"
|
||||
},
|
||||
"member": {
|
||||
"title": "成员",
|
||||
"description": "可以在项目、周期和模块内读取、写入、编辑和删除实体"
|
||||
},
|
||||
"admin": {
|
||||
"title": "管理员",
|
||||
"description": "工作区内的所有权限都设置为允许。"
|
||||
}
|
||||
},
|
||||
|
||||
"user_roles": {
|
||||
"product_or_project_manager": "产品/项目经理",
|
||||
"development_or_engineering": "开发/工程",
|
||||
"founder_or_executive": "创始人/高管",
|
||||
"freelancer_or_consultant": "自由职业者/顾问",
|
||||
"marketing_or_growth": "市场营销/增长",
|
||||
"sales_or_business_development": "销售/业务发展",
|
||||
"support_or_operations": "支持/运营",
|
||||
"student_or_professor": "学生/教授",
|
||||
"human_or_resources": "人力资源",
|
||||
"other": "其他"
|
||||
},
|
||||
|
||||
"importer": {
|
||||
"github": {
|
||||
"title": "Github",
|
||||
"description": "从 GitHub 仓库导入问题并同步。"
|
||||
},
|
||||
"jira": {
|
||||
"title": "Jira",
|
||||
"description": "从 Jira 项目导入问题和史诗。"
|
||||
}
|
||||
},
|
||||
|
||||
"exporter": {
|
||||
"csv": {
|
||||
"title": "CSV",
|
||||
"description": "将问题导出为 CSV 文件。"
|
||||
},
|
||||
"xlsx": {
|
||||
"title": "Excel",
|
||||
"description": "将问题导出为 Excel 文件。"
|
||||
},
|
||||
"json": {
|
||||
"title": "JSON",
|
||||
"description": "将问题导出为 JSON 文件。"
|
||||
}
|
||||
},
|
||||
|
||||
"default_global_view": {
|
||||
"all_issues": "所有问题",
|
||||
"assigned": "已分配",
|
||||
"created": "已创建",
|
||||
"subscribed": "已订阅"
|
||||
},
|
||||
|
||||
"stickies": {
|
||||
"title": "便签",
|
||||
"placeholder": "点击此处输入",
|
||||
"all": "所有便签",
|
||||
"no-data": "记下想法,捕捉灵感,或记录突发奇想。添加便签即可开始。",
|
||||
"add": "添加便签",
|
||||
"search_placeholder": "按标题搜索",
|
||||
"delete": "删除便签",
|
||||
"delete_confirmation": "确定要删除这个便签吗?",
|
||||
"empty_state": {
|
||||
"simple": "记下想法,捕捉灵感,或记录突发奇想。添加便签即可开始。",
|
||||
"general": {
|
||||
"title": "便签是您随手记下的快速笔记和待办事项。",
|
||||
"description": "通过创建便签轻松捕捉您的想法和创意,随时随地都可以访问。",
|
||||
"primary_button": {
|
||||
"text": "添加便签"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "没有找到匹配的便签。",
|
||||
"description": "尝试使用不同的搜索词,或如果您确定搜索正确,\n请告诉我们。",
|
||||
"primary_button": {
|
||||
"text": "添加便签"
|
||||
}
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"errors": {
|
||||
"wrong_name": "便签名称不能超过100个字符。",
|
||||
"already_exists": "已存在一个没有描述的便签"
|
||||
},
|
||||
"created": {
|
||||
"title": "便签已创建",
|
||||
"message": "便签已成功创建"
|
||||
},
|
||||
"not_created": {
|
||||
"title": "便签未创建",
|
||||
"message": "无法创建便签"
|
||||
},
|
||||
"updated": {
|
||||
"title": "便签已更新",
|
||||
"message": "便签已成功更新"
|
||||
},
|
||||
"not_updated": {
|
||||
"title": "便签未更新",
|
||||
"message": "无法更新便签"
|
||||
},
|
||||
"removed": {
|
||||
"title": "便签已删除",
|
||||
"message": "便签已成功删除"
|
||||
},
|
||||
"not_removed": {
|
||||
"title": "便签未删除",
|
||||
"message": "无法删除便签"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace_projects": {
|
||||
"network": {
|
||||
"private": {
|
||||
"title": "私有",
|
||||
"description": "仅限邀请访问"
|
||||
},
|
||||
"public": {
|
||||
"title": "公开",
|
||||
"description": "除访客外的工作区所有成员都可加入"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"permission": "您没有执行此操作的权限。",
|
||||
"cycle_delete": "删除周期失败",
|
||||
"module_delete": "删除模块失败",
|
||||
"issue_delete": "删除问题失败"
|
||||
},
|
||||
"state": {
|
||||
"backlog": "待办",
|
||||
"unstarted": "未开始",
|
||||
"started": "进行中",
|
||||
"completed": "已完成",
|
||||
"cancelled": "已取消"
|
||||
},
|
||||
"sort": {
|
||||
"manual": "手动",
|
||||
"name": "名称",
|
||||
"created_at": "创建日期",
|
||||
"members_length": "成员数量"
|
||||
},
|
||||
"scope": {
|
||||
"my_projects": "我的项目",
|
||||
"archived_projects": "已归档"
|
||||
},
|
||||
"common": {
|
||||
"months_count": "{months, plural, one{#个月} other{#个月}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 { 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
|
||||
export type TInboxIssueFilterMemberKeys = "assignees" | "created_by";
|
||||
|
||||
@@ -38,10 +21,7 @@ export type TInboxIssueFilter = {
|
||||
// sorting filters
|
||||
export type TInboxIssueSortingKeys = "order_by" | "sort_by";
|
||||
|
||||
export type TInboxIssueSortingOrderByKeys =
|
||||
| "issue__created_at"
|
||||
| "issue__updated_at"
|
||||
| "issue__sequence_id";
|
||||
export type TInboxIssueSortingOrderByKeys = "issue__created_at" | "issue__updated_at" | "issue__sequence_id";
|
||||
|
||||
export type TInboxIssueSortingSortByKeys = "asc" | "desc";
|
||||
|
||||
@@ -78,17 +58,6 @@ export type TInboxDuplicateIssueDetails = {
|
||||
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 & {
|
||||
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,
|
||||
TIssueOrderByOptions,
|
||||
TIssueGroupingFilters,
|
||||
TIssueExtraOptions
|
||||
TIssueExtraOptions,
|
||||
} from "@plane/types";
|
||||
|
||||
export interface IIssueCycle {
|
||||
|
||||
@@ -32,7 +32,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<link rel="shortcut icon" href={`${SPACE_BASE_PATH}/favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body>
|
||||
<AppProvider>{children}</AppProvider>
|
||||
<AppProvider>
|
||||
<>{children}</>
|
||||
</AppProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { ISSUE_PRIORITY_FILTERS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "./helpers";
|
||||
@@ -18,6 +19,9 @@ type Props = {
|
||||
export const FilterPriority: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
@@ -40,11 +44,11 @@ export const FilterPriority: React.FC<Props> = observer((props) => {
|
||||
isChecked={appliedFilters?.includes(priority.key) ? true : false}
|
||||
onClick={() => handleUpdate(priority.key)}
|
||||
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.no_matches_found")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
<HeaderGroupByCard
|
||||
groupBy={groupBy}
|
||||
icon={subList.icon}
|
||||
icon={subList.icon as any}
|
||||
title={subList.name}
|
||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||
/>
|
||||
|
||||
@@ -131,10 +131,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||
|
||||
if (subGroupByVisibilityToggle === false) return <></>;
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
})}
|
||||
@@ -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 left-0 flex-shrink-0">
|
||||
<HeaderSubGroupByCard
|
||||
icon={group.icon}
|
||||
icon={group.icon as any}
|
||||
title={group.name || ""}
|
||||
count={issueCount}
|
||||
isExpanded={isExpanded}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Fragment, MutableRefObject, forwardRef, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IGroupByColumn, TIssueGroupByOptions, IIssueDisplayProperties, TPaginationData, TLoader } from "@plane/types";
|
||||
// plane utils
|
||||
@@ -62,6 +63,8 @@ export const ListGroup = observer((props: Props) => {
|
||||
} = props;
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const groupRef = useRef<HTMLDivElement | null>(null);
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -85,7 +88,7 @@ export const ListGroup = observer((props: Props) => {
|
||||
}
|
||||
onClick={() => loadMoreIssues(group.id)}
|
||||
>
|
||||
Load More ↓
|
||||
{t("common.load_more")} ↓
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { TIssuePriorities } from "@plane/types";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
@@ -13,17 +14,19 @@ export const IssueBlockPriority = ({
|
||||
priority: TIssuePriorities | null;
|
||||
shouldShowName?: boolean;
|
||||
}) => {
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
const priority_detail = priority != null ? getIssuePriorityFilters(priority) : null;
|
||||
|
||||
if (priority_detail === null) 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={`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>
|
||||
</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>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// ui
|
||||
import { SITES_ISSUE_LAYOUTS } from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
@@ -19,6 +21,8 @@ type Props = {
|
||||
|
||||
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||
const { anchor } = props;
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -45,7 +49,7 @@ export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||
if (!layoutOptions[layout.key]) return;
|
||||
|
||||
return (
|
||||
<Tooltip key={layout.key} tooltipContent={layout.title}>
|
||||
<Tooltip key={layout.key} tooltipContent={t(layout.titleTranslationKey)}>
|
||||
<button
|
||||
type="button"
|
||||
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded transition-all hover:bg-custom-background-100 ${
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { CalendarCheck2, Signal } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { getIssuePriorityFilters } from "@plane/utils";
|
||||
@@ -24,6 +25,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDetails, mode }) => {
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
const { getStateById } = useStates();
|
||||
const state = getStateById(issueDetails?.state_id ?? undefined);
|
||||
|
||||
@@ -95,7 +98,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
|
||||
<Icon iconName={priority?.icon} />
|
||||
</span>
|
||||
)}
|
||||
<span>{priority?.title ?? "None"}</span>
|
||||
<span>{t(priority?.titleTranslationKey || "common.none")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@mui/material": "^5.14.1",
|
||||
"@plane/constants": "*",
|
||||
"@plane/editor": "*",
|
||||
"@plane/i18n": "*",
|
||||
"@plane/types": "*",
|
||||
"@plane/ui": "*",
|
||||
"@plane/services": "*",
|
||||
|
||||
@@ -5,28 +5,41 @@ import { observer } from "mobx-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { ANALYTICS_TABS } from "@plane/constants";
|
||||
import { ANALYTICS_TABS, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
// components
|
||||
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// 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";
|
||||
// plane web
|
||||
import { EUserPermissions } from "@/plane-web/constants";
|
||||
|
||||
const AnalyticsPage = observer(() => {
|
||||
const searchParams = useSearchParams();
|
||||
const analytics_tab = searchParams.get("analytics_tab");
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, loader } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
|
||||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
// TODO: refactor loader implementation
|
||||
return (
|
||||
<>
|
||||
@@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
|
||||
</Tab.Group>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
type={EmptyStateType.WORKSPACE_ANALYTICS}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_analytics.empty_state.general.title")}
|
||||
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");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Home } from "lucide-react";
|
||||
import githubBlackImage from "/public/logos/github-black.png";
|
||||
import githubWhiteImage from "/public/logos/github-white.png";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
@@ -19,6 +20,7 @@ export const WorkspaceDashboardHeader = () => {
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,7 +30,9 @@ export const WorkspaceDashboardHeader = () => {
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
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>
|
||||
</div>
|
||||
@@ -51,7 +55,7 @@ export const WorkspaceDashboardHeader = () => {
|
||||
width={16}
|
||||
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>
|
||||
</Header.RightItem>
|
||||
</Header>
|
||||
|
||||
@@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
import { InboxContentRoot } from "@/components/inbox";
|
||||
import { IssuePeekOverview } from "@/components/issues";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
|
||||
// hooks
|
||||
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const {
|
||||
@@ -34,6 +37,7 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined;
|
||||
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
||||
notificationLiteByNotificationId(currentSelectedNotificationId);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
|
||||
|
||||
// fetching workspace issue properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
@@ -82,7 +86,7 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||
{!currentSelectedNotificationId ? (
|
||||
<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>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { WorkspaceHomeView } from "@/components/home";
|
||||
// hooks
|
||||
@@ -11,8 +12,9 @@ import { WorkspaceDashboardHeader } from "./header";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - ${t("home.title")}` : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,12 +6,13 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronDown, PanelRight } from "lucide-react";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserProfileProjectSegregation } from "@plane/types";
|
||||
import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// components
|
||||
import { ProfileIssuesFilter } from "@/components/profile";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
@@ -30,6 +31,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
|
||||
const { data: currentUser } = useUser();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const isAuthorized = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
@@ -44,7 +46,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
|
||||
const isCurrentUser = currentUser?.id === userId;
|
||||
|
||||
const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`;
|
||||
const breadcrumbLabel = isCurrentUser ? t("profile.page_label") : `${userName} ${t("profile.work")}`;
|
||||
|
||||
return (
|
||||
<Header>
|
||||
@@ -86,7 +88,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
|
||||
className="w-full text-custom-text-300"
|
||||
>
|
||||
{tab.label}
|
||||
{t(tab.i18n_label)}
|
||||
</Link>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
|
||||
@@ -4,12 +4,12 @@ import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { ProfileSidebar } from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
@@ -66,7 +66,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
<AppHeader
|
||||
header={
|
||||
<UserProfileHeader
|
||||
type={currentTab?.label}
|
||||
type={currentTab?.i18n_label}
|
||||
userProjectsData={userProjectsData}
|
||||
showProfileIssuesFilter={isIssuesTab}
|
||||
/>
|
||||
|
||||
@@ -6,21 +6,30 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ChevronDown } from "lucide-react";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useLabel } from "@/hooks/store";
|
||||
|
||||
export const ProfileIssuesMobileHeader = observer(() => {
|
||||
// plane i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
// store hook
|
||||
@@ -112,7 +121,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
placement="bottom-start"
|
||||
customButton={
|
||||
<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} />
|
||||
</div>
|
||||
}
|
||||
@@ -129,19 +138,19 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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} />
|
||||
</div>
|
||||
}
|
||||
@@ -149,7 +158,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
@@ -163,18 +172,18 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
// components
|
||||
// constants
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
|
||||
type Props = {
|
||||
isAuthorized: boolean;
|
||||
@@ -33,7 +33,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
||||
: "border-transparent"
|
||||
}`}
|
||||
>
|
||||
{t(tab.label)}
|
||||
{t(tab.i18n_label)}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// types
|
||||
import { GROUP_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||
// components
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
@@ -16,7 +18,6 @@ import {
|
||||
} from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
|
||||
import { GROUP_CHOICES } from "@/constants/project";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
@@ -26,6 +27,7 @@ const userService = new UserService();
|
||||
export default function ProfileOverviewPage() {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: userProfile } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
||||
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
||||
@@ -40,7 +42,7 @@ export default function ProfileOverviewPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Your work" />
|
||||
<PageHead title={t("profile.page_label")} />
|
||||
<ContentWrapper className="space-y-7">
|
||||
<ProfileStats userProfile={userProfile} />
|
||||
<ProfileWorkload stateDistribution={stateDistribution} />
|
||||
|
||||
@@ -7,7 +7,9 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ArrowRight, PanelRight } from "lucide-react";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
@@ -16,8 +18,6 @@ import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from "
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
@@ -72,6 +72,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
@@ -184,7 +186,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
type="text"
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label="Cycles"
|
||||
label={t("common.cycles")}
|
||||
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
|
||||
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
@@ -239,7 +241,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
@@ -247,7 +249,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
@@ -258,10 +260,10 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
@@ -276,7 +278,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
{canUserCreateIssue && (
|
||||
<>
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</Button>
|
||||
{!isCompletedCycle && (
|
||||
<Button
|
||||
@@ -287,7 +289,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
Add issue
|
||||
{t("issue.add")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -5,32 +5,42 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
|
||||
|
||||
export const CycleIssuesMobileHeader = () => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { getCycleById } = useCycle();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
|
||||
const { workspaceSlug, projectId, cycleId } = useParams();
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const {
|
||||
@@ -123,7 +133,9 @@ export const CycleIssuesMobileHeader = () => {
|
||||
maxHeight={"md"}
|
||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||
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"
|
||||
closeOnSelect
|
||||
>
|
||||
@@ -135,18 +147,18 @@ export const CycleIssuesMobileHeader = () => {
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="w-3 h-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="w-3 h-3" />
|
||||
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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" />
|
||||
</span>
|
||||
}
|
||||
@@ -156,7 +168,7 @@ export const CycleIssuesMobileHeader = () => {
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
@@ -170,18 +182,18 @@ export const CycleIssuesMobileHeader = () => {
|
||||
</div>
|
||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
@@ -198,7 +210,7 @@ export const CycleIssuesMobileHeader = () => {
|
||||
onClick={() => setAnalyticsModal(true)}
|
||||
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
||||
>
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PageHead } from "@/components/core";
|
||||
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";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// 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(() => {
|
||||
// states
|
||||
@@ -26,13 +28,23 @@ const ProjectCyclesPage = observer(() => {
|
||||
const { currentProjectCycleIds, loader } = useCycle();
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// cycle filters hook
|
||||
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const totalCycles = currentProjectCycleIds?.length ?? 0;
|
||||
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Cycles` : 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) => {
|
||||
if (!projectId) return;
|
||||
@@ -50,9 +62,17 @@ const ProjectCyclesPage = observer(() => {
|
||||
if (currentProjectDetails?.cycle_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_CYCLE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.cycle.title")}
|
||||
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>
|
||||
);
|
||||
@@ -71,12 +91,22 @@ const ProjectCyclesPage = observer(() => {
|
||||
/>
|
||||
{totalCycles === 0 ? (
|
||||
<div className="h-full place-items-center">
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_CYCLES}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Cycle empty state");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("project_cycles.empty_state.general.title")}
|
||||
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");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -4,7 +4,9 @@ import { FC, useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
@@ -12,8 +14,6 @@ import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
@@ -23,6 +23,8 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
|
||||
|
||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
@@ -122,14 +124,18 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isIssueFilterActive(issueFilters)}>
|
||||
<FiltersDropdown
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
@@ -138,10 +144,10 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<FiltersDropdown title={t("common.display")} placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -2,35 +2,50 @@
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { EUserProjectRoles } from "@plane/constants/src/user";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { InboxIssueRoot } from "@/components/inbox";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper";
|
||||
// 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";
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const ProjectInboxPage = observer(() => {
|
||||
/// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const navigationTab = searchParams.get("currentTab");
|
||||
const inboxIssueId = searchParams.get("inboxIssueId");
|
||||
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
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
|
||||
if (currentProjectDetails?.inbox_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_INBOX}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.inbox.title")}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -6,26 +6,39 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import {
|
||||
DisplayFiltersSelection,
|
||||
FilterSelection,
|
||||
FiltersDropdown,
|
||||
IssueLayoutIcon,
|
||||
} from "@/components/issues/issue-layouts";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||
|
||||
export const ProjectIssuesMobileHeader = observer(() => {
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { workspaceSlug, projectId } = useParams() as {
|
||||
@@ -104,7 +117,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
||||
placement="bottom-start"
|
||||
customButton={
|
||||
<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} />
|
||||
</div>
|
||||
}
|
||||
@@ -119,18 +132,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.titleTranslationKey)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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" />
|
||||
</span>
|
||||
}
|
||||
@@ -142,7 +155,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
@@ -154,18 +167,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<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" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
@@ -181,7 +194,7 @@ export const ProjectIssuesMobileHeader = observer(() => {
|
||||
onClick={() => setAnalyticsModal(true)}
|
||||
className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200"
|
||||
>
|
||||
Analytics
|
||||
{t("common.analytics")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ArrowRight, PanelRight } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssuesStoreType, EIssueFilterType } from "@plane/constants";
|
||||
import { EIssueLayoutTypes, EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
@@ -16,8 +16,6 @@ import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@pla
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
@@ -247,7 +245,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
@@ -259,7 +257,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -6,16 +6,27 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
|
||||
// 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
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import {
|
||||
DisplayFiltersSelection,
|
||||
FilterSelection,
|
||||
FiltersDropdown,
|
||||
IssueLayoutIcon,
|
||||
} from "@/components/issues/issue-layouts";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
@@ -25,10 +36,11 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { getModuleById } = useModule();
|
||||
const { t } = useTranslation();
|
||||
const layouts = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Board", icon: Kanban },
|
||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||
{ key: "list", i18n_title: "issue.layouts.list", icon: List },
|
||||
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: Kanban },
|
||||
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar },
|
||||
];
|
||||
const { workspaceSlug, projectId, moduleId } = useParams() as {
|
||||
workspaceSlug: string;
|
||||
@@ -116,8 +128,8 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
@@ -139,7 +151,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
@@ -162,7 +174,7 @@ export const ModuleIssuesMobileHeader = observer(() => {
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -4,27 +4,36 @@ import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TModuleFilters } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// 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(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
|
||||
useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : 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(
|
||||
(key: keyof TModuleFilters, value: string | null) => {
|
||||
@@ -45,9 +54,17 @@ const ProjectModulesPage = observer(() => {
|
||||
if (currentProjectDetails?.module_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_MODULE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.module.title")}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -2,27 +2,35 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
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";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { PagesListRoot, PagesListView } from "@/components/pages";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// 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(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const type = searchParams.get("type");
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : 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 pageType = type?.toString();
|
||||
@@ -37,9 +45,17 @@ const ProjectPagesPage = observer(() => {
|
||||
if (currentProjectDetails?.page_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_PAGE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.page.title")}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -43,8 +43,6 @@ const GeneralSettingsPage = observer(() => {
|
||||
);
|
||||
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
|
||||
// const currentNetwork = NETWORK_CHOICES.find((n) => n.key === projectDetails?.network);
|
||||
// const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,7 +6,13 @@ import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Layers, Lock } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
EViewAccess,
|
||||
} from "@plane/constants";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
@@ -16,8 +22,6 @@ import { BreadcrumbLink, Logo } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import { ViewQuickActions } from "@/components/views";
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
import { truncateText } from "@/helpers/string.helper";
|
||||
@@ -242,7 +246,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
projectId={projectId.toString()}
|
||||
labels={projectLabels}
|
||||
@@ -255,7 +259,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -4,29 +4,37 @@ import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, EViewAccess } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TViewFilterProps } from "@plane/types";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ProjectViewsList } from "@/components/views";
|
||||
import { ViewAppliedFiltersList } from "@/components/views/applied-filters";
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// constants
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useProject, useProjectView } from "@/hooks/store";
|
||||
import { useProject, useProjectView, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const ProjectViewsPage = observer(() => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { filters, updateFilters, clearAllFilters } = useProjectView();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Views` : undefined;
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/views" });
|
||||
|
||||
const handleRemoveFilter = useCallback(
|
||||
(key: keyof TViewFilterProps, value: string | EViewAccess | null) => {
|
||||
@@ -53,9 +61,17 @@ const ProjectViewsPage = observer(() => {
|
||||
if (currentProjectDetails?.issue_views_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_VIEW}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.view.title")}
|
||||
description={t("disabled_project.empty_state.view.description")}
|
||||
assetPath={resolvedPath}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.view.primary_button.text"),
|
||||
onClick: () => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,19 +4,19 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// component
|
||||
import { ApiTokenListItem, CreateApiTokenModal } from "@/components/api-token";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { APITokenSettingsLoader } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
|
||||
// store hooks
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// services
|
||||
import { APITokenService } from "@/services/api_token.service";
|
||||
@@ -28,11 +28,14 @@ const ApiTokensPage = observer(() => {
|
||||
const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/api-tokens" });
|
||||
|
||||
const { data: tokens } = useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? API_TOKENS_LIST(workspaceSlug.toString()) : null,
|
||||
@@ -78,7 +81,11 @@ const ApiTokensPage = observer(() => {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_API_TOKENS} />
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_settings.empty_state.api_tokens.title")}
|
||||
description={t("workspace_settings.empty_state.api_tokens.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web constants
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
// plane web helpers
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
@@ -13,6 +14,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => {
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
const { t } = useTranslation();
|
||||
// mobx store
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
@@ -31,7 +33,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => {
|
||||
key={index}
|
||||
onClick={() => router.push(`/${workspaceSlug}${item.href}`)}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.i18n_label)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -4,13 +4,14 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
// plane web helpers
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
@@ -19,6 +20,7 @@ export const WorkspaceSettingsSidebar = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// mobx store
|
||||
const { t } = useTranslation();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
return (
|
||||
@@ -36,7 +38,7 @@ export const WorkspaceSettingsSidebar = observer(() => {
|
||||
isActive={link.highlight(pathname, `/${workspaceSlug}`)}
|
||||
className="text-sm font-medium px-4 py-2"
|
||||
>
|
||||
{link.label}
|
||||
{t(link.i18n_label)}
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -4,18 +4,18 @@ import React, { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { WebhookSettingsLoader } from "@/components/ui";
|
||||
import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const WebhooksListPage = observer(() => {
|
||||
@@ -23,13 +23,15 @@ const WebhooksListPage = observer(() => {
|
||||
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// mobx store
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
|
||||
const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
// derived values
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/webhooks" });
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
@@ -81,7 +83,11 @@ const WebhooksListPage = observer(() => {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<EmptyState type={EmptyStateType.WORKSPACE_SETTINGS_WEBHOOKS} />
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_settings.empty_state.webhooks.title")}
|
||||
description={t("workspace_settings.empty_state.webhooks.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { AllIssueLayoutRoot, GlobalViewsAppliedFiltersRoot } from "@/components/issues";
|
||||
import { GlobalViewsHeader } from "@/components/workspace";
|
||||
// constants
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Layers } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
@@ -14,8 +14,6 @@ import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues";
|
||||
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
@@ -119,7 +117,7 @@ export const GlobalIssuesHeader = observer(() => {
|
||||
isFiltersApplied={isIssueFilterActive(issueFilters)}
|
||||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
@@ -130,7 +128,7 @@ export const GlobalIssuesHeader = observer(() => {
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
|
||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.spreadsheet}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
|
||||
@@ -4,13 +4,15 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { Search } from "lucide-react";
|
||||
// plane imports
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Input } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { GlobalDefaultViewListItem, GlobalViewsList } from "@/components/workspace";
|
||||
// constants
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
|
||||
@@ -18,6 +20,7 @@ const WorkspaceViewsPage = observer(() => {
|
||||
const [query, setQuery] = useState("");
|
||||
// store
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined;
|
||||
|
||||
@@ -36,7 +39,7 @@ const WorkspaceViewsPage = observer(() => {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col h-full w-full vertical-scrollbar scrollbar-lg">
|
||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => v.label.toLowerCase().includes(query.toLowerCase())).map(
|
||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map(
|
||||
(option) => (
|
||||
<GlobalDefaultViewListItem key={option.key} view={option} />
|
||||
)
|
||||
|
||||
@@ -8,6 +8,8 @@ import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
@@ -18,7 +20,6 @@ import { EmptyState } from "@/components/common";
|
||||
// constants
|
||||
import { MEMBER_ACCEPTED } from "@/constants/event-tracker";
|
||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { truncateText } from "@/helpers/string.helper";
|
||||
import { getUserRole } from "@/helpers/user.helper";
|
||||
|
||||
@@ -7,24 +7,27 @@ import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import {
|
||||
ProfileActivityListPage,
|
||||
ProfileSettingContentHeader,
|
||||
ProfileSettingContentWrapper,
|
||||
} from "@/components/profile";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
const ProfileActivityPage = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
const [isEmpty, setIsEmpty] = useState(false);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" });
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
@@ -50,7 +53,13 @@ const ProfileActivityPage = observer(() => {
|
||||
const isLoadMoreVisible = pageCount < totalPages && resultsCount !== 0;
|
||||
|
||||
if (isEmpty) {
|
||||
return <EmptyState type={EmptyStateType.PROFILE_ACTIVITY} layout="screen-detailed" />;
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
title={t("profile.empty_state.activity.title")}
|
||||
description={t("profile.empty_state.activity.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function ProfileNotificationPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={`${t("profile")} - ${t("notifications")}`} />
|
||||
<PageHead title={`${t("profile.label")} - ${t("notifications")}`} />
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileSettingContentHeader
|
||||
title={t("email_notifications")}
|
||||
|
||||
@@ -23,7 +23,7 @@ const ProfileSettingsPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={`${t("profile")} - ${t("general_settings")}`} />
|
||||
<PageHead title={`${t("profile.label")} - ${t("general_settings")}`} />
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileForm user={currentUser} profile={userProfile.data} />
|
||||
</ProfileSettingContentWrapper>
|
||||
|
||||
@@ -5,15 +5,26 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
// icons
|
||||
import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react";
|
||||
import {
|
||||
ChevronLeft,
|
||||
LogOut,
|
||||
MoveLeft,
|
||||
Plus,
|
||||
UserPlus,
|
||||
Activity,
|
||||
Bell,
|
||||
CircleUser,
|
||||
KeyRound,
|
||||
Settings2,
|
||||
} from "lucide-react";
|
||||
// plane imports
|
||||
import { PROFILE_ACTION_LINKS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// constants
|
||||
import { PROFILE_ACTION_LINKS } from "@/constants/profile";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
@@ -36,6 +47,19 @@ const WORKSPACE_ACTION_LINKS = [
|
||||
},
|
||||
];
|
||||
|
||||
export const ProjectActionIcons = ({ type, size, className }: { type: string; size?: number; className?: string }) => {
|
||||
const icons = {
|
||||
profile: CircleUser,
|
||||
security: KeyRound,
|
||||
activity: Activity,
|
||||
appearance: Settings2,
|
||||
notifications: Bell,
|
||||
};
|
||||
|
||||
if (type === undefined) return null;
|
||||
const Icon = icons[type as keyof typeof icons];
|
||||
return <Icon size={size} className={className} />;
|
||||
};
|
||||
export const ProfileLayoutSidebar = observer(() => {
|
||||
// states
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
@@ -145,7 +169,8 @@ export const ProfileLayoutSidebar = observer(() => {
|
||||
isActive={link.highlight(pathname)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 py-[1px]">
|
||||
<link.Icon className="size-4" />
|
||||
<ProjectActionIcons type={link.key} size={16} />
|
||||
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(link.key)}</p>}
|
||||
</div>
|
||||
</SidebarNavItem>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// ui
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Row } from "@plane/ui";
|
||||
// components
|
||||
import {
|
||||
@@ -14,10 +15,10 @@ import {
|
||||
CyclesListItem,
|
||||
} from "@/components/cycles";
|
||||
import useCyclesDetails from "@/components/cycles/active-cycle/use-cycles-details";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCycle } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { ActiveCycleIssueDetails } from "@/store/issue/cycle";
|
||||
|
||||
interface IActiveCycleDetails {
|
||||
@@ -29,9 +30,13 @@ interface IActiveCycleDetails {
|
||||
|
||||
export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { currentProjectActiveCycleId } = useCycle();
|
||||
// derived values
|
||||
const cycleId = propsCycleId ?? currentProjectActiveCycleId;
|
||||
const activeCycleResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/cycle/active" });
|
||||
// fetch cycle details
|
||||
const {
|
||||
handleFiltersUpdate,
|
||||
@@ -43,7 +48,11 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
|
||||
() => (
|
||||
<>
|
||||
{!cycleId || !activeCycle ? (
|
||||
<EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" />
|
||||
<DetailedEmptyState
|
||||
title={t("project_cycles.empty_state.active.title")}
|
||||
description={t("project_cycles.empty_state.active.description")}
|
||||
assetPath={activeCycleResolvedPath}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col border-b border-custom-border-200">
|
||||
{cycleId && (
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS, TActivityFilterOption } from "@plane/constants";
|
||||
import { ActivityFilter } from "@/components/issues";
|
||||
// plane web constants
|
||||
import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS, TActivityFilterOption } from "@/plane-web/constants/issues";
|
||||
|
||||
export type TActivityFilterRoot = {
|
||||
selectedFilters: TActivityFilters[];
|
||||
@@ -20,7 +20,7 @@ export const ActivityFilterRoot: FC<TActivityFilterRoot> = (props) => {
|
||||
const filterKey = key as TActivityFilters;
|
||||
return {
|
||||
key: filterKey,
|
||||
label: value.label,
|
||||
labelTranslationKey: value.labelTranslationKey,
|
||||
isSelected: selectedFilters.includes(filterKey),
|
||||
onClick: () => toggleFilter(filterKey),
|
||||
};
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
"use client";
|
||||
import { FC } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { NETWORK_CHOICES, ETabIndices } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { CustomSelect } from "@plane/ui";
|
||||
// components
|
||||
import { MemberDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
import { NETWORK_CHOICES } from "@/constants/project";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getTabIndex } from "@/helpers/tab-indices.helper";
|
||||
|
||||
@@ -40,7 +38,7 @@ const ProjectAttributes: FC<Props> = (props) => {
|
||||
{currentNetwork ? (
|
||||
<>
|
||||
<currentNetwork.icon className="h-3 w-3" />
|
||||
{currentNetwork.label}
|
||||
{t(currentNetwork.i18n_label)}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-custom-text-400">{t("select_network")}</span>
|
||||
@@ -58,8 +56,8 @@ const ProjectAttributes: FC<Props> = (props) => {
|
||||
<div className="flex items-start gap-2">
|
||||
<network.icon className="h-3.5 w-3.5" />
|
||||
<div className="-mt-1">
|
||||
<p>{network.label}</p>
|
||||
<p className="text-xs text-custom-text-400">{network.description}</p>
|
||||
<p>{t(network.i18n_label)}</p>
|
||||
<p className="text-xs text-custom-text-400">{t(network.description)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CustomSelect.Option>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { PROJECT_UNSPLASH_COVERS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
@@ -11,7 +12,6 @@ import ProjectCommonAttributes from "@/components/project/create/common-attribut
|
||||
import ProjectCreateHeader from "@/components/project/create/header";
|
||||
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
|
||||
import { PROJECT_CREATED } from "@/constants/event-tracker";
|
||||
import { PROJECT_UNSPLASH_COVERS } from "@/constants/project";
|
||||
// helpers
|
||||
import { getRandomEmoji } from "@/helpers/emoji.helper";
|
||||
// hooks
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export * from "./ai";
|
||||
export * from "./estimates";
|
||||
export * from "./gantt-chart";
|
||||
export * from "./issues";
|
||||
export * from "./project";
|
||||
export * from "./user-permissions";
|
||||
export * from "./workspace";
|
||||
export * from "./user-permissions";
|
||||
@@ -1,38 +0,0 @@
|
||||
import { ILayoutDisplayFiltersOptions, TIssueActivityComment } from "@plane/types";
|
||||
|
||||
export enum EActivityFilterType {
|
||||
ACTIVITY = "ACTIVITY",
|
||||
COMMENT = "COMMENT",
|
||||
}
|
||||
|
||||
export type TActivityFilters = EActivityFilterType;
|
||||
|
||||
export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<EActivityFilterType, { label: string }> = {
|
||||
[EActivityFilterType.ACTIVITY]: {
|
||||
label: "Updates",
|
||||
},
|
||||
[EActivityFilterType.COMMENT]: {
|
||||
label: "Comments",
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultActivityFilters: TActivityFilters[] = [EActivityFilterType.ACTIVITY, EActivityFilterType.COMMENT];
|
||||
|
||||
export type TActivityFilterOption = {
|
||||
key: TActivityFilters;
|
||||
label: string;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const filterActivityOnSelectedFilters = (
|
||||
activity: TIssueActivityComment[],
|
||||
filter: TActivityFilters[]
|
||||
): TIssueActivityComment[] =>
|
||||
activity.filter((activity) => filter.includes(activity.activity_type as TActivityFilters));
|
||||
|
||||
export const ENABLE_ISSUE_DEPENDENCIES = false;
|
||||
|
||||
export const ADDITIONAL_ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
[pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions };
|
||||
} = {};
|
||||
@@ -1,72 +0,0 @@
|
||||
// icons
|
||||
import { SettingIcon } from "@/components/icons/attachment";
|
||||
import { Props } from "@/components/icons/types";
|
||||
import { EUserPermissions } from "./user-permissions";
|
||||
// constants
|
||||
|
||||
export const WORKSPACE_SETTINGS = {
|
||||
general: {
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
members: {
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
"billing-and-plans": {
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
export: {
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
webhooks: {
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
"api-tokens": {
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: [EUserPermissions.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserPermissions[];
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
WORKSPACE_SETTINGS["general"],
|
||||
WORKSPACE_SETTINGS["members"],
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["webhooks"],
|
||||
WORKSPACE_SETTINGS["api-tokens"],
|
||||
];
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EViewAccess } from "@plane/constants";
|
||||
import { TPublishViewSettings } from "@plane/types";
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { ViewService as CoreViewService } from "@/services/view.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
import { EViewAccess } from "@plane/constants";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { WorkspaceService as CoreWorkspaceService } from "@/services/workspace.service";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import update from "lodash/update";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// plane package imports
|
||||
import { EIssueServiceType, E_SORT_ORDER } from "@plane/constants";
|
||||
import { EIssueServiceType, E_SORT_ORDER, EActivityFilterType } from "@plane/constants";
|
||||
import {
|
||||
TIssueActivityComment,
|
||||
TIssueActivity,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
TIssueServiceType,
|
||||
} from "@plane/types";
|
||||
// plane web constants
|
||||
import { EActivityFilterType } from "@/plane-web/constants/issues";
|
||||
// services
|
||||
import { IssueActivityService } from "@/services/issue";
|
||||
// store
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { NETWORK_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@/components/common";
|
||||
// constants
|
||||
import { NETWORK_CHOICES } from "@/constants/project";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
@@ -16,6 +17,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||
const { getCycleById } = useCycle();
|
||||
const { getModuleById } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
@@ -91,7 +93,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<h6 className="text-custom-text-200">Network</h6>
|
||||
<span>{NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? ""}</span>
|
||||
<span>{t(NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.i18n_label ?? "")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,14 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
// types
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
|
||||
// component
|
||||
import { SelectMonthModal } from "@/components/automation";
|
||||
// constants
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "@/constants/project";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
@@ -27,6 +28,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
@@ -90,8 +92,8 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
<span className="text-sm">{month.label}</span>
|
||||
<CustomSelect.Option key={month.i18n_label} value={month.value}>
|
||||
<span className="text-sm">{t(month.i18n_label, { month: month.value })}</span>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { ArchiveX } from "lucide-react";
|
||||
// types
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
|
||||
// component
|
||||
import { SelectMonthModal } from "@/components/automation";
|
||||
// constants
|
||||
import { PROJECT_AUTOMATION_MONTHS } from "@/constants/project";
|
||||
// hooks
|
||||
import { useProject, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
@@ -28,6 +29,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
|
||||
|
||||
@@ -117,8 +119,8 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
>
|
||||
<>
|
||||
{PROJECT_AUTOMATION_MONTHS.map((month) => (
|
||||
<CustomSelect.Option key={month.label} value={month.value}>
|
||||
{month.label}
|
||||
<CustomSelect.Option key={month.i18n_label} value={month.value}>
|
||||
{t(month.i18n_label, { month: month.value })}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
<button
|
||||
|
||||
@@ -5,12 +5,11 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Check } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssuesStoreType, ISSUE_PRIORITIES } from "@plane/constants";
|
||||
// plane types
|
||||
import { TIssue, TIssuePriorities } from "@plane/types";
|
||||
// mobx store
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
import { ISSUE_PRIORITIES } from "@/constants/issue";
|
||||
import { useIssues } from "@/hooks/store";
|
||||
// ui
|
||||
// types
|
||||
|
||||
@@ -4,13 +4,15 @@ import { Command } from "cmdk";
|
||||
// hooks
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { SettingIcon } from "@/components/icons";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane wev constants
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace";
|
||||
// plane web helpers
|
||||
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
|
||||
|
||||
@@ -25,6 +27,7 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) =
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
// mobx store
|
||||
const { t } = useTranslation();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
|
||||
@@ -46,8 +49,8 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) =
|
||||
>
|
||||
<Link href={`/${workspaceSlug}${setting.href}`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<setting.Icon className="h-4 w-4 text-custom-text-200" />
|
||||
{setting.label}
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
{t(setting.i18n_label)}
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
|
||||
@@ -7,9 +7,9 @@ import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { FolderPlus, Search, Settings } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// types
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceSearchResults } from "@plane/types";
|
||||
// ui
|
||||
import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import {
|
||||
@@ -23,9 +23,7 @@ import {
|
||||
CommandPaletteThemeActions,
|
||||
CommandPaletteWorkspaceSettingsActions,
|
||||
} from "@/components/command-palette";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// fetch-keys
|
||||
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
@@ -36,6 +34,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// plane web services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
@@ -47,10 +46,9 @@ const workspaceService = new WorkspaceService();
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const CommandModal: React.FC = observer(() => {
|
||||
// hooks
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { canPerformAnyCreateAction } = useUser();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, issueId } = useParams();
|
||||
// states
|
||||
const [placeholder, setPlaceholder] = useState("Type a command or search...");
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
@@ -70,26 +68,25 @@ export const CommandModal: React.FC = observer(() => {
|
||||
});
|
||||
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);
|
||||
const [pages, setPages] = useState<string[]>([]);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { canPerformAnyCreateAction } = useUser();
|
||||
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
|
||||
useCommandPalette();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
// router params
|
||||
const { workspaceSlug, projectId, issueId } = useParams();
|
||||
|
||||
// derived values
|
||||
const page = pages[pages.length - 1];
|
||||
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||
|
||||
const { baseTabIndex } = getTabIndex(undefined, isMobile);
|
||||
|
||||
const canPerformWorkspaceActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" });
|
||||
|
||||
// TODO: update this to mobx store
|
||||
const { data: issueDetails } = useSWR(
|
||||
@@ -268,7 +265,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||
|
||||
{!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState type={EmptyStateType.COMMAND_K_SEARCH_EMPTY_STATE} layout="screen-simple" />
|
||||
<SimpleEmptyState title={t("command_k.empty_state.search.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// constants
|
||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters";
|
||||
// plane constants
|
||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@plane/constants";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { capitalizeFirstLetter } from "@/helpers/string.helper";
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
// plane constants
|
||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@plane/constants";
|
||||
import { DateFilterModal } from "@/components/core";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// constants
|
||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters";
|
||||
// helpers
|
||||
import { isInDateFormat } from "@/helpers/date-time.helper";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
interface IContentOverflowWrapper {
|
||||
@@ -31,6 +32,9 @@ export const ContentOverflowWrapper = observer((props: IContentOverflowWrapper)
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!contentRef?.current) return;
|
||||
|
||||
@@ -142,7 +146,7 @@ export const ContentOverflowWrapper = observer((props: IContentOverflowWrapper)
|
||||
onClick={handleToggle}
|
||||
disabled={isTransitioning}
|
||||
>
|
||||
{showAll ? "Show less" : "Show all"}
|
||||
{showAll ? t("show_less") : t("show_all")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -6,19 +6,18 @@ import { useParams } from "next/navigation";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Search } from "lucide-react";
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { EIssuesStoreType } from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ISearchIssueResponse, IUser } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useIssues } from "@/hooks/store";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
// services
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { ProjectService } from "@/services/project";
|
||||
// local components
|
||||
import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item";
|
||||
@@ -39,16 +38,19 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
const { isOpen, onClose } = props;
|
||||
// router params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// hooks
|
||||
const {
|
||||
issues: { removeBulkIssues },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
// hooks
|
||||
const {
|
||||
issues: { removeBulkIssues },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const debouncedSearchTerm: string = useDebounce(query, 500);
|
||||
const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" });
|
||||
const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || !workspaceSlug || !projectId) return;
|
||||
@@ -131,12 +133,11 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
</li>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState
|
||||
type={
|
||||
query === "" ? EmptyStateType.ISSUE_RELATION_EMPTY_STATE : EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE
|
||||
}
|
||||
layout="screen-simple"
|
||||
/>
|
||||
{query === "" ? (
|
||||
<SimpleEmptyState title={t("issue_relation.empty_state.no_issues.title")} assetPath={issuesResolvedPath} />
|
||||
) : (
|
||||
<SimpleEmptyState title={t("issue_relation.empty_state.search.title")} assetPath={searchResolvedPath} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
// components
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// types
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// constants
|
||||
// components
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
interface EmptyStateProps {
|
||||
issues: ISearchIssueResponse[];
|
||||
@@ -19,18 +19,28 @@ export const IssueSearchModalEmptyState: React.FC<EmptyStateProps> = ({
|
||||
debouncedSearchTerm,
|
||||
isSearching,
|
||||
}) => {
|
||||
const renderEmptyState = (type: EmptyStateType) => (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState type={type} layout="screen-simple" />
|
||||
</div>
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" });
|
||||
const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" });
|
||||
|
||||
const EmptyStateContainer = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">{children}</div>
|
||||
);
|
||||
|
||||
const emptyState =
|
||||
issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && !isSearching
|
||||
? renderEmptyState(EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE)
|
||||
: issues.length === 0
|
||||
? renderEmptyState(EmptyStateType.ISSUE_RELATION_EMPTY_STATE)
|
||||
: null;
|
||||
|
||||
return emptyState;
|
||||
if (issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && !isSearching) {
|
||||
return (
|
||||
<EmptyStateContainer>
|
||||
<SimpleEmptyState title={t("issue_relation.empty_state.no_issues.title")} assetPath={issuesResolvedPath} />
|
||||
</EmptyStateContainer>
|
||||
);
|
||||
} else if (issues.length === 0) {
|
||||
return (
|
||||
<EmptyStateContainer>
|
||||
<SimpleEmptyState title={t("issue_relation.empty_state.search.title")} assetPath={searchResolvedPath} />
|
||||
</EmptyStateContainer>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -6,17 +6,16 @@ import { observer } from "mobx-react";
|
||||
import { CalendarCheck } from "lucide-react";
|
||||
// headless ui
|
||||
import { Tab } from "@headlessui/react";
|
||||
// types
|
||||
// plane imports
|
||||
import { EIssuesStoreType } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui";
|
||||
// components
|
||||
import { SingleProgressStats } from "@/components/core";
|
||||
import { StateDropdown } from "@/components/dropdowns";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper";
|
||||
@@ -26,6 +25,7 @@ import { useIssueDetail, useIssues } from "@/hooks/store";
|
||||
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
// plane web components
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
// store
|
||||
import { ActiveCycleIssueDetails } from "@/store/issue/cycle";
|
||||
@@ -41,11 +41,18 @@ export type ActiveCycleStatsProps = {
|
||||
|
||||
export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycle, cycleId, handleFiltersUpdate, cycleIssueDetails } = props;
|
||||
|
||||
// local storage
|
||||
const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees");
|
||||
|
||||
// refs
|
||||
const issuesContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
// states
|
||||
const [issuesLoaderElement, setIssueLoaderElement] = useState<HTMLDivElement | null>(null);
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const priorityResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/priority" });
|
||||
const assigneesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/assignee" });
|
||||
const labelsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/label" });
|
||||
|
||||
const currentValue = (tab: string | null) => {
|
||||
switch (tab) {
|
||||
@@ -231,10 +238,9 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.ACTIVE_CYCLE_PRIORITY_ISSUE_EMPTY_STATE}
|
||||
layout="screen-simple"
|
||||
size="sm"
|
||||
<SimpleEmptyState
|
||||
title={t("active_cycle.empty_state.priority_issue.title")}
|
||||
assetPath={priorityResolvedPath}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -293,10 +299,9 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
})
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.ACTIVE_CYCLE_ASSIGNEE_EMPTY_STATE}
|
||||
layout="screen-simple"
|
||||
size="sm"
|
||||
<SimpleEmptyState
|
||||
title={t("active_cycle.empty_state.assignee.title")}
|
||||
assetPath={assigneesResolvedPath}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -336,7 +341,7 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
))
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_LABEL_EMPTY_STATE} layout="screen-simple" size="sm" />
|
||||
<SimpleEmptyState title={t("active_cycle.empty_state.label.title")} assetPath={labelsResolvedPath} />
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, TCycleEstimateType } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import ProgressChart from "@/components/core/sidebar/progress-chart";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { useCycle, useProjectEstimates } from "@/hooks/store";
|
||||
import { useCycle } from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EEstimateSystem } from "@/plane-web/constants/estimates";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { EstimateTypeDropdown } from "../dropdowns/estimate-type-dropdown";
|
||||
|
||||
export type ActiveCycleProductivityProps = {
|
||||
@@ -21,11 +22,13 @@ export type ActiveCycleProductivityProps = {
|
||||
|
||||
export const ActiveCycleProductivity: FC<ActiveCycleProductivityProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycle } = props;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { getEstimateTypeByCycleId, setEstimateType } = useCycle();
|
||||
|
||||
// derived values
|
||||
const estimateType: TCycleEstimateType = (cycle && getEstimateTypeByCycleId(cycle.id)) || "issues";
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/chart" });
|
||||
|
||||
const onChange = async (value: TCycleEstimateType) => {
|
||||
if (!workspaceSlug || !projectId || !cycle || !cycle.id) return;
|
||||
@@ -95,7 +98,7 @@ export const ActiveCycleProductivity: FC<ActiveCycleProductivityProps> = observe
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_CHART_EMPTY_STATE} layout="screen-simple" size="sm" />
|
||||
<SimpleEmptyState title={t("active_cycle.empty_state.chart.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -4,14 +4,14 @@ import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane package imports
|
||||
import { PROGRESS_STATE_GROUPS_DETAILS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, IIssueFilterOptions } from "@plane/types";
|
||||
import { LinearProgressIndicator, Loader } from "@plane/ui";
|
||||
// components
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export type ActiveCycleProgressProps = {
|
||||
cycle: ICycle | null;
|
||||
@@ -22,9 +22,10 @@ export type ActiveCycleProgressProps = {
|
||||
|
||||
export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = observer((props) => {
|
||||
const { handleFiltersUpdate, cycle } = props;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { groupedProjectStates } = useProjectState();
|
||||
|
||||
// derived values
|
||||
const progressIndicatorData = PROGRESS_STATE_GROUPS_DETAILS.map((group, index) => ({
|
||||
id: index,
|
||||
@@ -40,6 +41,7 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = observer((props
|
||||
backlog: cycle?.backlog_issues,
|
||||
}
|
||||
: {};
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/progress" });
|
||||
|
||||
return cycle && cycle.hasOwnProperty("started_issues") ? (
|
||||
<div className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg">
|
||||
@@ -101,7 +103,7 @@ export const ActiveCycleProgress: FC<ActiveCycleProgressProps> = observer((props
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState type={EmptyStateType.ACTIVE_CYCLE_PROGRESS_EMPTY_STATE} layout="screen-simple" size="sm" />
|
||||
<SimpleEmptyState title={t("active_cycle.empty_state.progress.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
// helpers
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters";
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants";
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { capitalizeFirstLetter } from "@/helpers/string.helper";
|
||||
// constants
|
||||
|
||||
@@ -2,28 +2,31 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// types
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
import { ArchivedCyclesView, CycleAppliedFiltersList } from "@/components/cycles";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { CycleModuleListLayout } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useCycle, useCycleFilter } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
export const ArchivedCycleLayoutRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { fetchArchivedCycles, currentProjectArchivedCycleIds, loader } = useCycle();
|
||||
// cycle filters hook
|
||||
const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useCycleFilter();
|
||||
// derived values
|
||||
const totalArchivedCycles = currentProjectArchivedCycleIds?.length ?? 0;
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/archived/empty-cycles" });
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `ARCHIVED_CYCLES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
|
||||
@@ -64,7 +67,11 @@ export const ArchivedCycleLayoutRoot: React.FC = observer(() => {
|
||||
)}
|
||||
{totalArchivedCycles === 0 ? (
|
||||
<div className="h-full place-items-center">
|
||||
<EmptyState type={EmptyStateType.PROJECT_ARCHIVED_NO_CYCLES} />
|
||||
<DetailedEmptyState
|
||||
title={t("project_cycles.empty_state.archived.title")}
|
||||
description={t("project_cycles.empty_state.archived.description")}
|
||||
assetPath={resolvedPath}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
|
||||
@@ -4,12 +4,13 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// types
|
||||
import { PROJECT_ERROR_MESSAGES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { CYCLE_DELETED } from "@/constants/event-tracker";
|
||||
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
|
||||
// hooks
|
||||
import { useEventTracker, useCycle } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
@@ -29,6 +30,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
// store hooks
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { deleteCycle } = useCycle();
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { cycleId } = useParams();
|
||||
@@ -59,9 +61,9 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
? PROJECT_ERROR_MESSAGES.permissionError
|
||||
: PROJECT_ERROR_MESSAGES.cycleDeleteError;
|
||||
setToast({
|
||||
title: currentError.title,
|
||||
title: t(currentError.i18n_title),
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: currentError.message,
|
||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||
});
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_DELETED,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
// constants
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants";
|
||||
// components
|
||||
import { DateFilterModal } from "@/components/core";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// constants
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters";
|
||||
|
||||
// helpers
|
||||
import { isInDateFormat } from "@/helpers/date-time.helper";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
// constants
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants";
|
||||
// components
|
||||
import { DateFilterModal } from "@/components/core";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||
// constants
|
||||
import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters";
|
||||
|
||||
// helpers
|
||||
import { isInDateFormat } from "@/helpers/date-time.helper";
|
||||
|
||||
@@ -17,7 +17,6 @@ type Props = {
|
||||
|
||||
export const FilterStartDate: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// plane imports
|
||||
import { ETabIndices } from "@plane/constants";
|
||||
// types
|
||||
import { ICycle } from "@plane/types";
|
||||
// ui
|
||||
@@ -9,7 +11,6 @@ import { Button, Input, TextArea } from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
||||
// constants
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { shouldRenderProject } from "@/helpers/project.helper";
|
||||
|
||||
@@ -2,13 +2,15 @@ import { useCallback, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes } from "@plane/constants";
|
||||
import { EIssueLayoutTypes, ISSUE_LAYOUT_MAP } from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane ui
|
||||
import { Dropdown } from "@plane/ui";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
import { ISSUE_LAYOUT_MAP } from "@/constants/issue";
|
||||
// components
|
||||
import { IssueLayoutIcon } from "@/components/issues";
|
||||
|
||||
type TLayoutDropDown = {
|
||||
onChange: (value: EIssueLayoutTypes) => void;
|
||||
@@ -18,6 +20,8 @@ type TLayoutDropDown = {
|
||||
|
||||
export const LayoutDropDown = observer((props: TLayoutDropDown) => {
|
||||
const { onChange, value = EIssueLayoutTypes.LIST, disabledLayouts = [] } = props;
|
||||
// plane i18n
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const availableLayouts = useMemo(
|
||||
() => Object.values(ISSUE_LAYOUT_MAP).filter((layout) => !disabledLayouts.includes(layout.key)),
|
||||
@@ -35,11 +39,10 @@ export const LayoutDropDown = observer((props: TLayoutDropDown) => {
|
||||
|
||||
const buttonContent = useCallback((isOpen: boolean, buttonValue: string | string[] | undefined) => {
|
||||
const dropdownValue = ISSUE_LAYOUT_MAP[buttonValue as EIssueLayoutTypes];
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center text-custom-text-200">
|
||||
<dropdownValue.icon strokeWidth={2} className={`size-3.5 text-custom-text-200`} />
|
||||
<span className="font-medium text-xs">{dropdownValue.label}</span>
|
||||
<IssueLayoutIcon layout={dropdownValue.key} strokeWidth={2} className={`size-3.5 text-custom-text-200`} />
|
||||
<span className="font-medium text-xs">{t(dropdownValue.i18n_label)}</span>
|
||||
</div>
|
||||
);
|
||||
}, []);
|
||||
@@ -50,8 +53,8 @@ export const LayoutDropDown = observer((props: TLayoutDropDown) => {
|
||||
return (
|
||||
<div className={cn("flex gap-2 items-center text-custom-text-200 w-full justify-between")}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<dropdownValue.icon strokeWidth={2} className={`size-3 text-custom-text-200`} />
|
||||
<span className="font-medium text-xs">{dropdownValue.label}</span>
|
||||
<IssueLayoutIcon layout={dropdownValue.key} strokeWidth={2} className={`size-3 text-custom-text-200`} />
|
||||
<span className="font-medium text-xs">{t(dropdownValue.i18n_label)}</span>
|
||||
</div>
|
||||
{props.selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,12 @@ import { useTheme } from "next-themes";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Search, SignalHigh } from "lucide-react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { ISSUE_PRIORITIES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { TIssuePriorities } from "@plane/types";
|
||||
// ui
|
||||
import { ComboDropDown, PriorityIcon, Tooltip } from "@plane/ui";
|
||||
// constants
|
||||
import { ISSUE_PRIORITIES } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
@@ -77,7 +76,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipHeading={t("priority")}
|
||||
tooltipContent={t(priorityDetails?.key ?? "none")}
|
||||
tooltipContent={priorityDetails?.title ?? t("common.none")}
|
||||
disabled={!showTooltip}
|
||||
isMobile={isMobile}
|
||||
renderByDefault={renderToolTipByDefault}
|
||||
@@ -121,7 +120,7 @@ const BorderButton = (props: ButtonProps) => {
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
{!hideText && <span className="flex-grow truncate">{t(priorityDetails?.key ?? "priority") ?? placeholder}</span>}
|
||||
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title ?? placeholder}</span>}
|
||||
{dropdownArrow && (
|
||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
||||
)}
|
||||
@@ -204,7 +203,9 @@ const BackgroundButton = (props: ButtonProps) => {
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
{!hideText && <span className="flex-grow truncate">{t(priorityDetails?.key ?? "priority") ?? placeholder}</span>}
|
||||
{!hideText && (
|
||||
<span className="flex-grow truncate">{priorityDetails?.title ?? t("common.priority") ?? placeholder}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
||||
)}
|
||||
@@ -244,7 +245,7 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipHeading={t("priority")}
|
||||
tooltipContent={t(priorityDetails?.key ?? "none")}
|
||||
tooltipContent={priorityDetails?.title ?? t("common.none")}
|
||||
disabled={!showTooltip}
|
||||
isMobile={isMobile}
|
||||
renderByDefault={renderToolTipByDefault}
|
||||
@@ -289,7 +290,9 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
{!hideText && <span className="flex-grow truncate">{t(priorityDetails?.key ?? "priority") ?? placeholder}</span>}
|
||||
{!hideText && (
|
||||
<span className="flex-grow truncate">{priorityDetails?.title ?? t("common.priority") ?? placeholder}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
||||
)}
|
||||
@@ -299,6 +302,8 @@ const TransparentButton = (props: ButtonProps) => {
|
||||
};
|
||||
|
||||
export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
//hooks
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
button,
|
||||
buttonClassName,
|
||||
@@ -312,7 +317,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
highlightUrgent = true,
|
||||
onChange,
|
||||
onClose,
|
||||
placeholder = "Priority",
|
||||
placeholder = t("common.priority"),
|
||||
placement,
|
||||
showTooltip = false,
|
||||
tabIndex,
|
||||
@@ -340,8 +345,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
},
|
||||
],
|
||||
});
|
||||
//hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
// next-themes
|
||||
// TODO: remove this after new theming implementation
|
||||
const { resolvedTheme } = useTheme();
|
||||
@@ -352,7 +356,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<PriorityIcon priority={priority.key} size={14} withContainer />
|
||||
<span className="flex-grow truncate">{t(priority.key)}</span>
|
||||
<span className="flex-grow truncate">{priority.title}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -3,10 +3,11 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { usePopper } from "react-popper";
|
||||
// plane imports
|
||||
import { ROLE } from "@plane/constants";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// constants
|
||||
import { ROLE } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
// components
|
||||
import { Button, TButtonSizes, TButtonVariant } from "@plane/ui";
|
||||
// constant
|
||||
import { EMPTY_STATE_DETAILS, EmptyStateType } from "@/constants/empty-state";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { ComicBoxButton } from "./comic-box-button";
|
||||
|
||||
export type EmptyStateProps = {
|
||||
size?: TButtonSizes;
|
||||
type: EmptyStateType;
|
||||
layout?: "screen-detailed" | "screen-simple";
|
||||
additionalPath?: string;
|
||||
primaryButtonConfig?: {
|
||||
size?: TButtonSizes;
|
||||
variant?: TButtonVariant;
|
||||
};
|
||||
primaryButtonOnClick?: () => void;
|
||||
primaryButtonLink?: string;
|
||||
secondaryButtonOnClick?: () => void;
|
||||
};
|
||||
|
||||
export const EmptyState: React.FC<EmptyStateProps> = observer((props) => {
|
||||
const {
|
||||
size = "lg",
|
||||
type,
|
||||
layout = "screen-detailed",
|
||||
additionalPath = "",
|
||||
primaryButtonConfig = {
|
||||
size: "lg",
|
||||
variant: "primary",
|
||||
},
|
||||
primaryButtonOnClick,
|
||||
primaryButtonLink,
|
||||
secondaryButtonOnClick,
|
||||
} = props;
|
||||
// store
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
// if empty state type is not found
|
||||
if (!EMPTY_STATE_DETAILS[type]) return null;
|
||||
|
||||
// current empty state details
|
||||
const { key, title, description, path, primaryButton, secondaryButton, accessType, access } =
|
||||
EMPTY_STATE_DETAILS[type];
|
||||
// resolved empty state path
|
||||
const resolvedEmptyStatePath = `${additionalPath && additionalPath !== "" ? `${path}${additionalPath}` : path}-${
|
||||
resolvedTheme === "light" ? "light" : "dark"
|
||||
}.webp`;
|
||||
// permission
|
||||
const isEditingAllowed =
|
||||
access &&
|
||||
accessType &&
|
||||
allowPermissions(
|
||||
access,
|
||||
accessType === "workspace" ? EUserPermissionsLevel.WORKSPACE : EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const anyButton = primaryButton || secondaryButton;
|
||||
|
||||
// primary button
|
||||
const renderPrimaryButton = () => {
|
||||
if (!primaryButton) return null;
|
||||
|
||||
const commonProps = {
|
||||
size: primaryButtonConfig.size,
|
||||
variant: primaryButtonConfig.variant,
|
||||
prependIcon: primaryButton.icon,
|
||||
onClick: primaryButtonOnClick ? primaryButtonOnClick : undefined,
|
||||
disabled: !isEditingAllowed,
|
||||
};
|
||||
|
||||
if (primaryButton.comicBox) {
|
||||
return (
|
||||
<ComicBoxButton
|
||||
label={primaryButton.text}
|
||||
icon={primaryButton.icon}
|
||||
title={primaryButton.comicBox?.title}
|
||||
description={primaryButton.comicBox?.description}
|
||||
onClick={primaryButtonOnClick}
|
||||
disabled={!isEditingAllowed}
|
||||
/>
|
||||
);
|
||||
} else if (primaryButtonLink) {
|
||||
return (
|
||||
<Link href={primaryButtonLink}>
|
||||
<Button {...commonProps}>{primaryButton.text}</Button>
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return <Button {...commonProps}>{primaryButton.text}</Button>;
|
||||
}
|
||||
};
|
||||
// secondary button
|
||||
const renderSecondaryButton = () => {
|
||||
if (!secondaryButton) return null;
|
||||
|
||||
return (
|
||||
<Button
|
||||
size={size}
|
||||
variant="neutral-primary"
|
||||
prependIcon={secondaryButton.icon}
|
||||
onClick={secondaryButtonOnClick}
|
||||
disabled={!isEditingAllowed}
|
||||
>
|
||||
{secondaryButton.text}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{layout === "screen-detailed" && (
|
||||
<div className="flex items-center justify-center min-h-full min-w-full overflow-y-auto py-10 md:px-20 px-5">
|
||||
<div
|
||||
className={cn("flex flex-col gap-5", {
|
||||
"md:min-w-[24rem] max-w-[45rem]": size === "sm",
|
||||
"md:min-w-[30rem] max-w-[60rem]": size === "lg",
|
||||
})}
|
||||
>
|
||||
<div className="flex flex-col gap-1.5 flex-shrink">
|
||||
{description ? (
|
||||
<>
|
||||
<h3 className="text-xl font-semibold">{title}</h3>
|
||||
<p className="text-sm">{description}</p>
|
||||
</>
|
||||
) : (
|
||||
<h3 className="text-xl font-medium">{title}</h3>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{path && (
|
||||
<Image
|
||||
src={resolvedEmptyStatePath}
|
||||
alt={key || "button image"}
|
||||
width={384}
|
||||
height={250}
|
||||
layout="responsive"
|
||||
lazyBoundary="100%"
|
||||
/>
|
||||
)}
|
||||
|
||||
{anyButton && (
|
||||
<div className="relative flex items-center justify-center gap-2 flex-shrink-0 w-full">
|
||||
{renderPrimaryButton()}
|
||||
{renderSecondaryButton()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{layout === "screen-simple" && (
|
||||
<div className="text-center flex flex-col gap-2.5 items-center">
|
||||
<div className={`${size === "sm" ? "h-24 w-24" : "h-28 w-28"}`}>
|
||||
<Image
|
||||
src={resolvedEmptyStatePath}
|
||||
alt={key || "button image"}
|
||||
width={size === "sm" ? 78 : 96}
|
||||
height={size === "sm" ? 78 : 96}
|
||||
layout="responsive"
|
||||
lazyBoundary="100%"
|
||||
/>
|
||||
</div>
|
||||
{description ? (
|
||||
<>
|
||||
<h3 className="text-lg font-medium text-custom-text-300 whitespace-pre-line">{title}</h3>
|
||||
<p className="text-base font-medium text-custom-text-400 whitespace-pre-line">{description}</p>
|
||||
</>
|
||||
) : (
|
||||
<h3 className="text-sm font-medium text-custom-text-400 whitespace-pre-line">{title}</h3>
|
||||
)}
|
||||
{anyButton && (
|
||||
<div className="relative flex items-center justify-center gap-2 flex-shrink-0 w-full">
|
||||
{renderPrimaryButton()}
|
||||
{renderSecondaryButton()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./empty-state";
|
||||
export * from "./helper";
|
||||
export * from "./comic-box-button";
|
||||
export * from "./detailed-empty-state-root";
|
||||
|
||||
@@ -6,7 +6,7 @@ import Image from "next/image";
|
||||
// utils
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type EmptyStateSize = "sm" | "md" | "lg";
|
||||
type EmptyStateSize = "sm" | "lg";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
@@ -17,12 +17,8 @@ type Props = {
|
||||
|
||||
const sizeConfig = {
|
||||
sm: {
|
||||
container: "size-20",
|
||||
dimensions: 78,
|
||||
},
|
||||
md: {
|
||||
container: "size-24",
|
||||
dimensions: 80,
|
||||
dimensions: 78,
|
||||
},
|
||||
lg: {
|
||||
container: "size-28",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user