import { addDays } from '../habits/calendar'; import { dateSchema, type Schedule } from '../habits/contracts'; import { dailyAction, detail } from './daily'; import { fields, type Context, type ControlData, type Message, type Reply } from './types'; import { button, screen, modal, pages, row, text, componentMessage, display, panel, section, listSize, listPage, pageLabel } from './ui'; const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; function scheduleLabel(schedule: Schedule): string { if (schedule.type === 'daily') return 'Every day'; if (schedule.type === 'weekdays') return schedule.days.map(day => weekdays[day]).join(', '); if (schedule.type === 'interval') return `Every ${schedule.every} days from ${schedule.anchor}`; return `Every ${schedule.every} weeks on ${weekdays[schedule.weekday]} from ${schedule.anchor}`; } export function history(c: Context, data: ControlData = {}): Reply { const all = c.service.list(true); const page = listPage(data.page, all.length); const state: ControlData = { date: data.date ?? c.service.today, mode: 'history', today: false }; return componentMessage(c, [ panel([display(`## History\n${text(c.service.user.timezone)} · ${pageLabel(page, all.length)}\n${all.length ? 'Open a habit to inspect or correct a date. Archived habits are included.' : 'No habits yet. Use /habits to create one.'}`)]), ...all.slice(page * listSize, (page + 1) * listSize).map(h => panel([ section(`**${text(h.name)}**\n${h.archived ? 'Archived · ' : ''}Tracked since ${h.createdDate}`, button(c, 'Open history', 'history.detail', { ...state, habitId: h.id })), ], c.service.settings(h.id).mainColor)), ...pages(c, 'history', { ...state, page }, all.length, listSize), ]); } function dateForm(c: Context, data: ControlData): Reply { c.service.owned(data.habitId!); return modal(c, 'Choose history date', 'history.date', data, [{ id: 'date', label: 'Calendar date (YYYY-MM-DD)', value: data.date ?? c.service.today, max: 10 }]); } // Keep the dated editor and all mutation checks in daily.ts. Its opaque payloads // preserve mode/today, so subsequent saves, forms and task pages return here. function withNavigation(c: Context, data: ControlData, reply: Reply): Reply { if (![4, 7].includes(reply.type)) return reply; const d = c.service.day(data.habitId!, data.date!); const state: ControlData = { habitId: data.habitId, date: data.date, mode: 'history', today: false, page: 0 }; const navigation = [ ...(data.date! > '1970-01-01' ? [button(c, 'Previous date', 'history.detail', { ...state, date: addDays(data.date!, -1) })] : []), ...(data.date! < '9998-12-31' ? [button(c, 'Next date', 'history.detail', { ...state, date: addDays(data.date!, 1) })] : []), button(c, 'Change date', 'history.form', state), button(c, 'Choose habit', 'history', { date: data.date }), ]; const explanation = !d.requirements ? 'Read-only · This habit was not tracked yet.' : d.requirements.archived ? 'Read-only · This habit was archived on this date.' : !d.due ? 'Read-only · Day off; nothing is scheduled for this date.' : ''; const body = reply.data as Message; const requirements = d.requirements ? `${d.method === 'manual' ? 'Checkbox' : d.method === 'count' ? 'Count' : 'Tasks'} · ${scheduleLabel(d.requirements.schedule)}\n` : ''; const [card, ...rest] = body.components ?? []; if (card?.type === 17) { const children = card.components as import('./types').DiscordComponent[]; const metadata = `${requirements}${explanation}`.trim(); return { ...reply, data: { ...body, components: [{ ...card, components: [children[0]!, ...(metadata ? [display(metadata)] : []), ...children.slice(1)] }, ...rest, row(...navigation)] } }; } return reply; } export function historyAction(c: Context, action: string, data: ControlData): Reply | undefined { if (action === 'history') return history(c, data); if (action === 'history.select') return dateForm(c, { ...data, habitId: c.interaction.data?.values?.[0], page: 0 }); if (action === 'history.form') return dateForm(c, data); if (action === 'history.date') { const parsed = dateSchema.safeParse(fields(c.interaction).date?.trim()); if (!parsed.success) return screen(c, 'Enter a real calendar date as YYYY-MM-DD, from 1970 through 9998.', [row(button(c, 'Change date', 'history.form', data))]); data = { ...data, date: parsed.data, page: 0 }; action = 'history.detail'; } if (action === 'history.detail' || (data.mode === 'history' && action.startsWith('daily.'))) { const state = { ...data, date: dateSchema.parse(data.date), mode: 'history', today: false }; const reply = action === 'history.detail' ? detail(c, state) : dailyAction(c, action, state); return reply && withNavigation(c, state, reply); } }