mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 18:09:40 +02:00
Add notificationService (Browser Notification API wrapper), createReminderScheduler (30s poller with source pattern for checking due reminders), and ReminderPicker UI component. Todo module gets todoReminderSource (checks task dueDate - minutesBefore) and ReminderSelector now delegates to shared ReminderPicker. Scheduler supports multiple sources (todo, calendar, planta, etc.), tag-based dedup, graceful error handling, and runtime source addition. 22 new tests (8 notification + 8 scheduler + 6 ReminderPicker). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
1.9 KiB
Svelte
68 lines
1.9 KiB
Svelte
<script lang="ts">
|
|
import { Bell, BellSlash } from '@manacore/shared-icons';
|
|
|
|
/**
|
|
* Reusable reminder time picker dropdown.
|
|
* Lets user select "X minutes before" for reminders on tasks, events, etc.
|
|
*/
|
|
|
|
interface ReminderOption {
|
|
value: number | null;
|
|
label: string;
|
|
}
|
|
|
|
interface Props {
|
|
/** Selected value in minutes (null = no reminder) */
|
|
value: number | null;
|
|
/** Called when selection changes */
|
|
onChange: (minutes: number | null) => void;
|
|
/** Custom options (defaults to standard set) */
|
|
options?: ReminderOption[];
|
|
/** Disable the picker */
|
|
disabled?: boolean;
|
|
}
|
|
|
|
const DEFAULT_OPTIONS: ReminderOption[] = [
|
|
{ value: null, label: 'Keine Erinnerung' },
|
|
{ value: 5, label: '5 Minuten vorher' },
|
|
{ value: 15, label: '15 Minuten vorher' },
|
|
{ value: 30, label: '30 Minuten vorher' },
|
|
{ value: 60, label: '1 Stunde vorher' },
|
|
{ value: 1440, label: '1 Tag vorher' },
|
|
];
|
|
|
|
let { value, onChange, options = DEFAULT_OPTIONS, disabled = false }: Props = $props();
|
|
|
|
function handleChange(e: Event) {
|
|
const target = e.target as HTMLSelectElement;
|
|
const raw = target.value;
|
|
onChange(raw === '' ? null : parseInt(raw, 10));
|
|
}
|
|
|
|
const displayLabel = $derived(
|
|
options.find((o) => o.value === value)?.label ?? 'Keine Erinnerung'
|
|
);
|
|
|
|
const hasReminder = $derived(value !== null);
|
|
</script>
|
|
|
|
<div class="inline-flex items-center gap-1.5">
|
|
{#if hasReminder}
|
|
<Bell size={14} weight="fill" class="text-primary flex-shrink-0" />
|
|
{:else}
|
|
<BellSlash size={14} class="text-muted-foreground flex-shrink-0" />
|
|
{/if}
|
|
<select
|
|
class="appearance-none bg-transparent text-xs cursor-pointer
|
|
{hasReminder ? 'text-primary font-medium' : 'text-muted-foreground'}
|
|
focus:outline-none"
|
|
{disabled}
|
|
onchange={handleChange}
|
|
>
|
|
{#each options as option}
|
|
<option value={option.value ?? ''} selected={option.value === value}>
|
|
{option.label}
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|