Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/application/sidebar/AppSidebar.tsx
T

270 lines
9.3 KiB
TypeScript

import { SideBarItem } from "./SidebarItem";
import { EntitySimple } from "~/utils/db/entities/entities.db.server";
import { TFunction, type i18n as I18n } from "i18next";
import { EntityGroupWithDetails } from "~/utils/db/entities/entityGroups.db.server";
import { AppConfiguration } from "~/utils/db/appConfiguration.db.server";
import IconSettings from "~/components/layouts/icons/IconSettings";
import IconDashboard from "~/components/layouts/icons/IconDashboard";
import IconAdmin from "~/components/layouts/icons/IconAdmin";
import IconNotifications from "~/components/layouts/icons/IconNotifications";
import IconEmails from "~/components/layouts/icons/IconEmails";
import IconKeys from "~/components/layouts/icons/IconKeys";
import IconWorkflows from "~/components/layouts/icons/IconWorkflows";
import CreditsIcon from "~/components/ui/icons/CreditsIcon";
import AddressBookIcon from "~/components/ui/icons/crm/AddressBookIcon";
import IconDocs from "~/components/layouts/icons/IconDocs";
import ViewListIcon from "~/components/ui/icons/ViewListIcon";
import { UI_ID_PATTERNS, UI_NAV_IDS, uiIdSegment } from "~/application/ui/uiRegistry";
import { appPath } from "~/utils/app/CanonicalUrlUtils";
import { absoluteAdminPath, type PublicDomainRoutingConfig } from "~/utils/app/CrossHostUrls";
import { getEntityPermission } from "~/utils/helpers/PermissionsHelper";
import { translateLabel } from "~/utils/i18n/translateLabel";
import { resolveConnectorNavState } from "~/utils/connector/connectorNavState";
import { WALKING_SKELETON_MODULE_KEY } from "~/modules/walkingSkeleton/constants";
type Props = {
t: TFunction;
i18n: I18n;
tenantId: string;
entities: EntitySimple[];
entityGroups: EntityGroupWithDetails[];
appConfiguration: AppConfiguration | null;
routing?: PublicDomainRoutingConfig;
productAccess?: Record<string, { allowed: boolean; reason?: string }>;
};
export const AppSidebar = ({ t, i18n, tenantId, entities, entityGroups, routing, productAccess }: Props): SideBarItem[] => {
const tenantPath = (path?: string) => appPath({ tenantSlug: tenantId, path, mode: "host-aware" });
const hasFeatureAccess = (featureKey: string) => productAccess?.[featureKey]?.allowed === true;
// R-P1c Konnektor-Demo: Walking-Skeleton-Eintrag zeigt die drei Gate-Zustaende (aktiv/gesperrt/ausgeblendet).
const walkingSkeletonDecision = productAccess?.[WALKING_SKELETON_MODULE_KEY];
const walkingSkeletonState = walkingSkeletonDecision ? resolveConnectorNavState(walkingSkeletonDecision) : "hidden";
const sectionItems: SideBarItem[] = [];
const entitiesItems: SideBarItem[] = [];
const productCoreItems: SideBarItem[] = [
...(hasFeatureAccess("crm-basic")
? [
{
id: UI_NAV_IDS.appCrm,
title: t("crm:title"),
path: tenantPath("crm"),
icon: <AddressBookIcon />,
},
]
: []),
...(hasFeatureAccess("files")
? [
{
id: "ui.nav.app.files",
title: t("files.nav.title"),
path: tenantPath("files"),
icon: <IconDocs />,
permission: "files.read_own" as const,
},
]
: []),
...(hasFeatureAccess("tasks")
? [
{
id: "ui.nav.app.tasks",
title: t("tasks.nav.title"),
path: tenantPath("tasks"),
icon: <ViewListIcon />,
permission: "tasks.read_own" as const,
},
]
: []),
...(hasFeatureAccess("email-marketing")
? [
{
id: UI_NAV_IDS.appEmailMarketing,
title: t("emailMarketing:title"),
path: tenantPath("email-marketing"),
icon: <IconEmails />,
},
]
: []),
...(hasFeatureAccess("workflow-engine")
? [
{
id: UI_NAV_IDS.appWorkflowEngine,
title: t("workflows.title"),
path: tenantPath("workflow-engine"),
icon: <IconWorkflows />,
},
]
: []),
...(hasFeatureAccess("api")
? [
{
id: UI_NAV_IDS.appApi,
title: t("models.apiKey.plural"),
path: tenantPath("settings/api"),
icon: <IconKeys />,
},
]
: []),
...(hasFeatureAccess("ai-credits")
? [
{
id: UI_NAV_IDS.appCredits,
title: t("models.credit.plural"),
path: tenantPath("settings/credits"),
icon: <CreditsIcon />,
},
]
: []),
...(walkingSkeletonState !== "hidden"
? [
{
id: "ui.nav.app.walkingSkeleton",
title: t("walkingSkeleton.nav"),
path: tenantPath("walking-skeleton"),
icon: <IconDashboard />,
disabled: walkingSkeletonState === "locked",
},
]
: []),
];
entityGroups.forEach((group) => {
const item: SideBarItem = {
id: `ui.nav.app.entityGroup.${uiIdSegment(group.slug)}`,
uiIdPattern: UI_ID_PATTERNS.appEntityGroup,
uiIdParams: { groupSlug: group.slug },
title: translateLabel(t, i18n, group.title),
// icon: group.icon,
path: tenantPath(`g/${group.slug}`),
icon: group.icon,
// isActive: true,
};
if (group.collapsible) {
item.isCollapsible = true;
item.items = [
{
id: `ui.nav.app.entityGroup.${uiIdSegment(group.slug)}.overview`,
title: t("shared.overview"),
path: tenantPath(`g/${group.slug}`),
},
...group.entities.map(({ entity }) => {
return {
id: `ui.nav.app.entityGroup.${uiIdSegment(group.slug)}.entity.${uiIdSegment(entity.slug)}`,
uiIdPattern: UI_ID_PATTERNS.appEntityGroupEntity,
uiIdParams: { groupSlug: group.slug, entityName: entity.name ?? entity.slug },
title: translateLabel(t, i18n, entity.titlePlural),
// icon: entity.icon,
path: tenantPath(`g/${group.slug}/${entity.slug}`),
permission: getEntityPermission(entity, "view"),
// entityIcon: entity.icon,
};
}),
];
}
if (group.section) {
const sectionTitle = translateLabel(t, i18n, group.section);
const section = sectionItems.find((f) => f.title === sectionTitle);
if (section) {
section.items = [...(section.items ?? []), item];
} else {
sectionItems.push({
id: `ui.nav.app.section.${uiIdSegment(group.section)}`,
uiIdPattern: UI_ID_PATTERNS.appSection,
uiIdParams: { sectionName: group.section },
title: sectionTitle,
path: ``,
items: [item],
});
}
} else {
entitiesItems.push(item);
}
});
entities
.filter((f) => f.showInSidebar)
.forEach((entity) => {
entitiesItems.push({
id: `ui.nav.app.entity.${uiIdSegment(entity.slug)}`,
uiIdPattern: UI_ID_PATTERNS.appEntity,
uiIdParams: { entityName: entity.name ?? entity.slug, entitySlug: entity.slug },
title: translateLabel(t, i18n, entity.titlePlural),
icon: entity.icon,
// tenantUserTypes: [TenantUserType.OWNER, TenantUserType.ADMIN],
path: tenantPath(entity.slug),
permission: getEntityPermission(entity, "view"),
// side: isDebugMode && SideText({ text: modelProperties.length > 0 ? "DB Model" : "Custom" }),
});
});
const appItem: SideBarItem = {
id: "ui.nav.app.primary",
title: "",
path: "",
items: [
{
id: UI_NAV_IDS.appDashboard,
title: t("app.sidebar.dashboard"),
path: tenantPath("dashboard"),
icon: <IconDashboard />,
// tenantUserTypes: [TenantUserType.OWNER, TenantUserType.ADMIN, TenantUserType.MEMBER],
},
{
id: UI_NAV_IDS.appNotifications,
title: t("app.sidebar.notifications"),
path: tenantPath("notifications"),
icon: <IconNotifications />,
},
...productCoreItems,
...entitiesItems,
// {
// title: t("models.email.plural"),
// path: `${currentTenantUrl}/emails`,
// icon: SvgIcon.EMAILS,
// },
],
};
return [
appItem,
...sectionItems,
{
id: "ui.nav.app.secondary",
title: "",
path: "",
isSecondary: true,
items: [
{
id: UI_NAV_IDS.appSettings,
title: t("app.sidebar.settings"),
icon: <IconSettings />,
// tenantUserTypes: [TenantUserType.OWNER, TenantUserType.ADMIN],
path: tenantPath("settings"),
redirectTo: tenantPath("settings/profile"),
},
// {
// title: t("app.sidebar.logs"),
// icon: SvgIcon.LOGS,
// tenantUserTypes: [TenantUserType.OWNER, TenantUserType.ADMIN],
// path: `${currentTenantUrl}/logs`,
// },
{
id: UI_NAV_IDS.appSwitchToAdmin,
title: t("admin.switchToAdmin"),
path: absoluteAdminPath(routing, "dashboard"),
icon: <IconAdmin />,
adminOnly: true,
},
],
},
];
};
// function SideText({ text }: { text: string }) {
// return (
// <div className="inline-flex justify-center space-x-1 items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-900 italic truncate w-20">
// <div className="truncate">{text}</div>
// </div>
// );
// }