feat: reminder button in list/tabs view, notifications show reminders, trash count live update
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 43s

- Add reminder action to NoteActions (masonry view) and NoteMetaSidebar (tabs view)
- NotificationPanel now fetches and displays upcoming/overdue reminders alongside share requests
- Badge count includes overdue reminders; overdue items show mark-as-done toggle
- Sidebar trash count refreshes on NoteRefreshContext trigger (no more manual refresh)
- Add "Clear completed" button on reminders page with clearCompletedReminders action
- Add i18n keys for new features (en/fr)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 20:43:02 +02:00
parent 07f8a60b69
commit 0a900b3582
9 changed files with 283 additions and 39 deletions

View File

@@ -50,8 +50,10 @@ import {
History,
PanelRightClose,
PanelRightOpen,
Bell,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { ReminderDialog } from '@/components/reminder-dialog'
import {
Dialog,
DialogContent,
@@ -385,17 +387,20 @@ function NoteMetaSidebar({
onArchive,
onOpenHistory,
onEnableHistory,
onUpdateReminder,
}: {
note: Note
onPinToggle: (note: Note) => void
onArchive: (note: Note) => void
onOpenHistory?: (note: Note) => void
onEnableHistory?: (noteId: string) => Promise<void>
onUpdateReminder?: (noteId: string, reminder: Date | null) => void
}) {
const { t } = useLanguage()
const { notebooks, moveNoteToNotebookOptimistic } = useNotebooks()
const [moveOpen, setMoveOpen] = useState(false)
const [isMoving, setIsMoving] = useState(false)
const [showReminder, setShowReminder] = useState(false)
// t() returns the key itself when not found — use this wrapper for safe fallbacks
const ts = (key: string, fallback: string) => {
@@ -571,6 +576,32 @@ function NoteMetaSidebar({
onClick={() => onArchive(note)}
/>
{/* Reminder */}
{onUpdateReminder && (
<>
<SidebarActionBtn
icon={<Bell className={cn("h-3.5 w-3.5", note.reminder && "text-primary")} />}
label={t('reminder.setReminder')}
onClick={() => setShowReminder(true)}
/>
<div onClick={(e) => e.stopPropagation()}>
<ReminderDialog
open={showReminder}
onOpenChange={setShowReminder}
currentReminder={note.reminder ? new Date(note.reminder) : null}
onSave={(date) => {
onUpdateReminder(note.id, date)
setShowReminder(false)
}}
onRemove={() => {
onUpdateReminder(note.id, null)
setShowReminder(false)
}}
/>
</div>
</>
)}
{/* History */}
<SidebarActionBtn
icon={<History className="h-3.5 w-3.5" />}
@@ -928,6 +959,22 @@ export function NotesTabsView({
onArchive={handleArchive}
onOpenHistory={onOpenHistory}
onEnableHistory={onEnableHistory}
onUpdateReminder={async (noteId, reminder) => {
try {
await updateNote(noteId, { reminder })
setItems((prev) =>
prev.map((n) => (n.id === noteId ? { ...n, reminder } : n))
)
if (reminder) {
toast.success(t('notes.reminderSet', { datetime: reminder.toLocaleString() }))
} else {
toast.info(t('reminder.removeReminder'))
}
triggerRefresh()
} catch {
toast.error(t('general.error'))
}
}}
/>
)}
</div>