import { Component } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableRow from '@mui/material/TableRow';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
import { StorageManagerContext } from '../../api/storageManagerContext';
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
/**
* Settings that may be set via PUT /api/company/setting/{key}.
* Only these keys are accepted by the backend.
*/
const SETTABLE_KEYS = [
'ui.theme',
'keys.tavily',
'keys.openrouter',
];
const KEY_LABELS = {
'ui.theme': 'UI-Theme',
'keys.tavily': 'Tavily API-Key',
'keys.openrouter': 'OpenRouter API-Key',
};
function keyLabel(key) {
return KEY_LABELS[key] ?? key;
}
export default class SettingsKeysPanel extends Component {
static contextType = StorageManagerContext;
constructor(props) {
super(props);
this.state = {
status: 'loading',
error: null,
settings: [],
// key → { value, busy, saveError }
drafts: {},
};
this.handleRetry = this.handleRetry.bind(this);
this.handleSave = this.handleSave.bind(this);
}
componentDidMount() {
void this.load();
}
async load() {
this.setState({ status: 'loading', error: null });
try {
const data = await this.context.setting.list();
requireObject(data, 'GET /api/company/setting');
const settings = requireArray(data.settings, 'settings');
const drafts = Object.fromEntries(
settings.map((s) => [
s.key,
{
value: s.value ?? '',
busy: false,
saveError: null,
},
]),
);
this.setState({ status: 'ready', settings, drafts });
} catch (err) {
notifyError(err);
this.setState({ status: 'failed', error: backendErrorFrom(err) });
}
}
handleRetry() {
void this.load();
}
handleChange(key, field, value) {
this.setState((prev) => ({
drafts: {
...prev.drafts,
[key]: { ...prev.drafts[key], [field]: value, saveError: null },
},
}));
}
async handleSave(key) {
const draft = this.state.drafts[key];
if (!draft || draft.busy) return;
const row = this.state.settings.find((s) => s.key === key);
if (!row) return;
const body = {};
if (draft.value !== (row.value ?? '')) {
body.value = draft.value;
}
if (Object.keys(body).length === 0) {
return;
}
this.setState((prev) => ({
drafts: {
...prev.drafts,
[key]: { ...prev.drafts[key], busy: true, saveError: null },
},
}));
try {
const data = await this.context.setting.upsert(key, body);
requireObject(data, 'PUT /api/company/setting/:key');
requireObject(data.setting, 'setting');
const updated = requireDefined(data.setting, 'setting');
const settings = this.state.settings.map((s) =>
s.key === key ? { ...s, ...body } : s,
);
this.setState({
settings,
drafts: {
...this.state.drafts,
[key]: {
value: updated.value ?? '',
busy: false,
saveError: null,
},
},
});
enqueueSnackbar('Einstellung gespeichert.', { variant: 'success' });
} catch (err) {
if (this._unmounted) return;
this.setState((prev) => ({
drafts: {
...prev.drafts,
[key]: { ...prev.drafts[key], busy: false, saveError: backendErrorFrom(err) },
},
}));
}
}
componentWillUnmount() {
this._unmounted = true;
}
render() {
const { status, error, settings, drafts } = this.state;
if (status === 'loading') {
return (
);
}
if (status === 'failed') {
return ;
}
if (settings.length === 0) {
return (
Keine Einstellungen.
);
}
return (
{SETTABLE_KEYS.map((key) => {
const row = settings.find((s) => s.key === key);
const draft = row ? drafts[row.key] : null;
if (!row || !draft) return null;
return (
{keyLabel(row.key)}
this.handleChange(row.key, 'value', e.target.value)}
autoComplete="off"
/>
{draft.saveError ? (
{typeof draft.saveError === 'string'
? draft.saveError
: draft.saveError.message}
) : null}
void this.handleSave(row.key)}
>
);
})}
);
}
}