feat(bot): add reminder menu views (list, detail, wizard steps)
This commit is contained in:
parent
afd5fcb73b
commit
1578f1f948
@ -1,6 +1,8 @@
|
|||||||
import { InlineKeyboard } from "grammy";
|
import { InlineKeyboard } from "grammy";
|
||||||
import { db } from "../db.js";
|
import { db } from "../db.js";
|
||||||
import { sessionManager } from "../whatsapp/session-manager.js";
|
import { sessionManager } from "../whatsapp/session-manager.js";
|
||||||
|
import { listRemindersForOperator, getReminderWithDetails } from "../reminders/crud.js";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
// BotFather-style navigation: every leaf has a way home, every branch shows
|
// BotFather-style navigation: every leaf has a way home, every branch shows
|
||||||
// you where you are. All callbacks edit the same message.
|
// you where you are. All callbacks edit the same message.
|
||||||
@ -24,8 +26,9 @@ export type MenuView = {
|
|||||||
export function mainMenu(): MenuView {
|
export function mainMenu(): MenuView {
|
||||||
const keyboard = new InlineKeyboard()
|
const keyboard = new InlineKeyboard()
|
||||||
.text("📒 Accounts", "m:accounts")
|
.text("📒 Accounts", "m:accounts")
|
||||||
.text("📡 Pair New", "m:pair")
|
.text("📅 Reminders", "m:reminders")
|
||||||
.row()
|
.row()
|
||||||
|
.text("📡 Pair New", "m:pair")
|
||||||
.text("❓ Help", "m:help");
|
.text("❓ Help", "m:help");
|
||||||
return {
|
return {
|
||||||
text:
|
text:
|
||||||
@ -246,3 +249,138 @@ export function unpairDoneMenu(label: string): MenuView {
|
|||||||
keyboard,
|
keyboard,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function remindersMenu(operatorId: string, operatorTimezone: string): Promise<MenuView> {
|
||||||
|
const list = await listRemindersForOperator(operatorId, 30);
|
||||||
|
const keyboard = new InlineKeyboard();
|
||||||
|
for (const r of list) {
|
||||||
|
const when = r.scheduledAt
|
||||||
|
? DateTime.fromJSDate(r.scheduledAt).setZone(operatorTimezone).toFormat("dd MMM HH:mm")
|
||||||
|
: "—";
|
||||||
|
const label = `${r.status === "active" ? "🟢" : r.status === "ended" ? "⚪" : "⏸"} ${r.name} · ${when}`;
|
||||||
|
keyboard.text(label.slice(0, 60), `rm:${r.id}`).row();
|
||||||
|
}
|
||||||
|
keyboard.text("➕ New Reminder", "rm:new").row().text("⬅ Main Menu", "m:main");
|
||||||
|
if (list.length === 0) {
|
||||||
|
return {
|
||||||
|
text: "📅 *Reminders*\n\nYou haven't created any reminders yet.",
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
text: `📅 *Reminders* (${list.length})\n\nTap one to view, or *➕ New* to schedule a fresh one.`,
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reminderDetailMenu(
|
||||||
|
reminderId: string,
|
||||||
|
operatorTimezone: string,
|
||||||
|
): Promise<MenuView | null> {
|
||||||
|
const rem = await getReminderWithDetails(reminderId);
|
||||||
|
if (!rem) return null;
|
||||||
|
const when = rem.scheduledAt
|
||||||
|
? DateTime.fromJSDate(rem.scheduledAt).setZone(operatorTimezone).toFormat("yyyy-MM-dd HH:mm")
|
||||||
|
: "—";
|
||||||
|
const messagePreview = rem.messages
|
||||||
|
.map((m) => (m.kind === "text" ? m.textContent : `[${m.kind}]`))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const keyboard = new InlineKeyboard()
|
||||||
|
.text("🗑 Delete", `rm_del:${reminderId}`)
|
||||||
|
.row()
|
||||||
|
.text("⬅ Reminders", "m:reminders")
|
||||||
|
.text("⬅ Main Menu", "m:main");
|
||||||
|
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
`📅 *${rem.name}*\n\n` +
|
||||||
|
`When: \`${when}\` (${rem.timezone})\n` +
|
||||||
|
`Status: \`${rem.status}\`\n` +
|
||||||
|
`Targets: ${rem.targets.length}\n\n` +
|
||||||
|
`*Body:*\n${messagePreview || "(empty)"}`,
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reminderPickAccountMenu(
|
||||||
|
accounts: { id: string; label: string; phoneNumber: string | null }[],
|
||||||
|
): MenuView {
|
||||||
|
const keyboard = new InlineKeyboard();
|
||||||
|
for (const a of accounts) {
|
||||||
|
const phone = a.phoneNumber ? ` (+${a.phoneNumber})` : "";
|
||||||
|
keyboard.text(`📒 ${a.label}${phone}`, `rm_acc:${a.id}`).row();
|
||||||
|
}
|
||||||
|
keyboard.text("⬅ Cancel", "m:reminders");
|
||||||
|
return {
|
||||||
|
text: "➕ *New Reminder — Step 1 / 4*\n\nWhich WhatsApp account should send it?",
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reminderPickGroupMenu(
|
||||||
|
groups: { id: string; name: string }[],
|
||||||
|
): MenuView {
|
||||||
|
const keyboard = new InlineKeyboard();
|
||||||
|
for (const g of groups.slice(0, 30)) {
|
||||||
|
const name = g.name.length > 32 ? `${g.name.slice(0, 31)}…` : g.name;
|
||||||
|
keyboard.text(`👥 ${name}`, `rm_grp:${g.id}`).row();
|
||||||
|
}
|
||||||
|
keyboard.text("⬅ Cancel", "m:reminders");
|
||||||
|
return {
|
||||||
|
text: "➕ *New Reminder — Step 2 / 4*\n\nWhich group?",
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reminderComposeMenu(): MenuView {
|
||||||
|
const keyboard = new InlineKeyboard().text("⬅ Cancel", "m:reminders");
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
"➕ *New Reminder — Step 3 / 4*\n\n" +
|
||||||
|
"Send the message body now — text, photo, video, or document.\n\n" +
|
||||||
|
"Reply to *this* message with what you want sent. " +
|
||||||
|
"If you send media with a caption, the caption is included.",
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reminderTimeMenu(): MenuView {
|
||||||
|
const keyboard = new InlineKeyboard()
|
||||||
|
.text("🕐 In 1 hour", "rm_t:in_1h")
|
||||||
|
.text("🕒 In 3 hours", "rm_t:in_3h")
|
||||||
|
.row()
|
||||||
|
.text("🌅 Tomorrow 9 AM", "rm_t:tomorrow_9am")
|
||||||
|
.text("📅 Next Mon 9 AM", "rm_t:next_mon_9am")
|
||||||
|
.row()
|
||||||
|
.text("⌨️ Custom date/time", "rm_t:custom")
|
||||||
|
.row()
|
||||||
|
.text("⬅ Cancel", "m:reminders");
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
"➕ *New Reminder — Step 4 / 4*\n\n" +
|
||||||
|
"When should it fire? Pick a quick option or type a date/time.",
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reminderConfirmMenu(summary: {
|
||||||
|
accountLabel: string;
|
||||||
|
groupName: string;
|
||||||
|
body: string;
|
||||||
|
whenLocal: string;
|
||||||
|
}): MenuView {
|
||||||
|
const keyboard = new InlineKeyboard()
|
||||||
|
.text("✅ Schedule", "rm_save")
|
||||||
|
.text("⬅ Cancel", "m:reminders");
|
||||||
|
return {
|
||||||
|
text:
|
||||||
|
"*Review*\n\n" +
|
||||||
|
`Account: ${summary.accountLabel}\n` +
|
||||||
|
`Group: ${summary.groupName}\n` +
|
||||||
|
`When: ${summary.whenLocal}\n\n` +
|
||||||
|
`*Body:*\n${summary.body}`,
|
||||||
|
keyboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user