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;