Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/i18n/src/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,11 @@
}
},
"states": {
"describe_this_state_for_your_members": "Describe this state for your members."
"describe_this_state_for_your_members": "Describe this state for your members.",
"empty_state": {
"title": "No states available for the {groupKey} group",
"description": "Please create a new state"
}
},
"labels": {
"label_title": "Label title",
Expand Down
6 changes: 5 additions & 1 deletion packages/i18n/src/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,11 @@
}
},
"states": {
"describe_this_state_for_your_members": "Describe este estado para tus miembros."
"describe_this_state_for_your_members": "Describe este estado para tus miembros.",
"empty_state": {
"title": "No estados disponibles para el grupo {groupKey}",
"description": "Por favor, crea un nuevo estado"
}
},
"labels": {
"label_title": "Título de la etiqueta",
Expand Down
6 changes: 5 additions & 1 deletion packages/i18n/src/locales/fr/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,11 @@
}
},
"states": {
"describe_this_state_for_your_members": "Décrivez cet état pour vos membres."
"describe_this_state_for_your_members": "Décrivez cet état pour vos membres.",
"empty_state": {
"title": "Aucun état disponible pour le groupe {groupKey}",
"description": "Veuillez créer un nouvel état"
}
},
"labels": {
"label_title": "Titre de l'étiquette",
Expand Down
6 changes: 5 additions & 1 deletion packages/i18n/src/locales/ja/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,11 @@
}
},
"states": {
"describe_this_state_for_your_members": "このステータスについてメンバーに説明してください。"
"describe_this_state_for_your_members": "このステータスについてメンバーに説明してください。",
"empty_state": {
"title": "{groupKey}グループのステータスがありません",
"description": "新しいステータスを作成してください"
}
},
"labels": {
"label_title": "ラベルタイトル",
Expand Down
6 changes: 5 additions & 1 deletion packages/i18n/src/locales/zh-CN/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,11 @@
}
},
"states": {
"describe_this_state_for_your_members": "为您的成员描述此状态。"
"describe_this_state_for_your_members": "为您的成员描述此状态。",
"empty_state": {
"title": "{groupKey} 组中没有状态",
"description": "请创建一个新状态"
}
},
"labels": {
"label_title": "标签标题",
Expand Down
22 changes: 16 additions & 6 deletions web/core/components/project-states/group-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import { FC, useState, useRef } from "react";
import { observer } from "mobx-react";
import { ChevronDown, Plus } from "lucide-react";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { IState, TStateGroups } from "@plane/types";
// components
import { StateGroupIcon } from "@plane/ui";
import { cn } from "@plane/utils";
// components
import { StateList, StateCreate } from "@/components/project-states";
// hooks
import { useUserPermissions } from "@/hooks/store";
Expand All @@ -34,17 +36,18 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
handleExpand,
handleGroupCollapse,
} = props;
// refs
const dropElementRef = useRef<HTMLDivElement | null>(null);
// plane hooks
const { t } = useTranslation();
// store hooks
const { allowPermissions } = useUserPermissions();
// state
const [createState, setCreateState] = useState(false);

// derived values
const currentStateExpanded = groupsExpanded.includes(groupKey);
// refs
const dropElementRef = useRef<HTMLDivElement | null>(null);

const isEditable = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
const shouldShowEmptyState = states.length === 0 && currentStateExpanded && !createState;

return (
<div
Expand Down Expand Up @@ -80,7 +83,14 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
</div>
</div>

{groupedStates[groupKey].length > 0 && currentStateExpanded && (
{shouldShowEmptyState && (
<div className="flex flex-col justify-center items-center h-full py-4 text-sm text-custom-text-300">
<div>{t("project_settings.states.empty_state.title", { groupKey })}</div>
{isEditable && <div>{t("project_settings.states.empty_state.description")}</div>}
</div>
)}

{currentStateExpanded && (
<div id="group-droppable-container">
<StateList
workspaceSlug={workspaceSlug}
Expand Down
18 changes: 16 additions & 2 deletions web/core/store/state.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import groupBy from "lodash/groupBy";
import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// types
// plane imports
import { STATE_GROUPS } from "@plane/constants";
import { IState } from "@plane/types";
// helpers
import { convertStringArrayToBooleanObject } from "@/helpers/array.helper";
Expand Down Expand Up @@ -106,7 +107,20 @@ export class StateStore implements IStateStore {
*/
get groupedProjectStates() {
if (!this.router.projectId) return;
return groupBy(this.projectStates, "group") as Record<string, IState[]>;

// First group the existing states
const groupedStates = groupBy(this.projectStates, "group") as Record<string, IState[]>;

// Ensure all STATE_GROUPS are present
const allGroups = Object.keys(STATE_GROUPS).reduce(
(acc, group) => ({
...acc,
[group]: groupedStates[group] || [],
}),
{} as Record<string, IState[]>
);
Comment on lines +115 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize the reduce operation to avoid O(n²) complexity.

The spread operator in the reduce accumulator can lead to quadratic time complexity as it creates a new object and copies all properties in each iteration.

Consider this more efficient approach:

-    const allGroups = Object.keys(STATE_GROUPS).reduce(
-      (acc, group) => ({
-        ...acc,
-        [group]: groupedStates[group] || [],
-      }),
-      {} as Record<string, IState[]>
-    );
+    const allGroups = {} as Record<string, IState[]>;
+    for (const group of Object.keys(STATE_GROUPS)) {
+      allGroups[group] = groupedStates[group] || [];
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allGroups = Object.keys(STATE_GROUPS).reduce(
(acc, group) => ({
...acc,
[group]: groupedStates[group] || [],
}),
{} as Record<string, IState[]>
);
const allGroups = {} as Record<string, IState[]>;
for (const group of Object.keys(STATE_GROUPS)) {
allGroups[group] = groupedStates[group] || [];
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 117-117: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)


return allGroups;
}

/**
Expand Down