Files
picpeak/frontend/src/features/settings/components/SidebarPreview.tsx
T
Paul Nothaft c6cb01865e fix(settings): derive the sidebar preview from the real sidebar declaration
SidebarPreview kept its own hand-maintained 6-item array with only two gates
wired (analytics, userManagement), so toggling e.g. Workflows changed nothing
in the preview even though it does add a real sidebar entry once saved.

Export AdminSidebar's `navigation` as `adminNavigation` (2 lines) and derive
the preview from it, so every gate -- transfers, messaging, analytics,
userManagement, clients incl. its featureFlagsAny set, accounting, workflows --
is covered and the two can't drift again.

Note the report's item list was partly wrong: Quotes, Contracts, Invoices,
Hours, Projects, Calendar and the CRM dev tools have no top-level sidebar
entries at all -- they are sub-nav inside /admin/clients and surface in the
preview through the CRM entry's featureFlagsAny.

Permission filtering is deliberately not applied (unchanged): the preview
answers "what do these flags do to the sidebar", not "what can this admin see".

Refs testplan REPORT.md #20 (Part 3, J.14).
2026-09-01 16:29:33 +02:00

82 lines
3.1 KiB
TypeScript

import React, { useMemo } from 'react';
import clsx from 'clsx';
import { Card } from '../../../components/common';
import { useTranslation } from 'react-i18next';
import { adminNavigation } from '../../../components/admin/AdminSidebar';
import type { FeatureFlags } from '../../../contexts/FeatureFlagsContext';
interface PreviewItem {
key: string;
label: string;
icon: React.ComponentType<{ className?: string }>;
featureDriven: boolean;
}
interface SidebarPreviewProps {
staged: FeatureFlags;
}
/**
* Renders the live shape of the main admin sidebar based on the user's
* staged (unsaved) feature flags. Items in green (primary tint) are
* controlled by toggles above; greyscale items are unconditional.
*
* Derived from AdminSidebar's own `adminNavigation` declaration so every
* feature-gated entry is covered automatically — the previous hand-kept
* copy had drifted to 2 of the gates (QA J.14). Permissions are
* deliberately NOT applied here: the preview answers "what do these flags
* do to the sidebar", not "what can this particular admin see".
*/
export const SidebarPreview: React.FC<SidebarPreviewProps> = ({ staged }) => {
const { t } = useTranslation();
const items = useMemo<PreviewItem[]>(() => adminNavigation
.filter((it) => (!it.featureFlag || staged[it.featureFlag])
&& (!it.featureFlagsAny?.length || it.featureFlagsAny.some((k) => staged[k])))
.map((it) => ({
key: it.nameKey,
label: t(it.nameKey),
icon: it.icon,
featureDriven: Boolean(it.featureFlag || it.featureFlagsAny?.length),
})), [staged, t]);
return (
<Card padding="md">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-neutral-900 dark:text-neutral-100">
{t('settings.features.preview.title', 'Sidebar preview')}
</h3>
<span className="text-xs text-neutral-500 dark:text-neutral-400">
{t('settings.features.preview.note', 'Reflects unsaved changes')}
</span>
</div>
<ul className="flex flex-wrap gap-2">
{items.map((item) => (
<li
key={item.key}
className={clsx(
'inline-flex items-center gap-2 px-2.5 py-1.5 rounded-md text-xs font-medium border',
// Feature-driven pills pick up the admin's CI accent via
// .bg-accent-soft / .border-accent-soft, with
// .text-on-accent-soft as the legible foreground (the
// accent token itself washes out on its own tint).
item.featureDriven
? 'border-accent-soft bg-accent-soft text-on-accent-soft'
: 'border-neutral-200 bg-neutral-50 text-neutral-700 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300',
)}
>
<item.icon className="w-3.5 h-3.5" />
{item.label}
</li>
))}
</ul>
<p className="mt-3 text-xs text-neutral-500 dark:text-neutral-400">
{t(
'settings.features.preview.legend',
'Accent-tinted items are controlled by toggles above.',
)}
</p>
</Card>
);
};