fix: manage widgets integrations (#6331)

* wip

* chore: wip

* fix: preserved old component

* fix

* fix: seperate route added

* fix

* Only return user ID of project members

* Return issue ID

* fix: recents api integrations

* fix: types

* fix: types

* fix: added tooltips

* chore: added apis

* fix: widgets fix

* fix: lint

* fix: integrated dashboard apis

* fix: added ee dummy component for header

* fix: removed logs

---------

Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
This commit is contained in:
Akshita Goyal 2025-01-07 12:57:35 +05:30 committed by GitHub
parent edb68a1bc6
commit 0af9b09844
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 110 additions and 74 deletions

View file

@ -2,7 +2,7 @@ import { TLogoProps } from "./common";
import { TIssuePriorities } from "./issues"; import { TIssuePriorities } from "./issues";
export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project"; export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project";
export type THomeWidgetKeys = "quick_links" | "recent_activity" | "stickies"; export type THomeWidgetKeys = "quick_links" | "recents" | "my_stickies" | "quick_tutorial" | "new_at_plane";
export type THomeWidgetProps = { export type THomeWidgetProps = {
workspaceSlug: string; workspaceSlug: string;
@ -69,7 +69,7 @@ export type TLinkIdMap = {
}; };
export type TWidgetEntityData = { export type TWidgetEntityData = {
key: string; key: THomeWidgetKeys;
name: string; name: string;
is_enabled: boolean; is_enabled: boolean;
sort_order: number; sort_order: number;

View file

@ -12,18 +12,20 @@ import { DashboardQuickLinks } from "./widgets/links";
import { ManageWidgetsModal } from "./widgets/manage"; import { ManageWidgetsModal } from "./widgets/manage";
const WIDGETS_LIST: { const WIDGETS_LIST: {
[key in THomeWidgetKeys]: { component: React.FC<THomeWidgetProps>; fullWidth: boolean }; [key in THomeWidgetKeys]: { component: React.FC<THomeWidgetProps> | null; fullWidth: boolean };
} = { } = {
quick_links: { component: DashboardQuickLinks, fullWidth: false }, quick_links: { component: DashboardQuickLinks, fullWidth: false },
recent_activity: { component: RecentActivityWidget, fullWidth: false }, recents: { component: RecentActivityWidget, fullWidth: false },
stickies: { component: StickiesWidget, fullWidth: false }, my_stickies: { component: StickiesWidget, fullWidth: false },
new_at_plane: { component: null, fullWidth: false },
quick_tutorial: { component: null, fullWidth: false },
}; };
export const DashboardWidgets = observer(() => { export const DashboardWidgets = observer(() => {
// router // router
const { workspaceSlug } = useParams(); const { workspaceSlug } = useParams();
// store hooks // store hooks
const { toggleWidgetSettings, showWidgetSettings } = useHome(); const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets } = useHome();
if (!workspaceSlug) return null; if (!workspaceSlug) return null;
@ -36,9 +38,11 @@ export const DashboardWidgets = observer(() => {
handleOnClose={() => toggleWidgetSettings(false)} handleOnClose={() => toggleWidgetSettings(false)}
/> />
{Object.entries(WIDGETS_LIST).map(([key, widget]) => { {orderedWidgets.map((key) => {
const WidgetComponent = widget.component; const WidgetComponent = WIDGETS_LIST[key]?.component;
if (widget.fullWidth) const isEnabled = widgetsMap[key]?.is_enabled;
if (!WidgetComponent || !isEnabled) return null;
if (WIDGETS_LIST[key]?.fullWidth)
return ( return (
<div key={key} className="lg:col-span-2"> <div key={key} className="lg:col-span-2">
<WidgetComponent workspaceSlug={workspaceSlug.toString()} /> <WidgetComponent workspaceSlug={workspaceSlug.toString()} />

View file

@ -1,7 +1,7 @@
import { useEffect } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
// components // components
import useSWR from "swr";
import { ContentWrapper } from "@plane/ui"; import { ContentWrapper } from "@plane/ui";
import { EmptyState } from "@/components/empty-state"; import { EmptyState } from "@/components/empty-state";
import { TourRoot } from "@/components/onboarding"; import { TourRoot } from "@/components/onboarding";
@ -11,7 +11,7 @@ import { PRODUCT_TOUR_COMPLETED } from "@/constants/event-tracker";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
// hooks // hooks
import { useCommandPalette, useUserProfile, useEventTracker, useDashboard, useProject, useUser } from "@/hooks/store"; import { useCommandPalette, useUserProfile, useEventTracker, useProject, useUser } from "@/hooks/store";
import { useHome } from "@/hooks/store/use-home"; import { useHome } from "@/hooks/store/use-home";
import useSize from "@/hooks/use-window-size"; import useSize from "@/hooks/use-window-size";
import { IssuePeekOverview } from "../issues"; import { IssuePeekOverview } from "../issues";
@ -29,12 +29,20 @@ export const WorkspaceHomeView = observer(() => {
const { data: currentUser } = useUser(); const { data: currentUser } = useUser();
const { data: currentUserProfile, updateTourCompleted } = useUserProfile(); const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
const { captureEvent } = useEventTracker(); const { captureEvent } = useEventTracker();
const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard(); const { toggleWidgetSettings, fetchWidgets } = useHome();
const { toggleWidgetSettings } = useHome();
const { joinedProjectIds, loader } = useProject(); const { joinedProjectIds, loader } = useProject();
const [windowWidth] = useSize(); const [windowWidth] = useSize();
useSWR(
workspaceSlug ? `HOME_DASHBOARD_WIDGETS_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWidgets(workspaceSlug?.toString()) : null,
{
revalidateIfStale: true,
revalidateOnFocus: false,
revalidateOnReconnect: true,
}
);
const handleTourCompleted = () => { const handleTourCompleted = () => {
updateTourCompleted() updateTourCompleted()
.then(() => { .then(() => {
@ -48,13 +56,6 @@ export const WorkspaceHomeView = observer(() => {
}); });
}; };
// fetch home dashboard widgets on workspace change
useEffect(() => {
if (!workspaceSlug) return;
fetchHomeDashboardWidgets(workspaceSlug?.toString());
}, [fetchHomeDashboardWidgets, workspaceSlug]);
// TODO: refactor loader implementation // TODO: refactor loader implementation
return ( return (
<> <>
@ -63,7 +64,7 @@ export const WorkspaceHomeView = observer(() => {
<TourRoot onComplete={handleTourCompleted} /> <TourRoot onComplete={handleTourCompleted} />
</div> </div>
)} )}
{homeDashboardId && joinedProjectIds && ( {joinedProjectIds && (
<> <>
{joinedProjectIds.length > 0 || loader ? ( {joinedProjectIds.length > 0 || loader ? (
<> <>

View file

@ -51,13 +51,13 @@ export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
</div> </div>
</h6> </h6>
</div> </div>
{/* <button <button
onClick={handleWidgetModal} onClick={handleWidgetModal}
className="flex items-center gap-2 font-medium text-custom-text-300 justify-center border border-custom-border-200 rounded p-2 my-auto mb-0" className="flex items-center gap-2 font-medium text-custom-text-300 justify-center border border-custom-border-200 rounded p-2 my-auto mb-0"
> >
<Shapes size={16} /> <Shapes size={16} />
<div className="text-xs font-medium">Manage widgets</div> <div className="text-xs font-medium">Manage widgets</div>
</button> */} </button>
</div> </div>
); );
}; };

View file

@ -2,10 +2,10 @@ import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// computed // computed
import { useHome } from "@/hooks/store/use-home"; import { useHome } from "@/hooks/store/use-home";
import { EWidgetKeys, WidgetLoader } from "../loaders";
import { AddLink } from "./action"; import { AddLink } from "./action";
import { ProjectLinkDetail } from "./link-detail"; import { ProjectLinkDetail } from "./link-detail";
import { TLinkOperations } from "./use-links"; import { TLinkOperations } from "./use-links";
import { EWidgetKeys, WidgetLoader } from "../loaders";
export type TLinkOperationsModal = Exclude<TLinkOperations, "create">; export type TLinkOperationsModal = Exclude<TLinkOperations, "create">;

View file

@ -1,10 +1,10 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { THomeWidgetProps } from "@plane/types";
import { useHome } from "@/hooks/store/use-home"; import { useHome } from "@/hooks/store/use-home";
import { LinkCreateUpdateModal } from "./create-update-link-modal"; import { LinkCreateUpdateModal } from "./create-update-link-modal";
import { ProjectLinkList } from "./links"; import { ProjectLinkList } from "./links";
import { useLinks } from "./use-links"; import { useLinks } from "./use-links";
import { THomeWidgetProps } from "@plane/types";
export const DashboardQuickLinks = observer((props: THomeWidgetProps) => { export const DashboardQuickLinks = observer((props: THomeWidgetProps) => {
const { workspaceSlug } = props; const { workspaceSlug } = props;

View file

@ -1,6 +1,6 @@
// components // components
import { RecentActivityWidgetLoader } from "./recent-activity";
import { QuickLinksWidgetLoader } from "./quick-links"; import { QuickLinksWidgetLoader } from "./quick-links";
import { RecentActivityWidgetLoader } from "./recent-activity";
// types // types

View file

@ -14,38 +14,45 @@ import { attachInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// plane helpers // plane helpers
import { useParams } from "next/navigation";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
// ui // ui
import { InstructionType } from "@plane/types"; import { InstructionType, TWidgetEntityData } from "@plane/types";
// components // components
import { DropIndicator, ToggleSwitch } from "@plane/ui"; import { DropIndicator, ToggleSwitch } from "@plane/ui";
// helpers // helpers
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
import { useHome } from "@/hooks/store/use-home";
import { WidgetItemDragHandle } from "./widget-item-drag-handle"; import { WidgetItemDragHandle } from "./widget-item-drag-handle";
import { getCanDrop, getInstructionFromPayload } from "./widget.helpers"; import { getCanDrop, getInstructionFromPayload } from "./widget.helpers";
type Props = { type Props = {
widgetId: string;
isLastChild: boolean; isLastChild: boolean;
widget: any;
handleDrop: (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => void; handleDrop: (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => void;
handleToggle: (workspaceSlug: string, widgetKey: string, is_enabled: boolean) => void;
}; };
export const WidgetItem: FC<Props> = observer((props) => { export const WidgetItem: FC<Props> = observer((props) => {
// props // props
const { isLastChild, widget, handleDrop } = props; const { widgetId, isLastChild, handleDrop, handleToggle } = props;
const { workspaceSlug } = useParams();
//state //state
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined); const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
//ref //ref
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);
// hooks
const { widgetsMap } = useHome();
// derived values
const widget = widgetsMap[widgetId] as TWidgetEntityData;
// drag and drop // drag and drop
useEffect(() => { useEffect(() => {
const element = elementRef.current; const element = elementRef.current;
if (!element) return; if (!element) return;
const initialData = { id: widget.id, isGroup: false }; const initialData = { id: widget.key, isGroup: false };
return combine( return combine(
draggable({ draggable({
element, element,
@ -62,7 +69,7 @@ export const WidgetItem: FC<Props> = observer((props) => {
getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }), getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }),
render: ({ container }) => { render: ({ container }) => {
const root = createRoot(container); const root = createRoot(container);
root.render(<div className="rounded bg-custom-background-100 text-sm p-1 pr-2">{widget.title}</div>); root.render(<div className="rounded bg-custom-background-100 text-sm p-1 pr-2">{widget.key}</div>);
return () => root.unmount(); return () => root.unmount();
}, },
nativeSetDragImage, nativeSetDragImage,
@ -104,7 +111,7 @@ export const WidgetItem: FC<Props> = observer((props) => {
}) })
); );
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef?.current, isDragging, isLastChild, widget.id]); }, [elementRef?.current, isDragging, isLastChild, widget.key]);
return ( return (
<div className=""> <div className="">
@ -120,9 +127,12 @@ export const WidgetItem: FC<Props> = observer((props) => {
> >
<div className="flex items-center"> <div className="flex items-center">
<WidgetItemDragHandle sort_order={widget.sort_order} isDragging={isDragging} /> <WidgetItemDragHandle sort_order={widget.sort_order} isDragging={isDragging} />
<div>{widget.title}</div> <div>{widget.key.replaceAll("_", " ")}</div>
</div> </div>
{/* <ToggleSwitch /> */} <ToggleSwitch
value={widget.is_enabled}
onChange={() => handleToggle(workspaceSlug.toString(), widget.key, !widget.is_enabled)}
/>
</div> </div>
{isLastChild && <DropIndicator isVisible={instruction === "reorder-below"} />} {isLastChild && <DropIndicator isVisible={instruction === "reorder-below"} />}
</div> </div>

View file

@ -3,17 +3,14 @@ import {
DropTargetRecord, DropTargetRecord,
ElementDragPayload, ElementDragPayload,
} from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types"; } from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types";
import { observer } from "mobx-react";
import { setToast, TOAST_TYPE } from "@plane/ui";
import { useHome } from "@/hooks/store/use-home"; import { useHome } from "@/hooks/store/use-home";
import { WidgetItem } from "./widget-item"; import { WidgetItem } from "./widget-item";
import { getInstructionFromPayload, TargetData } from "./widget.helpers"; import { getInstructionFromPayload, TargetData } from "./widget.helpers";
const WIDGETS_LIST = [ export const WidgetList = observer(({ workspaceSlug }: { workspaceSlug: string }) => {
{ id: 1, title: "quick links" }, const { orderedWidgets, reorderWidget, toggleWidget } = useHome();
{ id: 2, title: "recents" },
{ id: 3, title: "stickies" },
];
export const WidgetList = ({ workspaceSlug }: { workspaceSlug: string }) => {
const { reorderWidget } = useHome();
const handleDrop = (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => { const handleDrop = (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => {
const dropTargets = location?.current?.dropTargets ?? []; const dropTargets = location?.current?.dropTargets ?? [];
@ -30,20 +27,34 @@ export const WidgetList = ({ workspaceSlug }: { workspaceSlug: string }) => {
if (!sourceData.id) return; if (!sourceData.id) return;
if (droppedId) { if (droppedId) {
reorderWidget(workspaceSlug, sourceData.id, droppedId, instruction); /** sequence */ try {
reorderWidget(workspaceSlug, sourceData.id, droppedId, instruction); /** sequence */
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Widget reordered successfully.",
});
} catch {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error occurred while reordering widget.",
});
}
} }
}; };
return ( return (
<div className="my-4"> <div className="my-4">
{WIDGETS_LIST.map((widget, index) => ( {orderedWidgets.map((widget, index) => (
<WidgetItem <WidgetItem
key={widget.id} key={widget}
widget={widget} widgetId={widget}
isLastChild={index === WIDGETS_LIST.length - 1} isLastChild={index === orderedWidgets.length - 1}
handleDrop={handleDrop} handleDrop={handleDrop}
handleToggle={toggleWidget}
/> />
))} ))}
</div> </div>
); );
}; });

View file

@ -1,5 +1,5 @@
import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
import { IFavorite, InstructionType, IPragmaticPayloadLocation, TDropTarget } from "@plane/types"; import { InstructionType, IPragmaticPayloadLocation, TDropTarget, TWidgetEntityData } from "@plane/types";
export type TargetData = { export type TargetData = {
id: string; id: string;
@ -11,7 +11,7 @@ export type TargetData = {
/** /**
* extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload * extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload
* @param dropTarget dropTarget for which the instruction is required * @param dropTarget dropTarget for which the instruction is required
* @param source the dragging favorite data that is being dragged on the dropTarget * @param source the dragging widget data that is being dragged on the dropTarget
* @param location location includes the data of all the dropTargets the source is being dragged on * @param location location includes the data of all the dropTargets the source is being dragged on
* @returns Instruction for dropTarget * @returns Instruction for dropTarget
*/ */
@ -37,7 +37,7 @@ export const getInstructionFromPayload = (
instruction = dropTargetData.isChild ? "reorder-above" : "make-child"; instruction = dropTargetData.isChild ? "reorder-above" : "make-child";
} }
// if source that is being dragged is a group. A group cannon be a child of any other favorite, // if source that is being dragged is a group. A group cannon be a child of any other widget,
// hence if current instruction is to be a child of dropTarget then reorder-above instead // hence if current instruction is to be a child of dropTarget then reorder-above instead
if (instruction === "make-child" && sourceData.isGroup) instruction = "reorder-above"; if (instruction === "make-child" && sourceData.isGroup) instruction = "reorder-above";
@ -45,18 +45,18 @@ export const getInstructionFromPayload = (
}; };
/** /**
* This provides a boolean to indicate if the favorite can be dropped onto the droptarget * This provides a boolean to indicate if the widget can be dropped onto the droptarget
* @param source * @param source
* @param favorite * @param widget
* @returns * @returns
*/ */
export const getCanDrop = (source: TDropTarget, favorite: IFavorite | undefined) => { export const getCanDrop = (source: TDropTarget, widget: TWidgetEntityData | undefined) => {
const sourceData = source?.data; const sourceData = source?.data;
if (!sourceData) return false; if (!sourceData) return false;
// a favorite cannot be dropped on to itself // a widget cannot be dropped on to itself
if (sourceData.id === favorite?.id) return false; if (sourceData.id === widget?.key) return false;
return true; return true;
}; };

View file

@ -3,9 +3,9 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { ChevronDown } from "lucide-react"; import { ChevronDown } from "lucide-react";
import { TRecentActivityFilterKeys } from "@plane/types";
import { CustomMenu } from "@plane/ui"; import { CustomMenu } from "@plane/ui";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
import { TRecentActivityFilterKeys } from "@plane/types";
export type TFiltersDropdown = { export type TFiltersDropdown = {
className?: string; className?: string;

View file

@ -3,18 +3,18 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// types // types
import useSWR from "swr";
import { Briefcase, FileText } from "lucide-react";
import { TActivityEntityData, THomeWidgetProps, TRecentActivityFilterKeys } from "@plane/types"; import { TActivityEntityData, THomeWidgetProps, TRecentActivityFilterKeys } from "@plane/types";
// components // components
import { LayersIcon } from "@plane/ui";
import { WorkspaceService } from "@/plane-web/services";
import { EmptyWorkspace } from "../empty-states";
import { EWidgetKeys, WidgetLoader } from "../loaders";
import { FiltersDropdown } from "./filters"; import { FiltersDropdown } from "./filters";
import { RecentIssue } from "./issue"; import { RecentIssue } from "./issue";
import { WorkspaceService } from "@/plane-web/services";
import useSWR from "swr";
import { RecentProject } from "./project";
import { RecentPage } from "./page"; import { RecentPage } from "./page";
import { EWidgetKeys, WidgetLoader } from "../loaders"; import { RecentProject } from "./project";
import { Briefcase, FileText } from "lucide-react";
import { LayersIcon } from "@plane/ui";
import { EmptyWorkspace } from "../empty-states";
const WIDGET_KEY = EWidgetKeys.RECENT_ACTIVITY; const WIDGET_KEY = EWidgetKeys.RECENT_ACTIVITY;
const workspaceService = new WorkspaceService(); const workspaceService = new WorkspaceService();

View file

@ -1,11 +1,11 @@
import { useRouter } from "next/navigation";
import { FileText } from "lucide-react";
import { TActivityEntityData, TPageEntityData } from "@plane/types"; import { TActivityEntityData, TPageEntityData } from "@plane/types";
import { Avatar, Logo } from "@plane/ui"; import { Avatar, Logo } from "@plane/ui";
import { getFileURL } from "@plane/utils";
import { ListItem } from "@/components/core/list"; import { ListItem } from "@/components/core/list";
import { calculateTimeAgo } from "@/helpers/date-time.helper"; import { calculateTimeAgo } from "@/helpers/date-time.helper";
import { FileText } from "lucide-react";
import { getFileURL } from "@plane/utils";
import { useMember } from "@/hooks/store"; import { useMember } from "@/hooks/store";
import { useRouter } from "next/navigation";
type BlockProps = { type BlockProps = {
activity: TActivityEntityData; activity: TActivityEntityData;

View file

@ -1,9 +1,9 @@
import { useRouter } from "next/navigation";
import { TActivityEntityData, TProjectEntityData } from "@plane/types"; import { TActivityEntityData, TProjectEntityData } from "@plane/types";
import { Logo } from "@plane/ui"; import { Logo } from "@plane/ui";
import { ListItem } from "@/components/core/list"; import { ListItem } from "@/components/core/list";
import { calculateTimeAgo } from "@/helpers/date-time.helper";
import { MemberDropdown } from "@/components/dropdowns"; import { MemberDropdown } from "@/components/dropdowns";
import { useRouter } from "next/navigation"; import { calculateTimeAgo } from "@/helpers/date-time.helper";
type BlockProps = { type BlockProps = {
activity: TActivityEntityData; activity: TActivityEntityData;

View file

@ -1,7 +1,7 @@
import orderBy from "lodash/orderBy"; import orderBy from "lodash/orderBy";
import set from "lodash/set"; import set from "lodash/set";
import { action, makeObservable, observable, runInAction } from "mobx"; import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { TWidgetEntityData } from "@plane/types"; import { THomeWidgetKeys, TWidgetEntityData } from "@plane/types";
import { WorkspaceService } from "@/plane-web/services"; import { WorkspaceService } from "@/plane-web/services";
import { IWorkspaceLinkStore, WorkspaceLinkStore } from "./link.store"; import { IWorkspaceLinkStore, WorkspaceLinkStore } from "./link.store";
@ -9,6 +9,9 @@ export interface IHomeStore {
// observables // observables
showWidgetSettings: boolean; showWidgetSettings: boolean;
widgetsMap: Record<string, TWidgetEntityData>; widgetsMap: Record<string, TWidgetEntityData>;
widgets: THomeWidgetKeys[];
// computed
orderedWidgets: THomeWidgetKeys[];
//stores //stores
quickLinks: IWorkspaceLinkStore; quickLinks: IWorkspaceLinkStore;
// actions // actions
@ -22,7 +25,7 @@ export class HomeStore implements IHomeStore {
// observables // observables
showWidgetSettings = false; showWidgetSettings = false;
widgetsMap: Record<string, TWidgetEntityData> = {}; widgetsMap: Record<string, TWidgetEntityData> = {};
widgets: string[] = []; widgets: THomeWidgetKeys[] = [];
// stores // stores
quickLinks: IWorkspaceLinkStore; quickLinks: IWorkspaceLinkStore;
// services // services
@ -34,6 +37,8 @@ export class HomeStore implements IHomeStore {
showWidgetSettings: observable, showWidgetSettings: observable,
widgetsMap: observable, widgetsMap: observable,
widgets: observable, widgets: observable,
// computed
orderedWidgets: computed,
// actions // actions
toggleWidgetSettings: action, toggleWidgetSettings: action,
fetchWidgets: action, fetchWidgets: action,
@ -47,6 +52,10 @@ export class HomeStore implements IHomeStore {
this.quickLinks = new WorkspaceLinkStore(); this.quickLinks = new WorkspaceLinkStore();
} }
get orderedWidgets() {
return orderBy(Object.values(this.widgetsMap), "sort_order", "desc").map((widget) => widget.key);
}
toggleWidgetSettings = (value?: boolean) => { toggleWidgetSettings = (value?: boolean) => {
this.showWidgetSettings = value !== undefined ? value : !this.showWidgetSettings; this.showWidgetSettings = value !== undefined ? value : !this.showWidgetSettings;
}; };
@ -55,7 +64,7 @@ export class HomeStore implements IHomeStore {
try { try {
const widgets = await this.workspaceService.fetchWorkspaceWidgets(workspaceSlug); const widgets = await this.workspaceService.fetchWorkspaceWidgets(workspaceSlug);
runInAction(() => { runInAction(() => {
this.widgets = widgets.map((widget) => widget.key); this.widgets = orderBy(Object.values(widgets), "sort_order", "desc").map((widget) => widget.key);
widgets.forEach((widget) => { widgets.forEach((widget) => {
this.widgetsMap[widget.key] = widget; this.widgetsMap[widget.key] = widget;
}); });

View file

@ -0,0 +1 @@
export const HomePageHeader = () => <></>;