feat(scanner): Schwellen im Dashboard pro Benutzer

"Anzeige ab %" und "Hinweis ab %" liegen jetzt am CRM-Benutzer
(crm_users.scanner_display_threshold/scanner_match_threshold) statt global
in den Scanner-Einstellungen. NULL = globaler Standard. Die Mail-Schwelle
bleibt global, die KI-Suche nimmt die Anzeigeschwelle des Suchenden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-10-27 08:50:40 +01:00
co-authored by Claude Opus 5.5
parent e1b69fffbd
commit 69001e9687
11 changed files with 114 additions and 24 deletions
+14 -13
View File
@@ -21,6 +21,7 @@ import {
type ScannerAgent,
type ScannerMatch,
type ScannerSettings,
type ScannerThresholds,
type ScannerStatus,
} from '../../lib/scannerApi';
@@ -165,13 +166,14 @@ export const ScannerDashboard: React.FC = () => {
async (silent = false) => {
if (!silent) setIsLoading(true);
try {
const [agentRows, portalRows, matchRows, settings, statusRow] = await Promise.all([
const [agentRows, portalRows, matchRows, settings, statusRow, mine] = await Promise.all([
scannerApi.agents(),
scannerApi.portals(),
scannerApi.matches({ limit: 2000 }),
// Fehlende Einstellungen sind kein Grund, das Dashboard leer zu lassen.
scannerApi.settings().catch((): Partial<ScannerSettings> => ({})),
scannerApi.status().catch((): ScannerStatus | null => null),
scannerApi.myThresholds().catch((): ScannerThresholds | null => null),
]);
setAgents(agentRows);
@@ -181,16 +183,15 @@ export const ScannerDashboard: React.FC = () => {
// den Knopf hier genauso sperren wie ein selbst gestarteter.
if (statusRow) setIsScanning(statusRow.scanRunning);
const parsedDisplay = parseInt(
settings.display_threshold ?? SCANNER_SETTINGS_DEFAULTS.display_threshold,
10
);
// Eigene Schwellen des Benutzers gehen vor, sonst der globale Standard.
const parsedDisplay =
mine?.display_threshold ??
parseInt(settings.display_threshold ?? SCANNER_SETTINGS_DEFAULTS.display_threshold, 10);
if (!Number.isNaN(parsedDisplay)) setDisplayThreshold(parsedDisplay);
const parsedNotify = parseInt(
settings.match_threshold ?? SCANNER_SETTINGS_DEFAULTS.match_threshold,
10
);
const parsedNotify =
mine?.match_threshold ??
parseInt(settings.match_threshold ?? SCANNER_SETTINGS_DEFAULTS.match_threshold, 10);
if (!Number.isNaN(parsedNotify)) {
notifyThreshold.current = parsedNotify;
setMatchThreshold(parsedNotify);
@@ -282,15 +283,15 @@ export const ScannerDashboard: React.FC = () => {
if (saveTimer.current) window.clearTimeout(saveTimer.current);
}, []);
/** Beide Schwellenwerte liegen in den Einstellungen - gebuendelt speichern. */
/** Beide Schwellenwerte gehoeren dem Benutzer - gebuendelt speichern. */
const persistThresholds = (display: number, notify: number) => {
if (saveTimer.current) window.clearTimeout(saveTimer.current);
setSaveState('pending');
saveTimer.current = window.setTimeout(async () => {
try {
await scannerApi.saveSettings({
display_threshold: String(display),
match_threshold: String(notify),
await scannerApi.saveMyThresholds({
display_threshold: display,
match_threshold: notify,
});
setSaveState('saved');
window.setTimeout(() => setSaveState('idle'), 1500);