Add general calendar events (recording, social, task, general); nav links; v1.1.16
- Four event types on the calendar: General, Recording, Social, Task — each color-coded - + Event button opens form with type picker, title, date, notes, reminder - Task events have a Mark Complete toggle; completed events show strikethrough - Edit/delete popover for each event - Email reminders work for calendar events same as episodes - Added Email/Calendar nav links to /contacts header - Added Email link to /calendar header (already had Admin) - /email already links to Contacts and Calendar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { state } from '../state.js'
|
||||
import { queueCalendarEventsWrite } from '../data.js'
|
||||
import { requireAdminAuth } from '../auth.js'
|
||||
|
||||
const VALID_TYPES = ['general', 'recording', 'social', 'task']
|
||||
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/
|
||||
|
||||
function sanitize(ev) {
|
||||
return {
|
||||
id: ev.id,
|
||||
type: VALID_TYPES.includes(ev.type) ? ev.type : 'general',
|
||||
title: typeof ev.title === 'string' ? ev.title.slice(0, 300) : '',
|
||||
date: typeof ev.date === 'string' && DATE_RE.test(ev.date) ? ev.date : '',
|
||||
notes: typeof ev.notes === 'string' ? ev.notes.slice(0, 2000) : '',
|
||||
completed: Boolean(ev.completed),
|
||||
reminderDays: Number.isFinite(ev.reminderDays) && ev.reminderDays > 0 ? ev.reminderDays : 0,
|
||||
reminderSentAt: typeof ev.reminderSentAt === 'string' ? ev.reminderSentAt : undefined,
|
||||
createdAt: typeof ev.createdAt === 'string' ? ev.createdAt : new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
export function register(app) {
|
||||
app.get('/api/admin-calendar-events', requireAdminAuth, (_req, res) => {
|
||||
res.json({ events: state.calendarEvents })
|
||||
})
|
||||
|
||||
app.post('/api/admin-calendar-events', requireAdminAuth, (req, res) => {
|
||||
const { type, title, date, notes, reminderDays } = req.body ?? {}
|
||||
if (!title?.trim()) { res.status(400).json({ message: 'Title is required.' }); return }
|
||||
if (!date || !DATE_RE.test(date)) { res.status(400).json({ message: 'Valid date is required.' }); return }
|
||||
if (type && !VALID_TYPES.includes(type)) { res.status(400).json({ message: 'Invalid type.' }); return }
|
||||
|
||||
const ev = sanitize({
|
||||
id: randomUUID(),
|
||||
type: type ?? 'general',
|
||||
title: String(title).trim(),
|
||||
date,
|
||||
notes: notes ?? '',
|
||||
completed: false,
|
||||
reminderDays: reminderDays ?? 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
|
||||
state.calendarEvents.unshift(ev)
|
||||
queueCalendarEventsWrite()
|
||||
res.json({ ok: true, event: ev })
|
||||
})
|
||||
|
||||
app.patch('/api/admin-calendar-events/:id', requireAdminAuth, (req, res) => {
|
||||
const { id } = req.params
|
||||
let found = false
|
||||
state.calendarEvents = state.calendarEvents.map(ev => {
|
||||
if (ev.id !== id) return ev
|
||||
found = true
|
||||
const patch = {}
|
||||
if (typeof req.body?.title === 'string') patch.title = req.body.title.trim().slice(0, 300)
|
||||
if (typeof req.body?.date === 'string' && DATE_RE.test(req.body.date)) patch.date = req.body.date
|
||||
if (typeof req.body?.notes === 'string') patch.notes = req.body.notes.trim().slice(0, 2000)
|
||||
if (typeof req.body?.completed === 'boolean') patch.completed = req.body.completed
|
||||
if (VALID_TYPES.includes(req.body?.type)) patch.type = req.body.type
|
||||
if (Number.isFinite(req.body?.reminderDays)) patch.reminderDays = req.body.reminderDays
|
||||
const dateChanged = patch.date && patch.date !== ev.date
|
||||
const reminderChanged = 'reminderDays' in patch && patch.reminderDays !== ev.reminderDays
|
||||
if (dateChanged || reminderChanged) patch.reminderSentAt = undefined
|
||||
return { ...ev, ...patch }
|
||||
})
|
||||
if (!found) { res.status(404).json({ message: 'Event not found.' }); return }
|
||||
queueCalendarEventsWrite()
|
||||
res.json({ ok: true })
|
||||
})
|
||||
|
||||
app.delete('/api/admin-calendar-events/:id', requireAdminAuth, (req, res) => {
|
||||
const before = state.calendarEvents.length
|
||||
state.calendarEvents = state.calendarEvents.filter(ev => ev.id !== req.params.id)
|
||||
if (state.calendarEvents.length === before) { res.status(404).json({ message: 'Event not found.' }); return }
|
||||
queueCalendarEventsWrite()
|
||||
res.json({ ok: true })
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user