39948-vm/frontend/src/stores/selectors.ts
2026-04-05 18:46:16 +04:00

182 lines
5.1 KiB
TypeScript

/**
* Memoized Redux Selectors
*
* Centralized selectors using createSelector for performance optimization.
* These selectors prevent unnecessary re-renders by memoizing derived state.
*/
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from './store';
// ============================================================================
// Auth Selectors
// ============================================================================
/** Select auth slice */
const selectAuthState = (state: RootState) => state.auth;
/** Select current user with memoization */
export const selectCurrentUser = createSelector(
[selectAuthState],
(auth) => auth.currentUser,
);
/** Select if user is authenticated */
export const selectIsAuthenticated = createSelector(
[selectCurrentUser],
(user) => !!user,
);
/** Select user's role */
export const selectUserRole = createSelector(
[selectCurrentUser],
(user) => user?.app_role,
);
/** Select user's permissions */
export const selectUserPermissions = createSelector(
[selectUserRole],
(role) => {
if (!role) return [];
return role.permissions || [];
},
);
// ============================================================================
// Entity Selectors Factory
// ============================================================================
/**
* Create memoized selectors for an entity slice
*
* @example
* const { selectData, selectCount, selectLoading } = createEntitySelectors('assets');
* const assets = useAppSelector(selectData);
*/
export function createEntitySelectors<T>(sliceName: keyof RootState) {
type EntityState = {
data: T[];
loading: boolean;
count: number;
refetch: boolean;
};
const selectSlice = (state: RootState) =>
state[sliceName] as unknown as EntityState;
const selectData = createSelector([selectSlice], (slice) => slice.data);
const selectLoading = createSelector([selectSlice], (slice) => slice.loading);
const selectCount = createSelector([selectSlice], (slice) => slice.count);
const selectRefetch = createSelector([selectSlice], (slice) => slice.refetch);
const selectFirstItem = createSelector([selectData], (data) => data[0]);
const selectById = (id: string) =>
createSelector([selectData], (data) =>
data.find((item: T & { id: string }) => item.id === id),
);
return {
selectSlice,
selectData,
selectLoading,
selectCount,
selectRefetch,
selectFirstItem,
selectById,
};
}
// ============================================================================
// Pre-built Entity Selectors
// ============================================================================
import type { Asset, TourPage, Project, Role, User } from '../types/entities';
/** Asset selectors */
export const assetSelectors = createEntitySelectors<Asset>('assets');
/** Tour page selectors */
export const tourPageSelectors = createEntitySelectors<TourPage>('tour_pages');
/** Project selectors */
export const projectSelectors = createEntitySelectors<Project>('projects');
/** Role selectors */
export const roleSelectors = createEntitySelectors<Role>('roles');
/** User selectors */
export const userSelectors = createEntitySelectors<User>('users');
// ============================================================================
// Style Selectors
// ============================================================================
/** Select style slice */
const selectStyleState = (state: RootState) => state.style;
/** Select dark mode with memoization */
export const selectDarkMode = createSelector(
[selectStyleState],
(style) => style.darkMode,
);
/** Select background layout color */
export const selectBgLayoutColor = createSelector(
[selectStyleState],
(style) => style.bgLayoutColor,
);
/** Select focus ring color */
export const selectFocusRingColor = createSelector(
[selectStyleState],
(style) => style.focusRingColor,
);
/** Select corners style */
export const selectCorners = createSelector(
[selectStyleState],
(style) => style.corners,
);
/** Select cards color */
export const selectCardsColor = createSelector(
[selectStyleState],
(style) => style.cardsColor,
);
// ============================================================================
// Constructor Selectors
// ============================================================================
/** Select constructor slice */
const selectConstructorState = (state: RootState) => state.constructorUI;
/** Select editor tab */
export const selectEditorTab = createSelector(
[selectConstructorState],
(constructor) => constructor.editorTab,
);
/** Select if menu is open */
export const selectIsMenuOpen = createSelector(
[selectConstructorState],
(constructor) => constructor.isMenuOpen,
);
/** Select if editor is collapsed */
export const selectIsEditorCollapsed = createSelector(
[selectConstructorState],
(constructor) => constructor.isEditorCollapsed,
);
/** Select last active page ID for a project */
export const selectLastActivePageId = (projectId: string) =>
createSelector(
[selectConstructorState],
(constructor) => constructor.lastActivePageId[projectId],
);