improvement/ux-overhaul up #61
44
frontend/src/components/shared/hidden-tabs.ts
Normal file
44
frontend/src/components/shared/hidden-tabs.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Tabs hidden from navigation and staff permission UI until features ship.
|
||||||
|
* Routes, pages, and backend permissions stay in place — flip flags to re-enable.
|
||||||
|
*/
|
||||||
|
export const HIDDEN_TAB_FEATURE_FLAGS = {
|
||||||
|
billing: true,
|
||||||
|
reports: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const HIDDEN_PATHS: readonly string[] = [
|
||||||
|
...(HIDDEN_TAB_FEATURE_FLAGS.billing ? (['/billing'] as const) : []),
|
||||||
|
...(HIDDEN_TAB_FEATURE_FLAGS.reports ? (['/reports'] as const) : []),
|
||||||
|
];
|
||||||
|
|
||||||
|
const HIDDEN_PERMISSIONS: readonly string[] = [
|
||||||
|
...(HIDDEN_TAB_FEATURE_FLAGS.billing
|
||||||
|
? (['TAB_BILLING_READ', 'TAB_BILLING_EDIT'] as const)
|
||||||
|
: []),
|
||||||
|
...(HIDDEN_TAB_FEATURE_FLAGS.reports
|
||||||
|
? (['TAB_REPORTS_READ', 'TAB_REPORTS_EDIT'] as const)
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
|
const HIDDEN_PATH_SET = new Set<string>(HIDDEN_PATHS);
|
||||||
|
const HIDDEN_PERMISSION_SET = new Set<string>(HIDDEN_PERMISSIONS);
|
||||||
|
|
||||||
|
export function isHiddenTabPath(path: string): boolean {
|
||||||
|
return HIDDEN_PATH_SET.has(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHiddenTabPermission(permission: string): boolean {
|
||||||
|
return HIDDEN_PERMISSION_SET.has(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Keep hidden-tab grants when saving staff permissions the UI no longer shows. */
|
||||||
|
export function preserveHiddenTabPermissions(
|
||||||
|
fromForm: string[],
|
||||||
|
existing?: string[] | null,
|
||||||
|
): string[] {
|
||||||
|
if (!existing?.length) return fromForm;
|
||||||
|
const preserved = existing.filter((p) => isHiddenTabPermission(p));
|
||||||
|
if (!preserved.length) return fromForm;
|
||||||
|
return [...new Set([...fromForm, ...preserved])];
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Organization } from '@/types/organization';
|
import type { Organization } from '@/types/organization';
|
||||||
|
import { isHiddenTabPath } from '@/components/shared/hidden-tabs';
|
||||||
|
|
||||||
export type OrgTypeName = 'CLINIC' | 'LAB';
|
export type OrgTypeName = 'CLINIC' | 'LAB';
|
||||||
|
|
||||||
@@ -64,6 +65,10 @@ export function canAccessDashboardRoute(org: Organization | null, pathname: stri
|
|||||||
const route = getRouteConfigForPath(pathname);
|
const route = getRouteConfigForPath(pathname);
|
||||||
if (!route) return true;
|
if (!route) return true;
|
||||||
|
|
||||||
|
if (isHiddenTabPath(route.prefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isRouteAllowedForOrgType(pathname, org.type)) {
|
if (!isRouteAllowedForOrgType(pathname, org.type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -89,6 +94,7 @@ export function firstAccessibleDashboardPath(org: Organization | null): string {
|
|||||||
|
|
||||||
for (const route of DASHBOARD_ROUTES) {
|
for (const route of DASHBOARD_ROUTES) {
|
||||||
if (!route.orgTypes.includes(org.type)) continue;
|
if (!route.orgTypes.includes(org.type)) continue;
|
||||||
|
if (isHiddenTabPath(route.prefix)) continue;
|
||||||
if (route.prefix === '/appointments') {
|
if (route.prefix === '/appointments') {
|
||||||
if (canAccessAppointmentsSection(org)) return route.prefix;
|
if (canAccessAppointmentsSection(org)) return route.prefix;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { OrgTypeName } from '@/components/shared/permissions';
|
import type { OrgTypeName } from '@/components/shared/permissions';
|
||||||
|
import { isHiddenTabPermission } from '@/components/shared/hidden-tabs';
|
||||||
|
|
||||||
export const STAFF_FEATURE_GROUPS = [
|
export const STAFF_FEATURE_GROUPS = [
|
||||||
{ labelKey: 'featureToday', read: 'TAB_TODAY_READ', edit: 'TAB_TODAY_EDIT', orgTypes: ['CLINIC', 'LAB'] as const },
|
{ labelKey: 'featureToday', read: 'TAB_TODAY_READ', edit: 'TAB_TODAY_EDIT', orgTypes: ['CLINIC', 'LAB'] as const },
|
||||||
@@ -24,8 +25,9 @@ export type OrgType = OrgTypeName | null | undefined;
|
|||||||
type StaffFeaturesTranslate = (key: string) => string;
|
type StaffFeaturesTranslate = (key: string) => string;
|
||||||
|
|
||||||
export function staffFeatureGroupsForOrgType(organizationType: OrgType) {
|
export function staffFeatureGroupsForOrgType(organizationType: OrgType) {
|
||||||
if (!organizationType) return [...STAFF_FEATURE_GROUPS];
|
const visible = STAFF_FEATURE_GROUPS.filter((g) => !isHiddenTabPermission(g.read));
|
||||||
return STAFF_FEATURE_GROUPS.filter((g) =>
|
if (!organizationType) return [...visible];
|
||||||
|
return visible.filter((g) =>
|
||||||
(g.orgTypes as readonly OrgTypeName[]).includes(organizationType),
|
(g.orgTypes as readonly OrgTypeName[]).includes(organizationType),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
canViewTasks,
|
canViewTasks,
|
||||||
canViewTab,
|
canViewTab,
|
||||||
} from '@/components/shared/permissions';
|
} from '@/components/shared/permissions';
|
||||||
|
import { isHiddenTabPath } from '@/components/shared/hidden-tabs';
|
||||||
import {
|
import {
|
||||||
counterpartOrganizationType,
|
counterpartOrganizationType,
|
||||||
organizationTypeIcon,
|
organizationTypeIcon,
|
||||||
@@ -82,6 +83,9 @@ function Sidebar({ mobileOpen = false, onClose }: SidebarProps) {
|
|||||||
const visibleMenu = useMemo(
|
const visibleMenu = useMemo(
|
||||||
() =>
|
() =>
|
||||||
menu.filter((item) => {
|
menu.filter((item) => {
|
||||||
|
if (isHiddenTabPath(item.path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!orgType || !item.orgTypes.includes(orgType)) {
|
if (!orgType || !item.orgTypes.includes(orgType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
canEditStaff,
|
canEditStaff,
|
||||||
canViewStaff,
|
canViewStaff,
|
||||||
} from '@/components/shared/permissions';
|
} from '@/components/shared/permissions';
|
||||||
|
import { preserveHiddenTabPermissions } from '@/components/shared/hidden-tabs';
|
||||||
import {
|
import {
|
||||||
permissionNamesFromFeatureState,
|
permissionNamesFromFeatureState,
|
||||||
emptyFeaturePermissionState,
|
emptyFeaturePermissionState,
|
||||||
@@ -464,7 +465,10 @@ export function StaffPage() {
|
|||||||
|
|
||||||
await staffApi.updateMember(editing.id, {
|
await staffApi.updateMember(editing.id, {
|
||||||
name: editName.trim(),
|
name: editName.trim(),
|
||||||
permissionNames: permissionNamesFromFeatureState(editPerms),
|
permissionNames: preserveHiddenTabPermissions(
|
||||||
|
permissionNamesFromFeatureState(editPerms),
|
||||||
|
editing.permissions,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.showSuccess(t('successMemberUpdated'));
|
toast.showSuccess(t('successMemberUpdated'));
|
||||||
|
|||||||
Reference in New Issue
Block a user