Files
platform/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx
T
pastilhas c3c62828e3 settings framework and section metadata keyed (487 -> 425)
SettingsSection now carries titleKey/descriptionKey instead of title/description, and
the sidebar and content pane resolve them. The section arrays are module-level consts
holding JSX content, so they cannot call a hook — the data holds keys and the renderers
translate. Sidebar search matches the RESOLVED words, so it works in the language on
screen.

Capital K in titleKey is load-bearing: docs-check-settings.ts greps key:\s*'…' for
route segments, and a lowercase title_key would have fed it twenty bogus paths.
Verified 0 bogus, 26 declared, 20 offered, 0 missing.

Section titles now share one key with the Spotlight rows that name the same section,
per 'one key per meaning'. Two casings had to converge, so Settings shows Spotlight's:
'Change Password' now reads 'Change password'.

Settings sidebar search placeholder was 'Search...'; it is now 'Search settings…',
which is what the skeleton catalog already said and what distinguishes it from the
global Spotlight.
2026-08-23 04:46:07 +00:00

111 lines
3.5 KiB
TypeScript

import { useMemo } from 'react';
import { Bot, Hammer, Mail, Mic, ScanText, Settings, Volume2 } from 'lucide-react';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout } from 'officerdev';
import { SettingsRoute, createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
import { SMTPSection } from './ServerSettings/SMTPSection';
import { MaintenanceSection } from './ServerSettings/MaintenanceSection';
import { TTSSection } from './ServerSettings/TTSSection';
import { STTSection } from './ServerSettings/STTSection';
import { OCRSection } from './ServerSettings/OCRSection';
const groups: SettingsSectionGroup[] = [
{
labelKey: 'settings.groups.ai',
icon: Bot,
sections: [
{
key: 'tts',
icon: Volume2,
titleKey: 'settings.sections.tts.label',
descriptionKey: 'settings.sections.tts.description',
content: <TTSSection />,
},
{
key: 'stt',
icon: Mic,
titleKey: 'settings.sections.stt.label',
descriptionKey: 'settings.sections.stt.description',
content: <STTSection />,
},
{
key: 'ocr',
icon: ScanText,
titleKey: 'settings.sections.ocr.label',
descriptionKey: 'settings.sections.ocr.description',
content: <OCRSection />,
},
],
},
{
labelKey: 'settings.groups.maintenance',
icon: Hammer,
sections: [
{
key: 'maintenance',
icon: Hammer,
titleKey: 'settings.sections.maintenance.label',
descriptionKey: 'settings.sections.maintenance.description',
content: <MaintenanceSection />,
},
],
},
{
labelKey: 'settings.groups.email',
icon: Mail,
sections: [
{
key: 'smtp',
icon: Mail,
titleKey: 'settings.sections.smtp.label',
descriptionKey: 'settings.sections.smtp.description',
content: <SMTPSection />,
},
],
},
];
const BASE_PATH = '/settings/system';
const { Sidebar, Content, allSections } = createSettingsPanelComponents({
basePath: BASE_PATH,
sidebarIcon: Settings,
sidebarLabelKey: 'settings.nav.system',
groups,
});
// There used to be a second layout here that split a terminal in below the settings content, driven by a
// `system-settings:run-command` channel: a section asked for a sudo command, this screen grew a pane, ran
// it, and invalidated the queries the section named. Its only writer was the AI harness installer, and
// that moved server-side to `POST /server-settings/chat-providers/install` — leaving a channel whose two
// remaining writes were both `setState(null)`, a pane nothing could ever open, and a layout nothing could
// ever select. Removed rather than rewired; the install path it existed for no longer wants a terminal.
const baseLayout: LayoutNode = {
type: 'group',
id: 'system-root',
direction: 'horizontal',
children: [
{ node: { type: 'panel', id: 'system-left', appType: null }, size: 20 },
{ node: { type: 'panel', id: 'system-right', appType: null }, size: 80 },
],
};
export const SystemSettings = () => {
const panelComponents: PanelComponents = useMemo(
() => ({
'system-left': Sidebar,
'system-right': Content,
}),
[],
);
return (
<SettingsRoute basePath={BASE_PATH} sections={allSections}>
<div className="h-full w-full pt-2">
<WorkspaceLayout layout={baseLayout} onLayoutChange={() => {}} components={panelComponents} />
</div>
</SettingsRoute>
);
};