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:
co-authored by
Claude Opus 5.5
parent
b34a1968df
commit
1ad774fdd2
@@ -12,6 +12,7 @@ import { useToast } from '../Toast';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { PrimaryButton, SecondaryButton, inputClass } from '../ui/form';
|
||||
import {
|
||||
AlarmBell,
|
||||
SOURCE_LABELS,
|
||||
SOURCE_ORDER,
|
||||
STATUS_LABELS,
|
||||
@@ -63,7 +64,10 @@ export const ScannerDashboard: React.FC = () => {
|
||||
const [knownOnly, setKnownOnly] = useState(false);
|
||||
const [showDiscarded, setShowDiscarded] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [sort, setSort] = useState<'known' | 'score' | 'published'>('known');
|
||||
const [sort, setSort] = useState<'known' | 'score' | 'published' | 'oldest'>('known');
|
||||
/** Alter der Anzeige: alle | bis 21 Tage | älter als 21 Tage */
|
||||
const [age, setAge] = useState<'' | 'recent' | 'old'>('');
|
||||
const [alarmOnly, setAlarmOnly] = useState(false);
|
||||
|
||||
const wasRunning = useRef(false);
|
||||
|
||||
@@ -77,6 +81,8 @@ export const ScannerDashboard: React.FC = () => {
|
||||
known: knownOnly ? 1 : undefined,
|
||||
status: showDiscarded ? 'alle' : undefined,
|
||||
q: search.trim() || undefined,
|
||||
age: age || undefined,
|
||||
alarm: alarmOnly ? 1 : undefined,
|
||||
sort,
|
||||
});
|
||||
setMatches(data.matches);
|
||||
@@ -84,7 +90,7 @@ export const ScannerDashboard: React.FC = () => {
|
||||
} catch (err) {
|
||||
toast(err instanceof Error ? err.message : 'Treffer nicht ladbar.', 'error');
|
||||
}
|
||||
}, [hours, agentId, source, minScore, knownOnly, showDiscarded, search, sort, toast]);
|
||||
}, [hours, agentId, source, minScore, knownOnly, showDiscarded, search, age, alarmOnly, sort, toast]);
|
||||
|
||||
const loadOverview = useCallback(async () => {
|
||||
try {
|
||||
@@ -168,7 +174,7 @@ export const ScannerDashboard: React.FC = () => {
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm text-ink-500">
|
||||
Offene Stellen aus Jobbörsen in DACH, höchstens {overview?.schedule.maxAge ?? 21} Tage alt.
|
||||
Offene Stellen aus Jobbörsen in DACH – jedes Alter, gefiltert wird unten nach „Anzeigenalter“.
|
||||
</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
||||
<span
|
||||
@@ -236,7 +242,7 @@ export const ScannerDashboard: React.FC = () => {
|
||||
<div className="flex flex-wrap items-end gap-3 border-b border-ink-200 p-4">
|
||||
<h2 className="mr-auto text-sm font-bold uppercase tracking-wider text-ink-900">Stellen-Treffer</h2>
|
||||
|
||||
<Filter label="Zeitraum" className="w-44">
|
||||
<Filter label="Gefunden" className="w-44">
|
||||
<select value={hours} onChange={(event) => setHours(Number(event.target.value))} className={inputClass}>
|
||||
{PERIODS.map((period) => (
|
||||
<option key={period.value} value={period.value}>
|
||||
@@ -265,6 +271,13 @@ export const ScannerDashboard: React.FC = () => {
|
||||
))}
|
||||
</select>
|
||||
</Filter>
|
||||
<Filter label="Anzeigenalter" className="w-44">
|
||||
<select value={age} onChange={(event) => setAge(event.target.value as typeof age)} className={inputClass}>
|
||||
<option value="">alle</option>
|
||||
<option value="recent">bis 21 Tage</option>
|
||||
<option value="old">älter als 21 Tage</option>
|
||||
</select>
|
||||
</Filter>
|
||||
<Filter label="Ab %" className="w-20">
|
||||
<input
|
||||
type="number"
|
||||
@@ -281,6 +294,7 @@ export const ScannerDashboard: React.FC = () => {
|
||||
<option value="known">bekannte Firmen zuerst</option>
|
||||
<option value="score">beste Quote</option>
|
||||
<option value="published">neueste Anzeige</option>
|
||||
<option value="oldest">älteste Anzeige</option>
|
||||
</select>
|
||||
</Filter>
|
||||
<Filter label="Suche" className="w-44">
|
||||
@@ -296,6 +310,10 @@ export const ScannerDashboard: React.FC = () => {
|
||||
<input type="checkbox" checked={knownOnly} onChange={(event) => setKnownOnly(event.target.checked)} />
|
||||
nur bekannte Firmen
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" checked={alarmOnly} onChange={(event) => setAlarmOnly(event.target.checked)} />
|
||||
nur Alarm
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" checked={showDiscarded} onChange={(event) => setShowDiscarded(event.target.checked)} />
|
||||
verworfene zeigen
|
||||
@@ -340,7 +358,10 @@ export const ScannerDashboard: React.FC = () => {
|
||||
} ${match.status === 'verworfen' ? 'opacity-60' : ''}`}
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<ScoreBox score={match.score} />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<ScoreBox score={match.score} />
|
||||
{match.alarm && <AlarmBell />}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 font-semibold text-ink-900">{match.agent_name}</td>
|
||||
<td className="max-w-md px-4 py-3">
|
||||
|
||||
Reference in New Issue
Block a user