feat(260929-if2): faellige Erinnerungen erledigen, spaeter erinnern, bearbeiten und loeschen
- API: Aendern (409 wenn faellig), Spaeter erinnern (409 wenn nicht faellig, setzt E-Mail-Spur zurueck), Loeschen; fremde Kennungen 404 - Kachel: faellige Zeilen hervorgehoben mit Erledigt und drei Spaeter-Optionen, kuenftige mit Bearbeiten und Loeschen - Zeit-Hilfen (morgen zur gleichen Uhrzeit), Zugriffsklassifikation nachgemessen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,22 @@ import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { requestBrowserPermissionOnce } from '@/lib/reminder-notify';
|
||||
import { defaultNewReminderInputs, localInputsToIso } from '@/lib/reminder-time';
|
||||
import { ReminderRequestError, createReminder } from '@/lib/reminders-api';
|
||||
import { defaultNewReminderInputs, isoToLocalInputs, localInputsToIso } from '@/lib/reminder-time';
|
||||
import {
|
||||
type Reminder,
|
||||
ReminderRequestError,
|
||||
createReminder,
|
||||
updateReminder,
|
||||
} from '@/lib/reminders-api';
|
||||
|
||||
interface ReminderFormModalProps {
|
||||
/** `null` = neue Erinnerung anlegen, sonst diese (noch nicht faellige) bearbeiten. */
|
||||
reminder: Reminder | null;
|
||||
onClose: () => void;
|
||||
/** Nach erfolgreichem Speichern (die Kachel laedt neu und meldet die Aenderung). */
|
||||
onSaved: () => void;
|
||||
/** Der Server meldet, dass die Erinnerung inzwischen faellig ist (409) — die Kachel laedt neu. */
|
||||
onStale?: () => void;
|
||||
}
|
||||
|
||||
const TITLE_MAX = 200;
|
||||
@@ -25,18 +34,23 @@ const DESCRIPTION_MAX = 2000;
|
||||
* macht sich fuer `position: fixed` zum Bezugsrahmen (Vorbild
|
||||
* `picture-frame-lightbox.tsx`). Dialog-Aufbau wie `custom-module-form-modal`.
|
||||
*
|
||||
* ERLAUBNIS (D-04): `requestBrowserPermissionOnce()` laeuft als ERSTES im
|
||||
* Absende-Handler, synchron und noch vor jedem `await` — so bleibt der Aufruf
|
||||
* BEARBEITEN (`reminder` gesetzt): nur fuer eine noch nicht faellige Erinnerung;
|
||||
* eine inzwischen faellige lehnt der Server mit 409 ab (`alreadyDue`).
|
||||
*
|
||||
* ERLAUBNIS (D-04): `requestBrowserPermissionOnce()` laeuft beim ANLEGEN als
|
||||
* ERSTES im Absende-Handler, synchron und noch vor jedem `await` — so bleibt der Aufruf
|
||||
* eine Nutzerhandlung, die der Browser fuer die Frage akzeptiert. Nie beim
|
||||
* Laden der Seite.
|
||||
*/
|
||||
export function ReminderFormModal({ onClose, onSaved }: ReminderFormModalProps) {
|
||||
export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: ReminderFormModalProps) {
|
||||
const t = useTranslations('widgets');
|
||||
const initial = useRef(defaultNewReminderInputs(new Date())).current;
|
||||
const initial = useRef(
|
||||
reminder ? isoToLocalInputs(reminder.dueAt) : defaultNewReminderInputs(new Date()),
|
||||
).current;
|
||||
const [date, setDate] = useState(initial.date);
|
||||
const [time, setTime] = useState(initial.time);
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [title, setTitle] = useState(reminder?.title ?? '');
|
||||
const [description, setDescription] = useState(reminder?.description ?? '');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const titleRef = useRef<HTMLInputElement>(null);
|
||||
@@ -53,7 +67,7 @@ export function ReminderFormModal({ onClose, onSaved }: ReminderFormModalProps)
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Als Erstes und synchron: Nutzerhandlung fuer die Browser-Erlaubnis (D-04).
|
||||
requestBrowserPermissionOnce();
|
||||
if (!reminder) requestBrowserPermissionOnce();
|
||||
setError(null);
|
||||
|
||||
const trimmedTitle = title.trim();
|
||||
@@ -70,15 +84,22 @@ export function ReminderFormModal({ onClose, onSaved }: ReminderFormModalProps)
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await createReminder({ title: trimmedTitle, description: description.trim(), dueAt });
|
||||
const input = { title: trimmedTitle, description: description.trim(), dueAt };
|
||||
if (reminder) {
|
||||
await updateReminder(reminder.id, input);
|
||||
} else {
|
||||
await createReminder(input);
|
||||
}
|
||||
onSaved();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof ReminderRequestError && err.status === 409
|
||||
? t('reminder.limitReached')
|
||||
: t('reminder.saveError'),
|
||||
);
|
||||
if (err instanceof ReminderRequestError && err.status === 409) {
|
||||
// Anlegen: Grenze erreicht; Bearbeiten: inzwischen faellig.
|
||||
setError(reminder ? t('reminder.alreadyDue') : t('reminder.limitReached'));
|
||||
if (reminder) onStale?.();
|
||||
} else {
|
||||
setError(t('reminder.saveError'));
|
||||
}
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -95,7 +116,7 @@ export function ReminderFormModal({ onClose, onSaved }: ReminderFormModalProps)
|
||||
className="w-full max-w-md rounded-lg bg-card p-6 shadow-lg dark:border dark:border-border"
|
||||
>
|
||||
<h2 id="reminder-form-title" className="mb-4 text-lg font-semibold text-foreground">
|
||||
{t('reminder.formTitle')}
|
||||
{reminder ? t('reminder.editTitle') : t('reminder.formTitle')}
|
||||
</h2>
|
||||
<form onSubmit={handleSubmit} noValidate className="space-y-4">
|
||||
<div className="flex gap-3">
|
||||
|
||||
Reference in New Issue
Block a user