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
@@ -78,12 +78,58 @@ export const formatAge = (value: string | null) => {
|
||||
|
||||
export const WEEKDAY_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||||
|
||||
const BELL_ICON =
|
||||
'M12 22a2.5 2.5 0 0 0 2.5-2.5h-5A2.5 2.5 0 0 0 12 22Zm7-5V11a7 7 0 0 0-5.5-6.8V3a1.5 1.5 0 0 0-3 0v1.2A7 7 0 0 0 5 11v6l-2 2v1h18v-1l-2-2Z';
|
||||
|
||||
/** Glocke für Treffer mit Alarm-Schlagwort. */
|
||||
export const AlarmBell: React.FC<{ className?: string }> = ({ className = 'h-4 w-4' }) => (
|
||||
<span title="Alarm-Schlagwort" className="inline-flex text-red-600">
|
||||
<svg viewBox="0 0 24 24" className={className} fill="currentColor" aria-hidden="true">
|
||||
<path d={BELL_ICON} />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
|
||||
/**
|
||||
* Kurzer Zweiklang über WebAudio - ohne Audiodatei.
|
||||
*
|
||||
* Browser spielen Ton erst, nachdem auf der Seite einmal geklickt oder getippt
|
||||
* wurde (Autoplay-Regel). Klappt es nicht, bleibt es still; das Pop-up kommt
|
||||
* trotzdem.
|
||||
*/
|
||||
export function playAlarmSound() {
|
||||
try {
|
||||
const AudioContextClass =
|
||||
window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
|
||||
if (!AudioContextClass) return;
|
||||
const context = new AudioContextClass();
|
||||
const tone = (frequency: number, start: number) => {
|
||||
const oscillator = context.createOscillator();
|
||||
const gain = context.createGain();
|
||||
oscillator.type = 'sine';
|
||||
oscillator.frequency.value = frequency;
|
||||
gain.gain.setValueAtTime(0.0001, context.currentTime + start);
|
||||
gain.gain.exponentialRampToValueAtTime(0.3, context.currentTime + start + 0.02);
|
||||
gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + start + 0.35);
|
||||
oscillator.connect(gain).connect(context.destination);
|
||||
oscillator.start(context.currentTime + start);
|
||||
oscillator.stop(context.currentTime + start + 0.4);
|
||||
};
|
||||
tone(880, 0);
|
||||
tone(1175, 0.18);
|
||||
// Nicht geschlossene Kontexte zählen gegen das Browserlimit.
|
||||
setTimeout(() => void context.close().catch(() => {}), 1000);
|
||||
} catch {
|
||||
/* kein Ton möglich - das Pop-up reicht */
|
||||
}
|
||||
}
|
||||
|
||||
/** Eingabe von Begriffen als Chips: Enter oder Komma fügt hinzu. */
|
||||
export const TermInput: React.FC<{
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
placeholder?: string;
|
||||
tone?: 'brand' | 'red';
|
||||
tone?: 'brand' | 'red' | 'alarm';
|
||||
}> = ({ value, onChange, placeholder, tone = 'brand' }) => {
|
||||
const [draft, setDraft] = React.useState('');
|
||||
|
||||
@@ -99,12 +145,17 @@ export const TermInput: React.FC<{
|
||||
};
|
||||
|
||||
const chip =
|
||||
tone === 'red' ? 'border-red-200 bg-red-50 text-red-700' : 'border-brand-200 bg-brand-50 text-brand-700';
|
||||
tone === 'red'
|
||||
? 'border-red-200 bg-red-50 text-red-700'
|
||||
: tone === 'alarm'
|
||||
? 'border-amber-300 bg-amber-50 text-amber-800'
|
||||
: 'border-brand-200 bg-brand-50 text-brand-700';
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[42px] flex-wrap items-center gap-1.5 rounded-lg border border-ink-200 bg-surface px-2 py-1.5">
|
||||
{value.map((term) => (
|
||||
<span key={term} className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium ${chip}`}>
|
||||
{tone === 'alarm' && <AlarmBell className="h-3 w-3" />}
|
||||
{term}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user