Skip to content

feat: add dashboard core - #1797

Open
Pieselak wants to merge 3 commits into
stagingfrom
pz_feat_1714_add_dashboard
Open

feat: add dashboard core#1797
Pieselak wants to merge 3 commits into
stagingfrom
pz_feat_1714_add_dashboard

Conversation

@Pieselak

@Pieselak Pieselak commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Issue(s)

Overview

Adds a customizable dashboard with role-based widgets. Users can reorder entire widget cards, resize supported widgets, manage visibility, restore defaults, and save their layout.

Business Value

Provides users with a personalized dashboard focused on relevant information and activities. The modular widget structure also makes it easier to add dedicated business widgets without changing the dashboard layout system.

Screenshots / Video

image image image

@Pieselak Pieselak added the review me 👀 PR is ready to be reviewed label Jul 21, 2026
@Pieselak
Pieselak changed the base branch from main to staging July 21, 2026 10:34
@Pieselak
Pieselak force-pushed the pz_feat_1714_add_dashboard branch from a6d2848 to 9a820c3 Compare July 21, 2026 11:45
return userSettings;
const widgetIds = userSettings.dashboard.widgets.map((widget) => widget.id);
const validIds = await this.filterDashboardWidgets(userId, widgetIds);
const returnedWidgets = userSettings.dashboard.widgets.filter((widget) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what if there is no dashboard in users settings jsonb yet?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Created a migration to ensure consistent data in db and resolve potentional issues with lack of dashboard property in user settings

Comment on lines +706 to +708
const { roleSlugs } = await this.permissionsService.getUserAccess(userId);
const userRoles = new Set(roleSlugs);
const globalSettings = await this.getPublicGlobalSettings();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@its-gabo shouldn't the dbInstance be passed down to those awaited methods?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There’s no need to do that, unless it’s part of a transaction and we specifically need the state of the data within given transaction.

If the concern was that it wouldn’t work because of RLS, that shouldn’t be an issue. The call happens within the request context, so the data remains properly scoped through AsyncLocalStorage.

return new BaseResponse(await this.settingsService.updateUserSettings(userId, updatedSettings));
}

@Get("dashboard")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shouldn't be guarded with @RequirePermission(PERMISSIONS.DASHBOARD_READ)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added missing Permission decorator to dashboard endpoints

widgets: [
{
id: DASHBOARD_WIDGET_IDS.ADMIN_PLACEHOLDER1,
enabled: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this flag used anywhere but specs?

@Pieselak
Pieselak force-pushed the pz_feat_1714_add_dashboard branch from 239cd70 to 306e711 Compare July 22, 2026 08:26
@Pieselak
Pieselak requested a review from its-gabo July 22, 2026 08:56
@Pieselak

Copy link
Copy Markdown
Collaborator Author

@its-gabo please review

@its-gabo its-gabo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Pretty good in overall, just some few minor things to adjust

Comment on lines +10 to +23
queryFn: async () => {
try {
const response = await ApiClient.api.settingsControllerGetAvailableDashboardWidgets();

return response.data;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 401) {
return null;
}
throw error;
}
},
staleTime: 1000 * 60 * 5,
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the try .. catch needed here? Can we just not query when there's no session for a user?

Comment on lines +39 to +41
if (!data) {
throw new Error("User not authenticated");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Translate the error please

Comment on lines +10 to +23
queryFn: async () => {
try {
const response = await ApiClient.api.settingsControllerGetDefaultDashboardWidgets();

return response.data;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 401) {
return null;
}
throw error;
}
},
staleTime: 1000 * 60 * 5,
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as in file above

Comment on lines +39 to +41
if (!data) {
throw new Error("User not authenticated");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as in file above


const OVERLAY_MODIFIERS = [restrictOverlayToViewport];

function SortableWidget({ widget, isEditing, onWidthChange }: SortableWidgetProps) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's extract this component to a separate file, alongside the types

@@ -1,5 +1,5 @@
/* AUTO-GENERATED FILE - DO NOT EDIT BY HAND */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's unstage this file

@Pieselak
Pieselak requested a review from its-gabo July 22, 2026 11:11
@Pieselak Pieselak self-assigned this Jul 23, 2026
Comment on lines +13 to +16
const titleKey = "dashboardHome.widgets.a_placeholder_3.title";
const descriptionKey = "dashboardHome.widgets.placeholderDescription";
const iconClassName = "text-warning-700";
const iconContainerClassName = "bg-warning-50";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a reason for having them not in the html? (applies to all placeholder widget files)

Comment on lines +38 to +45
export const adminPlaceholder3Widget = {
component: WidgetAdminPlaceholder3,
titleKey,
descriptionKey,
icon: ListChecks,
iconClassName,
iconContainerClassName,
} satisfies DashboardWidgetModule;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we move all of them to one file and then import these configs and use them in components?

So then the consts from the top of the file can be deleted and we can use it like adminPlaceholder3Widget.icon to display icon for example

(applies to all)

@its-gabo its-gabo added the run E2E tests ⚙️ Trigger E2E tests run on this branch. label Jul 23, 2026
@Pieselak Pieselak added run E2E tests ⚙️ Trigger E2E tests run on this branch. and removed run E2E tests ⚙️ Trigger E2E tests run on this branch. labels Jul 23, 2026
@Pieselak Pieselak added run E2E tests ⚙️ Trigger E2E tests run on this branch. and removed run E2E tests ⚙️ Trigger E2E tests run on this branch. labels Jul 23, 2026

@its-gabo its-gabo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks a bit overcomplicated, is there a way to eliminate some of the refs perhaps?

@Pieselak
Pieselak requested a review from its-gabo July 29, 2026 08:31
...updatedSettings,
dashboard: {
widgets: [...submittedWidgets]
.sort((first, second) => first.order - second.order)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorting feels kind of redundant since FE already sorts by order

import type { UpdateUserSettingsBody, UpdateUserSettingsResponse } from "../generated-api";
import type { UseMutationResult } from "@tanstack/react-query";

export function useUpdateDashboardWidgets(): UseMutationResult<

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need to type this explicitly?

height: number;
};

type DropPlacement = "before" | "after";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since we use these values several times here, might as well extract it to an enum and derive the union from it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So much is happening here, let's extract reusable utilities to separate file, merge refs into one ref with object.

const displayedWidgets = previewWidgets ?? sortedWidgets;
const activeEntry = activeId ? DASHBOARD_WIDGET_REGISTRY[activeId] : null;
const ActiveIcon = activeEntry?.icon;
const dashboardCollisionDetection: CollisionDetection = (args) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The logic is very abstract, can we include some jsdoc to explain what is happening in each function?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Either rename extension to .ts or change name to WidgetRegistry.tsx

@Japrolol
Japrolol force-pushed the pz_feat_1714_add_dashboard branch from 44a1e85 to 095ed57 Compare August 5, 2026 10:08
@Japrolol
Japrolol force-pushed the pz_feat_1714_add_dashboard branch from 095ed57 to dd67618 Compare August 7, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review me 👀 PR is ready to be reviewed run E2E tests ⚙️ Trigger E2E tests run on this branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants