Scanner: ohne Altersgrenze suchen, Filter Anzeigenalter, Alarm-Schlagwörter

- Suche ohne 21-Tage-Grenze (BA ohne veroeffentlichtseit, Adzuna ohne
  max_days_old); Einstellung Höchstalter entfällt
- Dashboard: Filter Anzeigenalter (bis/älter als 21 Tage), Sortierung
  älteste Anzeige, Filter nur Alarm, Zeitraum heißt jetzt Gefunden
- Suchagent: Schlagwörter mit Alarm; Treffer mit alarm = 1 melden sich
  über den AlarmWatcher (App-weit, jede Minute) mit Ton und Pop-up,
  Quittieren gilt für alle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-29 13:28:29 +02:00
co-authored by Claude Opus 5.5
parent b34a1968df
commit 1ad774fdd2
16 changed files with 978 additions and 625 deletions
+29 -2
View File
@@ -11,7 +11,7 @@ import { useNavigation } from '../../lib/navigation';
import { useToast } from '../Toast';
import { ConfirmDialog, Modal } from '../ui/Modal';
import { Field, PrimaryButton, SecondaryButton, inputClass } from '../ui/form';
import { SOURCE_LABELS, SOURCE_ORDER, TermInput } from './scannerUi';
import { AlarmBell, SOURCE_LABELS, SOURCE_ORDER, TermInput } from './scannerUi';
/*
* Scanner -> Suchagenten.
@@ -26,6 +26,7 @@ const EMPTY: ScannerAgentInput = {
keywords: [],
sources: ['ba', 'stepstone', 'karriere'],
excludes: [],
alarm_keywords: [],
is_active: true,
};
@@ -145,6 +146,7 @@ export const ScannerAgents: React.FC = () => {
keywords: agent.keywords,
sources: agent.sources,
excludes: agent.excludes,
alarm_keywords: agent.alarm_keywords,
is_active: agent.is_active,
},
})
@@ -169,6 +171,19 @@ export const ScannerAgents: React.FC = () => {
</span>
))}
</Row>
{agent.alarm_keywords.length > 0 && (
<Row label="Schlagwörter mit Alarm">
{agent.alarm_keywords.map((term) => (
<span
key={term}
className="inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-800"
>
<AlarmBell className="h-3 w-3" />
{term}
</span>
))}
</Row>
)}
<Row label="Börsen">
{agent.sources.map((id) => (
<span
@@ -209,7 +224,7 @@ export const ScannerAgents: React.FC = () => {
</SecondaryButton>
<PrimaryButton
type="button"
disabled={busy || !value?.name.trim() || !value?.keywords.length || !value?.sources.length}
disabled={busy || !value?.name.trim() || !(value?.keywords.length || value?.alarm_keywords.length) || !value?.sources.length}
onClick={() => void save()}
>
Speichern
@@ -236,6 +251,18 @@ export const ScannerAgents: React.FC = () => {
/>
</Field>
<Field
label="Schlagwörter mit Alarm"
hint="Werden gesucht und gezählt wie Schlagwörter. Wird eines gefunden, gibt es kurz einen Ton und ein Pop-up – in jeder Ansicht des CRM."
>
<TermInput
value={value.alarm_keywords}
onChange={(alarm_keywords) => patch({ alarm_keywords })}
placeholder="z. B. Interim CFO"
tone="alarm"
/>
</Field>
<Field label="Ausschlussbegriffe" hint="Kommt einer davon in Titel oder Text vor, wird die Anzeige ignoriert.">
<TermInput
value={value.excludes}