From 7f853654bfdf27c31781da93dde4d30bf2ed30db Mon Sep 17 00:00:00 2001 From: Ayobami Akingbade Date: Tue, 10 Oct 2023 12:05:03 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(menu):=20show=20entities=20men?= =?UTF-8?q?u=20as=20sub=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/_/forCodeCoverage.spec.ts | 5 +- src/__tests__/api/_test-utils/_app-config.ts | 4 +- .../api/config/[key]/[entity].spec.ts | 4 +- src/__tests__/api/menu/index.spec.ts | 118 ++++++++++++++++-- .../configuration/configuration.service.ts | 42 +++++-- .../dashboard-widgets.service.ts | 7 +- src/backend/entities/entities.service.ts | 2 +- .../AbstractConfigDataPersistenceService.ts | 31 +---- .../DatabaseConfigDataPersistenceAdaptor.ts | 4 +- .../JsonFileConfigDataPersistenceAdaptor.ts | 4 +- .../MemoryConfigDataPersistenceAdaptor.ts | 4 +- .../RedisConfigDataPersistenceAdaptor.ts | 4 +- .../ConfigDataPersistenceAdaptor.spec.ts | 8 +- src/backend/menu/menu.service.ts | 69 +++++----- src/backend/menu/types.ts | 2 + src/frontend/hooks/entity/entity.config.ts | 9 +- src/frontend/hooks/entity/entity.queries.ts | 23 ++-- src/frontend/views/entity/Diction/Form.tsx | 10 +- src/frontend/views/entity/Diction/index.tsx | 6 +- src/frontend/views/entity/_Base.tsx | 113 ++++++++++------- src/frontend/views/entity/portal/index.ts | 1 + src/frontend/views/entity/portal/main.ts | 10 ++ src/shared/configurations/constants.ts | 3 +- src/shared/types/config.ts | 2 + 24 files changed, 308 insertions(+), 177 deletions(-) create mode 100644 src/frontend/views/entity/portal/index.ts create mode 100644 src/frontend/views/entity/portal/main.ts diff --git a/src/__tests__/_/forCodeCoverage.spec.ts b/src/__tests__/_/forCodeCoverage.spec.ts index 8ae1e7a7b..2746b9bed 100644 --- a/src/__tests__/_/forCodeCoverage.spec.ts +++ b/src/__tests__/_/forCodeCoverage.spec.ts @@ -38,6 +38,8 @@ import { FOR_CODE_COV as $38 } from "shared/types/dashboard/types"; import { FOR_CODE_COV as $39 } from "shared/types/dashboard/base"; import { FOR_CODE_COV as $40 } from "frontend/design-system/layouts/types"; import { FOR_CODE_COV as $41 } from "frontend/design-system/components/Form/_types"; +import { FOR_CODE_COV as $42 } from "backend/menu/types"; + import { noop } from "shared/lib/noop"; noop( @@ -80,7 +82,8 @@ noop( $38, $39, $40, - $41 + $41, + $42 ); describe("Code coverage ignores plain types file", () => { diff --git a/src/__tests__/api/_test-utils/_app-config.ts b/src/__tests__/api/_test-utils/_app-config.ts index 3d1b86f3b..3702d3943 100644 --- a/src/__tests__/api/_test-utils/_app-config.ts +++ b/src/__tests__/api/_test-utils/_app-config.ts @@ -3,8 +3,8 @@ import { createConfigDomainPersistenceService } from "backend/lib/config-persist const TEST_APP_CONFIG: Partial> = { disabled_entities: ["disabled-entity-1", "disabled-entity-2"], "entity_diction__base-model": { - singular: "Base Model", - plural: "Base Models", + singular: "Base Model Singular", + plural: "Base Model Plural", }, system_settings: { forceIntrospection: false, diff --git a/src/__tests__/api/config/[key]/[entity].spec.ts b/src/__tests__/api/config/[key]/[entity].spec.ts index 28eb6e55e..116c900f4 100644 --- a/src/__tests__/api/config/[key]/[entity].spec.ts +++ b/src/__tests__/api/config/[key]/[entity].spec.ts @@ -22,8 +22,8 @@ describe("/api/config/[key]/[entity]", () => { expect(res._getStatusCode()).toBe(200); expect(res._getJSONData()).toEqual({ - singular: "Base Model", - plural: "Base Models", + singular: "Base Model Singular", + plural: "Base Model Plural", }); }); diff --git a/src/__tests__/api/menu/index.spec.ts b/src/__tests__/api/menu/index.spec.ts index 94f2a67c2..1210ab833 100644 --- a/src/__tests__/api/menu/index.spec.ts +++ b/src/__tests__/api/menu/index.spec.ts @@ -5,12 +5,12 @@ import { setupAppConfigTestData, } from "__tests__/api/_test-utils"; -describe.skip("/api/menu", () => { +describe("/api/menu", () => { beforeAll(async () => { await setupAllTestData(["schema", "app-config"]); }); - it("should list all entities not disabled", async () => { + it("should render the menu with non disabled menus", async () => { const { req, res } = createAuthenticatedMocks({ method: "GET", }); @@ -20,16 +20,63 @@ describe.skip("/api/menu", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` [ { - "label": "base-model", - "value": "base-model", + "icon": "Home", + "link": "home", + "title": "Home", + "type": "system", }, { - "label": "secondary-model", - "value": "secondary-model", + "children": [ + { + "icon": "File", + "link": "base-model", + "title": "Base Model Plural", + "type": "entities", + }, + { + "icon": "File", + "link": "secondary-model", + "title": "Secondary Model", + "type": "entities", + }, + { + "icon": "File", + "link": "tests", + "title": "Tests", + "type": "entities", + }, + ], + "icon": "File", + "link": "home", + "title": "Entities", + "type": "system", }, { - "label": "tests", - "value": "tests", + "icon": "Zap", + "link": "actions", + "title": "Actions", + "type": "system", + }, + { + "children": [], + "icon": "Settings", + "link": "settings", + "title": "Settings", + "type": "system", + }, + { + "children": [], + "icon": "Users", + "link": "users", + "title": "Users", + "type": "system", + }, + { + "children": [], + "icon": "Shield", + "link": "roles", + "title": "Roles", + "type": "system", }, ] `); @@ -51,16 +98,61 @@ describe.skip("/api/menu", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` [ { - "label": "secondary-model", - "value": "secondary-model", + "icon": "Home", + "link": "home", + "title": "Home", + "type": "system", + }, + { + "children": [ + { + "icon": "File", + "link": "secondary-model", + "title": "Secondary Model", + "type": "entities", + }, + { + "icon": "File", + "link": "base-model", + "title": "Base Model Plural", + "type": "entities", + }, + ], + "icon": "File", + "link": "home", + "title": "Entities", + "type": "system", + }, + { + "icon": "Zap", + "link": "actions", + "title": "Actions", + "type": "system", + }, + { + "children": [], + "icon": "Settings", + "link": "settings", + "title": "Settings", + "type": "system", + }, + { + "children": [], + "icon": "Users", + "link": "users", + "title": "Users", + "type": "system", }, { - "label": "base-model", - "value": "base-model", + "children": [], + "icon": "Shield", + "link": "roles", + "title": "Roles", + "type": "system", }, ] `); }); }); -// :test piService.filterPermitte +// TODO test isMenuItemAllowed diff --git a/src/backend/configuration/configuration.service.ts b/src/backend/configuration/configuration.service.ts index 21db88f8e..38ddffa2f 100644 --- a/src/backend/configuration/configuration.service.ts +++ b/src/backend/configuration/configuration.service.ts @@ -30,13 +30,37 @@ export class ConfigurationApiService implements IApplicationService { ); } + async showMultipleConfigForEntities( + key: AppConfigurationKeys, + entities: string[] + ): Promise> { + const allKeys = entities.map((entity) => + this._appConfigPersistenceService.mergeKeyWithSecondaryKey(key, entity) + ); + + const values = await this._appConfigPersistenceService.getAllItemsIn( + allKeys + ); + + return Object.fromEntries( + entities.map((entity) => [ + entity, + (values[ + this._appConfigPersistenceService.mergeKeyWithSecondaryKey( + key, + entity + ) + ] as T) || (CONFIGURATION_KEYS[key].defaultValue as T), + ]) + ); + } + async show(key: AppConfigurationKeys, entity?: string): Promise { this.checkConfigKeyEntityRequirement(key, entity); - const value = - await this._appConfigPersistenceService.getItemWithMaybeSecondaryKey( - key, - entity - ); + + const value = await this._appConfigPersistenceService.getItem( + this._appConfigPersistenceService.mergeKeyWithSecondaryKey(key, entity) + ); if (value) { return value as T; @@ -58,10 +82,10 @@ export class ConfigurationApiService implements IApplicationService { entity?: string ): Promise { this.checkConfigKeyEntityRequirement(key, entity); - await this._appConfigPersistenceService.upsertItemWithMaybeSecondaryKey( - key, - value, - entity + + return await this._appConfigPersistenceService.upsertItem( + this._appConfigPersistenceService.mergeKeyWithSecondaryKey(key, entity), + value ); } } diff --git a/src/backend/dashboard-widgets/dashboard-widgets.service.ts b/src/backend/dashboard-widgets/dashboard-widgets.service.ts index 228cb6fa9..972b0a974 100644 --- a/src/backend/dashboard-widgets/dashboard-widgets.service.ts +++ b/src/backend/dashboard-widgets/dashboard-widgets.service.ts @@ -179,10 +179,9 @@ return [actual[0], relative[0]]; return await this.generateDefaultDashboardWidgets(dashboardId); } - const widgets = - (await this._dashboardWidgetsPersistenceService.getAllItemsIn( - widgetList - )) as IWidgetConfig[]; + const widgets = Object.values( + await this._dashboardWidgetsPersistenceService.getAllItemsIn(widgetList) + ); return sortByListOrder(widgetList, widgets); } diff --git a/src/backend/entities/entities.service.ts b/src/backend/entities/entities.service.ts index a86a5d5ea..3285419cf 100644 --- a/src/backend/entities/entities.service.ts +++ b/src/backend/entities/entities.service.ts @@ -131,7 +131,7 @@ export class EntitiesApiService implements IApplicationService { ).includes(entity); } - async getAllEntities(): Promise<{ value: string; label: string }[]> { + async getAllEntities(): Promise { return (await this._schemasApiService.getDBSchema()).map(({ name }) => ({ value: name, label: name, diff --git a/src/backend/lib/config-persistence/AbstractConfigDataPersistenceService.ts b/src/backend/lib/config-persistence/AbstractConfigDataPersistenceService.ts index b2b56afd8..fb9c1c693 100644 --- a/src/backend/lib/config-persistence/AbstractConfigDataPersistenceService.ts +++ b/src/backend/lib/config-persistence/AbstractConfigDataPersistenceService.ts @@ -14,7 +14,10 @@ export abstract class AbstractConfigDataPersistenceService { this._configApiService = configApiService; } - private mergeKeyWithSecondaryKey(key: string, secondaryKey: string) { + public mergeKeyWithSecondaryKey(key: string, secondaryKey: string) { + if (!secondaryKey) { + return key; + } return `${key}__${secondaryKey}`; } @@ -30,17 +33,7 @@ export abstract class AbstractConfigDataPersistenceService { throw new NotFoundError(`${key} not found for '${this._configDomain}'`); } - public async getItemWithMaybeSecondaryKey( - key: string, - secondaryKey?: string - ): Promise { - if (!secondaryKey) { - return await this.getItem(key); - } - return await this.getItem(this.mergeKeyWithSecondaryKey(key, secondaryKey)); - } - - public abstract getAllItemsIn(itemIds: string[]): Promise; + public abstract getAllItemsIn(itemIds: string[]): Promise>; public abstract getAllAsKeyValuePair(): Promise>; @@ -60,20 +53,6 @@ export abstract class AbstractConfigDataPersistenceService { await this.persistItem(key, data); } - public async upsertItemWithMaybeSecondaryKey( - key: string, - value: T, - secondaryKey?: string - ): Promise { - if (!secondaryKey) { - return await this.upsertItem(key, value); - } - return await this.upsertItem( - this.mergeKeyWithSecondaryKey(key, secondaryKey), - value - ); - } - public abstract removeItem(key: string): Promise; public async resetState(keyField: keyof T, data: T[]) { diff --git a/src/backend/lib/config-persistence/DatabaseConfigDataPersistenceAdaptor.ts b/src/backend/lib/config-persistence/DatabaseConfigDataPersistenceAdaptor.ts index 6f9fc578d..2c40b8b9e 100644 --- a/src/backend/lib/config-persistence/DatabaseConfigDataPersistenceAdaptor.ts +++ b/src/backend/lib/config-persistence/DatabaseConfigDataPersistenceAdaptor.ts @@ -89,7 +89,9 @@ export class DatabaseConfigDataPersistenceAdaptor< .where("domain", "=", this._configDomain) .from(CONFIG_TABLE_NAME); - return items.map(({ value }) => JSON.parse(value)); + return Object.fromEntries( + items.map(({ key, value }) => [key, JSON.parse(value)]) + ); } async getItem(key: string) { diff --git a/src/backend/lib/config-persistence/JsonFileConfigDataPersistenceAdaptor.ts b/src/backend/lib/config-persistence/JsonFileConfigDataPersistenceAdaptor.ts index c1ec3b420..d7c16f312 100644 --- a/src/backend/lib/config-persistence/JsonFileConfigDataPersistenceAdaptor.ts +++ b/src/backend/lib/config-persistence/JsonFileConfigDataPersistenceAdaptor.ts @@ -66,7 +66,9 @@ export class JsonFileConfigDataPersistenceAdaptor< async getAllItemsIn(itemIds: string[]) { const allIndexedItems = await this.getDomainData(); - return itemIds.map((itemId) => allIndexedItems[itemId]); + return Object.fromEntries( + itemIds.map((itemId) => [itemId, allIndexedItems[itemId]]) + ); } async getItem(key: string) { diff --git a/src/backend/lib/config-persistence/MemoryConfigDataPersistenceAdaptor.ts b/src/backend/lib/config-persistence/MemoryConfigDataPersistenceAdaptor.ts index 7a3609811..5c9bca6e4 100644 --- a/src/backend/lib/config-persistence/MemoryConfigDataPersistenceAdaptor.ts +++ b/src/backend/lib/config-persistence/MemoryConfigDataPersistenceAdaptor.ts @@ -51,7 +51,9 @@ export class MemoryConfigDataPersistenceAdaptor< this._configDomain ); - return itemIds.map((itemId) => allItems[itemId]); + return Object.fromEntries( + itemIds.map((itemId) => [itemId, allItems[itemId]]) + ); } async getItem(key: string) { diff --git a/src/backend/lib/config-persistence/RedisConfigDataPersistenceAdaptor.ts b/src/backend/lib/config-persistence/RedisConfigDataPersistenceAdaptor.ts index 5090c6a5b..93762e9c0 100644 --- a/src/backend/lib/config-persistence/RedisConfigDataPersistenceAdaptor.ts +++ b/src/backend/lib/config-persistence/RedisConfigDataPersistenceAdaptor.ts @@ -67,7 +67,9 @@ export class RedisConfigDataPersistenceAdaptor< await this.getRedisInstance() ).hmGet(this.wrapWithConfigDomain(), itemIds); - return Object.values(allData).map((value) => JSON.parse(value)); + return Object.fromEntries( + allData.map((value, index) => [itemIds[index], JSON.parse(value)]) + ); } async getItem(key: string) { diff --git a/src/backend/lib/config-persistence/__tests__/ConfigDataPersistenceAdaptor.spec.ts b/src/backend/lib/config-persistence/__tests__/ConfigDataPersistenceAdaptor.spec.ts index 10d4f75f6..ccd9bbc9f 100644 --- a/src/backend/lib/config-persistence/__tests__/ConfigDataPersistenceAdaptor.spec.ts +++ b/src/backend/lib/config-persistence/__tests__/ConfigDataPersistenceAdaptor.spec.ts @@ -148,10 +148,10 @@ describe.each(PERSITENT_ADAPTORS)( }); it("should getAllItemsIn", async () => { - expect(await adaptor.getAllItemsIn(["id-2", "id-3"])).toEqual([ - { age: 2, id: "id-2", name: "Second Item" }, - { age: 3, id: "id-3", name: "Third Item" }, - ]); + expect(await adaptor.getAllItemsIn(["id-2", "id-3"])).toEqual({ + "id-2": { age: 2, id: "id-2", name: "Second Item" }, + "id-3": { age: 3, id: "id-3", name: "Third Item" }, + }); }); } ); diff --git a/src/backend/menu/menu.service.ts b/src/backend/menu/menu.service.ts index a179f7b65..0d924087e 100644 --- a/src/backend/menu/menu.service.ts +++ b/src/backend/menu/menu.service.ts @@ -21,6 +21,7 @@ import { import { sortByList } from "shared/logic/entities/sort.utils"; import { RolesApiService, rolesApiService } from "backend/roles/roles.service"; import { ILabelValue } from "shared/types/options"; +import { ISingularPlural } from "shared/types/config"; import { portalCheckIfIsMenuAllowed, getPortalMenuItems } from "./portal"; import { IBaseNavigationMenuApiService } from "./types"; @@ -71,33 +72,33 @@ export class NavigationMenuApiService }, ]); - const entitiesToShow = await this.getUserMenuEntities(); + const entitiesToShow = await this.getUserEntities(); + + const dictionMap = + await this._configurationApiService.showMultipleConfigForEntities( + "entity_diction", + entitiesToShow.map((value) => value.value) + ); navItems = navItems.concat([ { id: nanoid(), title: "Entities", - type: NavigationMenuItemType.Header, - children: [], + icon: "File", + type: NavigationMenuItemType.System, + link: SystemLinks.Home, + children: entitiesToShow.map((entity) => ({ + id: nanoid(), + title: + dictionMap[entity.value].plural || userFriendlyCase(entity.label), + icon: "File", + type: NavigationMenuItemType.Entities, + link: entity.value, + })), }, ]); - entitiesToShow.forEach((entity) => { - navItems.push({ - id: nanoid(), - title: userFriendlyCase(entity.label), // TODO get the current label - icon: "File", - type: NavigationMenuItemType.Entities, - link: entity.value, - }); - }); - navItems = navItems.concat([ - { - id: nanoid(), - title: "Application Menu", - type: NavigationMenuItemType.Header, - }, { id: nanoid(), title: "Actions", @@ -113,37 +114,29 @@ export class NavigationMenuApiService link: SystemLinks.Settings, children: [], }, + { id: nanoid(), - title: "Accounts", + title: "Users", icon: "Users", type: NavigationMenuItemType.System, link: SystemLinks.Users, - children: [ - { - id: nanoid(), - title: "Users", - icon: "Users", - type: NavigationMenuItemType.System, - link: SystemLinks.Users, - children: [], - }, - { - id: nanoid(), - title: "Roles", - icon: "Shield", - type: NavigationMenuItemType.System, - link: SystemLinks.Roles, - children: [], - }, - ], + children: [], + }, + { + id: nanoid(), + title: "Roles", + icon: "Shield", + type: NavigationMenuItemType.System, + link: SystemLinks.Roles, + children: [], }, ]); return navItems; } - private async getUserMenuEntities(): Promise { + private async getUserEntities(): Promise { const [hiddenMenuEntities, entitiesOrder, activeEntities] = await Promise.all([ this._configurationApiService.show("disabled_menu_entities"), diff --git a/src/backend/menu/types.ts b/src/backend/menu/types.ts index 17db2bd3f..966300988 100644 --- a/src/backend/menu/types.ts +++ b/src/backend/menu/types.ts @@ -7,3 +7,5 @@ export interface IBaseNavigationMenuApiService { navItems: INavigationMenuItem[] ): Promise; } + +export const FOR_CODE_COV = 1; diff --git a/src/frontend/hooks/entity/entity.config.ts b/src/frontend/hooks/entity/entity.config.ts index 0de1083b2..3a20cf0cc 100644 --- a/src/frontend/hooks/entity/entity.config.ts +++ b/src/frontend/hooks/entity/entity.config.ts @@ -14,6 +14,7 @@ import { uniqBy } from "shared/lib/array/uniq-by"; import { userFriendlyCase } from "shared/lib/strings/friendly-case"; import { FIELD_TYPES_CONFIG_MAP } from "shared/validations"; import { IEntityCrudSettings } from "shared/configurations"; +import { ISingularPlural } from "shared/types/config"; import { useEntityFields } from "./entity.store"; import { getFieldTypeBoundedValidations, @@ -33,10 +34,10 @@ export function useEntityId() { export function useEntityDiction(paramEntity?: string) { const entity = useEntitySlug(paramEntity); - const entityDiction = useEntityConfiguration<{ - plural: string; - singular: string; - }>("entity_diction", entity); + const entityDiction = useEntityConfiguration( + "entity_diction", + entity + ); return { singular: entityDiction.data?.singular || userFriendlyCase(entity), plural: entityDiction.data?.plural || userFriendlyCase(entity), diff --git a/src/frontend/hooks/entity/entity.queries.ts b/src/frontend/hooks/entity/entity.queries.ts index 830a9dab1..fb4cc0aae 100644 --- a/src/frontend/hooks/entity/entity.queries.ts +++ b/src/frontend/hooks/entity/entity.queries.ts @@ -2,24 +2,23 @@ import { useCallback } from "react"; import { userFriendlyCase } from "shared/lib/strings/friendly-case"; import { useApiQueries } from "frontend/lib/data/useApi/useApiQueries"; import { AppStorage } from "frontend/lib/storage/app"; +import { ISingularPlural } from "shared/types/config"; import { configurationApiPath } from "../configuration/configuration.store"; export function useEntityDictionPlurals( input: T[], field: P ) { - const entityDictions = useApiQueries( - { - input, - accessor: field, - pathFn: (entity) => - configurationApiPath("entity_diction", entity as unknown as string), - placeholderDataFn: (entity) => - AppStorage.get( - configurationApiPath("entity_diction", entity as unknown as string) - ), - } - ); + const entityDictions = useApiQueries({ + input, + accessor: field, + pathFn: (entity) => + configurationApiPath("entity_diction", entity as unknown as string), + placeholderDataFn: (entity) => + AppStorage.get( + configurationApiPath("entity_diction", entity as unknown as string) + ), + }); return useCallback( (fieldName: string, singular?: boolean): string => { diff --git a/src/frontend/views/entity/Diction/Form.tsx b/src/frontend/views/entity/Diction/Form.tsx index 50cc38015..d9fcabe14 100644 --- a/src/frontend/views/entity/Diction/Form.tsx +++ b/src/frontend/views/entity/Diction/Form.tsx @@ -1,18 +1,14 @@ import { IFormProps } from "frontend/lib/form/types"; import { SchemaForm } from "frontend/components/SchemaForm"; import { MAKE_APP_CONFIGURATION_CRUD_CONFIG } from "frontend/hooks/configuration/configuration.constant"; - -type IDictionSettings = { - plural: string; - singular: string; -}; +import { ISingularPlural } from "shared/types/config"; export function EntityDictionForm({ onSubmit, initialValues, -}: IFormProps) { +}: IFormProps) { return ( - + onSubmit={onSubmit} initialValues={initialValues} icon="save" diff --git a/src/frontend/views/entity/Diction/index.tsx b/src/frontend/views/entity/Diction/index.tsx index 2756585a6..f3012ba46 100644 --- a/src/frontend/views/entity/Diction/index.tsx +++ b/src/frontend/views/entity/Diction/index.tsx @@ -16,6 +16,7 @@ import { MAKE_APP_CONFIGURATION_CRUD_CONFIG } from "frontend/hooks/configuration import { useState } from "react"; import { DictionDocumentation } from "frontend/docs/diction"; import { DOCUMENTATION_LABEL } from "frontend/docs"; +import { NAVIGATION_MENU_ENDPOINT } from "frontend/_layouts/app/LayoutImpl/constants"; import { BaseEntitySettingsLayout } from "../_Base"; import { EntityDictionForm } from "./Form"; import { ENTITY_CONFIGURATION_VIEW } from "../constants"; @@ -30,7 +31,10 @@ export function EntityDictionSettings() { const entityDiction = useEntityDiction(); const upsertConfigurationMutation = useUpsertConfigurationMutation( "entity_diction", - entity + entity, + { + otherEndpoints: [NAVIGATION_MENU_ENDPOINT], + } ); const [isDocOpen, setIsDocOpen] = useState(false); diff --git a/src/frontend/views/entity/_Base.tsx b/src/frontend/views/entity/_Base.tsx index 6db6eb162..e433974cd 100644 --- a/src/frontend/views/entity/_Base.tsx +++ b/src/frontend/views/entity/_Base.tsx @@ -15,7 +15,10 @@ import { Codepen, } from "react-feather"; import { SoftButton } from "frontend/design-system/components/Button/SoftButton"; -import { MenuSection } from "frontend/design-system/components/Section/MenuSection"; +import { + IMenuSectionItem, + MenuSection, +} from "frontend/design-system/components/Section/MenuSection"; import { ContentLayout } from "frontend/design-system/components/Section/SectionDivider"; import { Spacer } from "frontend/design-system/primitives/Spacer"; import { ADMIN_ACTION_INSTANCES_CRUD_CONFIG } from "./Actions/constants"; @@ -23,6 +26,62 @@ import { ENTITY_CRUD_SETTINGS_TAB_LABELS, ENTITY_FIELD_SETTINGS_TAB_LABELS, } from "./constants"; +import { useMutateBaseEntitySettingsMenu } from "./portal"; + +const baseMenuItems = (entity: string): IMenuSectionItem[] => [ + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.CRUD(entity, { + tab: ENTITY_CRUD_SETTINGS_TAB_LABELS.CREATE, + }), + IconComponent: Sliders, + name: "CRUD", + order: 10, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.DICTION(entity), + name: "Diction", + IconComponent: Type, + order: 20, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.FIELDS(entity, { + tab: ENTITY_FIELD_SETTINGS_TAB_LABELS.LABELS, + }), + name: "Fields", + IconComponent: File, + order: 30, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.RELATIONS(entity), + name: "Relations", + IconComponent: Link2, + order: 40, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.VIEWS(entity), + name: "Views", + IconComponent: Filter, + order: 50, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.FORM(entity), + name: "Form Scripts", + IconComponent: Code, + order: 60, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.PRESENTATION(entity), + name: "Presentation Scripts", + IconComponent: Codepen, + order: 70, + }, + { + action: NAVIGATION_LINKS.ENTITY.CONFIG.FORM_INTEGRATIONS(entity), + name: ADMIN_ACTION_INSTANCES_CRUD_CONFIG.TEXT_LANG.TITLE, + IconComponent: Zap, + order: 80, + }, +]; interface IProps { children: ReactNode; @@ -33,52 +92,10 @@ export function BaseEntitySettingsLayout({ children }: IProps) { const { canGoBack, goBack } = useNavigationStack(); const router = useRouter(); - const baseMenuItems = [ - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.CRUD(entity, { - tab: ENTITY_CRUD_SETTINGS_TAB_LABELS.CREATE, - }), - IconComponent: Sliders, - name: "CRUD", - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.DICTION(entity), - name: "Diction", - IconComponent: Type, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.FIELDS(entity, { - tab: ENTITY_FIELD_SETTINGS_TAB_LABELS.LABELS, - }), - name: "Fields", - IconComponent: File, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.RELATIONS(entity), - name: "Relations", - IconComponent: Link2, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.VIEWS(entity), - name: "Views", - IconComponent: Filter, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.FORM(entity), - name: "Form Scripts", - IconComponent: Code, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.PRESENTATION(entity), - name: "Presentation Scripts", - IconComponent: Codepen, - }, - { - action: NAVIGATION_LINKS.ENTITY.CONFIG.FORM_INTEGRATIONS(entity), - name: ADMIN_ACTION_INSTANCES_CRUD_CONFIG.TEXT_LANG.TITLE, - IconComponent: Zap, - }, - ]; + const menuItems = useMutateBaseEntitySettingsMenu( + entity, + baseMenuItems(entity) + ); return ( @@ -99,7 +116,7 @@ export function BaseEntitySettingsLayout({ children }: IProps) { diff --git a/src/frontend/views/entity/portal/index.ts b/src/frontend/views/entity/portal/index.ts new file mode 100644 index 000000000..184d92030 --- /dev/null +++ b/src/frontend/views/entity/portal/index.ts @@ -0,0 +1 @@ +export { useMutateBaseEntitySettingsMenu } from "./main"; diff --git a/src/frontend/views/entity/portal/main.ts b/src/frontend/views/entity/portal/main.ts new file mode 100644 index 000000000..8f9573d2b --- /dev/null +++ b/src/frontend/views/entity/portal/main.ts @@ -0,0 +1,10 @@ +import { IMenuSectionItem } from "frontend/design-system/components/Section/MenuSection"; +import { noop } from "shared/lib/noop"; + +export const useMutateBaseEntitySettingsMenu = ( + entity: string, + baseMenu: IMenuSectionItem[] +) => { + noop(entity); + return baseMenu; +}; diff --git a/src/shared/configurations/constants.ts b/src/shared/configurations/constants.ts index 001aff0ea..889bd42bd 100644 --- a/src/shared/configurations/constants.ts +++ b/src/shared/configurations/constants.ts @@ -1,3 +1,4 @@ +import { ISingularPlural } from "shared/types/config"; import { BaseAppConfigurationKeys } from "./base-types"; import { PortalConfigurationKeys, PORTAL_CONFIGURATION_KEYS } from "./portal"; import { DEFAULT_SYSTEM_SETTINGS } from "./system"; @@ -55,7 +56,7 @@ export const CONFIGURATION_KEYS: Record< entity_diction: { crudConfigLabel: "Diction Settings", requireEntity: true, - defaultValue: { singular: "", plural: "" }, + defaultValue: { singular: "", plural: "" } as ISingularPlural, }, entity_form_extension: { crudConfigLabel: "Form Scripts", diff --git a/src/shared/types/config.ts b/src/shared/types/config.ts index 22f7373a7..0ab6fa778 100644 --- a/src/shared/types/config.ts +++ b/src/shared/types/config.ts @@ -5,4 +5,6 @@ export type ISiteSettings = { fullLogo: string; }; +export type ISingularPlural = { singular: string; plural: string }; + export const FOR_CODE_COV = 1;