diff --git a/plane-src/apps/api/plane/app/views/workspace/home.py b/plane-src/apps/api/plane/app/views/workspace/home.py
index ec35aaf..1299a5c 100644
--- a/plane-src/apps/api/plane/app/views/workspace/home.py
+++ b/plane-src/apps/api/plane/app/views/workspace/home.py
@@ -34,9 +34,7 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
if key not in ["quick_tutorial", "new_at_plane"]
]
- sort_order_counter = 1
-
- for preference in keys:
+ for sort_order_counter, preference in enumerate(keys, start=1):
if preference not in get_preference.values_list("key", flat=True):
create_preference_keys.append(preference)
@@ -55,7 +53,6 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
batch_size=10,
ignore_conflicts=True,
)
- sort_order_counter += 1
preference = WorkspaceHomePreference.objects.filter(user=request.user, workspace_id=workspace.id)
diff --git a/plane-src/apps/api/plane/db/models/workspace.py b/plane-src/apps/api/plane/db/models/workspace.py
index 919e2fe..c7e367a 100644
--- a/plane-src/apps/api/plane/db/models/workspace.py
+++ b/plane-src/apps/api/plane/db/models/workspace.py
@@ -380,6 +380,7 @@ class WorkspaceHomePreference(BaseModel):
QUICK_LINKS = "quick_links", "Quick Links"
RECENTS = "recents", "Recents"
MY_STICKIES = "my_stickies", "My Stickies"
+ PROJECT_LATEST_ISSUES = "project_latest_issues", "Project Latest Issues"
NEW_AT_PLANE = "new_at_plane", "New at Plane"
QUICK_TUTORIAL = "quick_tutorial", "Quick Tutorial"
diff --git a/plane-src/apps/web/core/components/home/home-dashboard-widgets.tsx b/plane-src/apps/web/core/components/home/home-dashboard-widgets.tsx
index bb06ebe..02cc74b 100644
--- a/plane-src/apps/web/core/components/home/home-dashboard-widgets.tsx
+++ b/plane-src/apps/web/core/components/home/home-dashboard-widgets.tsx
@@ -57,6 +57,11 @@ export const HOME_WIDGETS_LIST: {
fullWidth: false,
title: "stickies.title",
},
+ project_latest_issues: {
+ component: null,
+ fullWidth: true,
+ title: "Последние задачи проекта",
+ },
new_at_plane: {
component: null,
fullWidth: false,
@@ -164,6 +169,7 @@ export const DashboardWidgets = observer(function DashboardWidgets(props: Dashbo
const isRecentsEnabled = !!widgetsMap.recents?.is_enabled;
const isQuickLinksEnabled = !!widgetsMap.quick_links?.is_enabled;
const isStickiesEnabled = !!widgetsMap.my_stickies?.is_enabled;
+ const isProjectLatestIssuesEnabled = widgetsMap.project_latest_issues?.is_enabled ?? true;
const hasSecondaryWidgets = isQuickLinksEnabled || isStickiesEnabled;
if (!workspaceSlugValue) return null;
@@ -199,6 +205,14 @@ export const DashboardWidgets = observer(function DashboardWidgets(props: Dashbo
handleOnClose={() => toggleWidgetSettings(false)}
/>
+
+
-
-
+ {isProjectLatestIssuesEnabled && (
+
+ )}
{!isWikiApp &&
}
diff --git a/plane-src/apps/web/core/components/home/widgets/manage/widget-item.tsx b/plane-src/apps/web/core/components/home/widgets/manage/widget-item.tsx
index f254e8b..ca92f41 100644
--- a/plane-src/apps/web/core/components/home/widgets/manage/widget-item.tsx
+++ b/plane-src/apps/web/core/components/home/widgets/manage/widget-item.tsx
@@ -31,6 +31,16 @@ import { HOME_WIDGETS_LIST } from "../../home-dashboard-widgets";
import { WidgetItemDragHandle } from "./widget-item-drag-handle";
import { getCanDrop, getInstructionFromPayload } from "./widget.helpers";
+const WIDGET_TITLE_FALLBACKS: Record
= {
+ "home.project_latest_issues.title": "Последние задачи проекта",
+ my_stickies: "Ваши стикеры",
+ new_at_plane: "Новое в NODE.DC",
+ project_latest_issues: "Последние задачи проекта",
+ quick_links: "Быстрые ссылки",
+ quick_tutorial: "Быстрое обучение",
+ recents: "Недавние",
+};
+
type Props = {
widgetId: string;
isLastChild: boolean;
@@ -53,6 +63,11 @@ export const WidgetItem = observer(function WidgetItem(props: Props) {
// derived values
const widget = widgetsMap[widgetId];
const widgetTitle = HOME_WIDGETS_LIST[widget.key]?.title;
+ const translatedWidgetTitle = widgetTitle ? t(widgetTitle, { count: 1 }) : undefined;
+ const widgetLabel =
+ !translatedWidgetTitle || translatedWidgetTitle === widgetTitle
+ ? (WIDGET_TITLE_FALLBACKS[widgetTitle ?? ""] ?? WIDGET_TITLE_FALLBACKS[widget.key] ?? widget.key)
+ : translatedWidgetTitle;
// drag and drop
useEffect(() => {
@@ -76,7 +91,7 @@ export const WidgetItem = observer(function WidgetItem(props: Props) {
getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }),
render: ({ container }) => {
const root = createRoot(container);
- root.render({widget.key}
);
+ root.render({widgetLabel}
);
return () => root.unmount();
},
nativeSetDragImage,
@@ -118,7 +133,7 @@ export const WidgetItem = observer(function WidgetItem(props: Props) {
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [elementRef?.current, isDragging, isLastChild, widget.key]);
+ }, [elementRef?.current, isDragging, isLastChild, widget.key, widgetLabel]);
return (
@@ -134,7 +149,7 @@ export const WidgetItem = observer(function WidgetItem(props: Props) {
>
-
{t(widgetTitle, { count: 1 })}
+
{widgetLabel}
.nodedc-glass-modal {
- max-width: min(1840px, calc(100vw - 5rem));
- margin-inline: auto;
- border: 0 !important;
- background: transparent !important;
- padding-inline: 0 !important;
- box-shadow: none !important;
- -webkit-backdrop-filter: none !important;
- backdrop-filter: none !important;
- }
-
- .nodedc-home-top-toolbar {
- padding-inline: 0 !important;
- }
-
.nodedc-home-dashboard-shell {
gap: 0.75rem;
}
@@ -1833,8 +1818,8 @@
}
.nodedc-home-project-panel {
- margin-top: 1.75rem;
- height: calc(100% - 1.75rem) !important;
+ margin-top: 0;
+ height: 100% !important;
min-height: 0;
}
@@ -1879,7 +1864,7 @@
display: grid;
min-width: 0;
position: relative;
- min-height: 8.55rem;
+ min-height: 11.1rem;
}
@media (min-width: 1280px) {
@@ -1894,13 +1879,13 @@
inset: 0;
z-index: 1;
display: flex;
- min-height: 8.55rem;
+ min-height: 11.1rem;
flex-direction: column;
justify-content: center;
border-radius: 1.7rem;
background: #474747 !important;
- padding: 1.25rem;
- padding-right: max(1.25rem, calc(100% - var(--nodedc-home-title-width)));
+ padding: 1.6rem 1.35rem;
+ padding-right: max(1.35rem, calc(100% - var(--nodedc-home-title-width)));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035) !important;
}
@@ -1963,12 +1948,12 @@
display: flex;
width: auto;
min-width: 0;
- min-height: 8.55rem;
+ min-height: 11.1rem;
align-items: flex-end;
gap: 1rem;
border-radius: 1.7rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
- padding: 1rem;
+ padding: 1.35rem 1.15rem;
color: rgb(var(--nodedc-on-card-active-rgb));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.42),
diff --git a/plane-src/packages/i18n/src/locales/en/translations.ts b/plane-src/packages/i18n/src/locales/en/translations.ts
index 00bb8c8..eb15534 100644
--- a/plane-src/packages/i18n/src/locales/en/translations.ts
+++ b/plane-src/packages/i18n/src/locales/en/translations.ts
@@ -643,6 +643,9 @@ export default {
new_at_plane: {
title: "New at NODE.DC",
},
+ project_latest_issues: {
+ title: "Latest project tasks",
+ },
quick_tutorial: {
title: "Quick tutorial",
},
diff --git a/plane-src/packages/i18n/src/locales/ru/translations.ts b/plane-src/packages/i18n/src/locales/ru/translations.ts
index 7234877..3cccdf2 100644
--- a/plane-src/packages/i18n/src/locales/ru/translations.ts
+++ b/plane-src/packages/i18n/src/locales/ru/translations.ts
@@ -799,6 +799,9 @@ export default {
new_at_plane: {
title: "Новое в NODE.DC",
},
+ project_latest_issues: {
+ title: "Последние задачи проекта",
+ },
quick_tutorial: {
title: "Быстрое обучение",
},
diff --git a/plane-src/packages/propel/src/toast/toast.tsx b/plane-src/packages/propel/src/toast/toast.tsx
index c4c8fad..f8aaafd 100644
--- a/plane-src/packages/propel/src/toast/toast.tsx
+++ b/plane-src/packages/propel/src/toast/toast.tsx
@@ -7,7 +7,6 @@
import * as React from "react";
import { Toast as BaseToast } from "@base-ui-components/react/toast";
import { AlertTriangle, CheckIcon, InfoIcon, XIcon } from "lucide-react";
-import { CloseIcon } from "../icons/actions/close-icon";
// spinner
import { CircularBarSpinner } from "../spinners/circular-bar-spinner";
import { cn } from "../utils/classname";
@@ -54,6 +53,14 @@ export type ToastProps = {
};
const toastManager = BaseToast.createToastManager();
+const DEFAULT_LOADING_TITLE = "Загрузка...";
+
+const TOAST_SURFACE_CLASSNAME =
+ "!border-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.07)_0%,rgba(255,255,255,0.025)_100%),rgba(55,55,56,0.78)] text-white shadow-[0_26px_64px_rgba(0,0,0,0.36),inset_0_1px_0_rgba(255,255,255,0.09)] !outline-none backdrop-blur-[34px]";
+const TOAST_CLOSE_CLASSNAME =
+ "absolute top-1/2 left-4 grid size-12 -translate-y-1/2 cursor-pointer place-items-center rounded-full !border-0 bg-black/[0.68] p-0 text-white/[0.72] shadow-[inset_0_1px_0_rgba(255,255,255,0.06)] !outline-none transition-colors hover:bg-black/[0.8] hover:text-white focus:outline-none focus-visible:bg-black/[0.84] focus-visible:text-white";
+const TOAST_STATUS_CLASSNAME =
+ "absolute top-1/2 right-5 grid size-12 -translate-y-1/2 place-items-center rounded-full bg-black/[0.24] text-white/[0.7] shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]";
export function Toast(props: ToastProps) {
return (
@@ -69,40 +76,28 @@ export function Toast(props: ToastProps) {
const TOAST_DATA = {
[TOAST_TYPE.SUCCESS]: {
- icon: ,
- iconBgClassName: "bg-success-primary",
- backgroundColorClassName: "!bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-[rgb(var(--nodedc-card-active-rgb,195_255_102))]",
},
[TOAST_TYPE.ERROR]: {
- icon: ,
- iconBgClassName: "bg-danger-primary",
- backgroundColorClassName: "bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-[#ff4452]",
},
[TOAST_TYPE.WARNING]: {
- icon: ,
- iconBgClassName: "bg-warning-primary",
- backgroundColorClassName: "bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-[#ff8830]",
},
[TOAST_TYPE.INFO]: {
- icon: ,
- iconBgClassName: "bg-accent-primary",
- backgroundColorClassName: "bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-[rgb(var(--nodedc-accent-rgb,51_163_255))]",
},
[TOAST_TYPE.LOADING]: {
- icon: ,
- iconBgClassName: "bg-layer-2",
- backgroundColorClassName: "bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-white",
},
[TOAST_TYPE.LOADING_TOAST]: {
- icon: ,
- iconBgClassName: "bg-layer-2",
- backgroundColorClassName: "bg-surface-1",
- borderColorClassName: "border-subtle",
+ icon: ,
+ iconClassName: "text-white",
},
};
@@ -122,7 +117,7 @@ function ToastRender({ id, toast }: { id: React.Key; toast: BaseToast.Root.Toast
key={id}
className={cn(
// Base layout and positioning
- "group flex w-[350px] items-center rounded-lg border shadow-raised-200",
+ "group flex min-h-[6.4rem] w-[min(430px,calc(100vw-2rem))] items-center rounded-[1.85rem]",
"absolute right-3 bottom-3 z-[calc(1000-var(--toast-index))]",
"ease-[cubic-bezier(0.22,1,0.36,1)] transition-[opacity,transform] duration-500 select-none",
@@ -150,8 +145,7 @@ function ToastRender({ id, toast }: { id: React.Key; toast: BaseToast.Root.Toast
// Default ending transform for non-limited toasts
"data-[ending-style]:[&:not([data-limited])]:[transform:translateY(150%)]",
- data.backgroundColorClassName,
- data.borderColorClassName
+ TOAST_SURFACE_CLASSNAME
)}
style={{
["--gap" as string]: "1rem",
@@ -163,30 +157,24 @@ function ToastRender({ id, toast }: { id: React.Key; toast: BaseToast.Root.Toast
e.preventDefault();
}}
>
-
-
+
+
-
-
- {data.icon && (
-
- {data.icon}
-
- )}
-
-
-
- {toastData.type === TOAST_TYPE.LOADING ? (toastData.title ?? "Loading...") : toastData.title}
+ {data.icon && {data.icon}
}
+
+
+
+ {toastData.type === TOAST_TYPE.LOADING ? (toastData.title ?? DEFAULT_LOADING_TITLE) : toastData.title}
{toastData.type !== TOAST_TYPE.LOADING && toastData.message && (
-
+
{toastData.message}
)}
{toastData.type !== TOAST_TYPE.LOADING && toastData.actionItems && (
- {toastData.actionItems}
+
+ {toastData.actionItems}
+
)}
@@ -211,36 +199,28 @@ export function ToastStatic({ type, title, message, actionItems, theme = "light"
-
-
+
+
-
-
- {data.icon && (
-
- {data.icon}
-
- )}
-
-
-
- {type === TOAST_TYPE.LOADING ? (title ?? "Loading...") : title}
+ {data.icon &&
{data.icon}
}
+
+
+
+ {type === TOAST_TYPE.LOADING ? (title ?? DEFAULT_LOADING_TITLE) : title}
{type !== TOAST_TYPE.LOADING && message && (
-
{message}
+
{message}
+ )}
+ {type !== TOAST_TYPE.LOADING && actionItems && (
+
+ {actionItems}
+
)}
- {type !== TOAST_TYPE.LOADING && actionItems &&
{actionItems}
}
@@ -294,7 +274,7 @@ export const setPromiseToast =
(
toastManager.promise(promise, {
loading: {
data: {
- title: options.loading ?? "Loading...",
+ title: options.loading ?? DEFAULT_LOADING_TITLE,
type: TOAST_TYPE.LOADING,
message: undefined,
actionItems: undefined,
diff --git a/plane-src/packages/types/src/home.ts b/plane-src/packages/types/src/home.ts
index 16c8def..969738a 100644
--- a/plane-src/packages/types/src/home.ts
+++ b/plane-src/packages/types/src/home.ts
@@ -8,7 +8,13 @@ import type { TLogoProps } from "./common";
import type { TIssuePriorities } from "./issues";
export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project" | "workspace_page";
-export type THomeWidgetKeys = "quick_links" | "recents" | "my_stickies" | "quick_tutorial" | "new_at_plane";
+export type THomeWidgetKeys =
+ | "quick_links"
+ | "recents"
+ | "my_stickies"
+ | "project_latest_issues"
+ | "quick_tutorial"
+ | "new_at_plane";
export type THomeWidgetProps = {
workspaceSlug: string;