75 lines
4.7 KiB
TypeScript
75 lines
4.7 KiB
TypeScript
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);
|
|
}
|
|
}
|