feat: /tasks detail view + In Progress button

- /tasks number:26 shows full task detail embed
  (description, tags, status, priority, owner, dates)
- Mark Done, In Progress, and Take Task buttons on detail view
- task_progress_ button handler sets status to in_progress

Chronicler #78 | firefrost-services
This commit is contained in:
Claude (Chronicler #78)
2026-04-11 14:13:16 +00:00
parent 48f74e8658
commit 78179de2bc

View File

@@ -49,6 +49,10 @@ const tasksCommand = new SlashCommandBuilder()
{ name: 'Done', value: 'done' },
{ name: 'Everything', value: 'all' }
)
)
.addIntegerOption(option =>
option.setName('number')
.setDescription('View a specific task by number (e.g. 26)')
);
function isStaff(member) {
@@ -123,6 +127,84 @@ async function buildTaskEmbed(tasks, filterLabel) {
return { embed, rows };
}
async function handleTaskDetail(interaction, taskNumber) {
try {
const result = await db.query(
'SELECT * FROM tasks WHERE task_number = $1',
[taskNumber]
);
if (result.rows.length === 0) {
return interaction.editReply(`❌ Task #${taskNumber} not found.`);
}
const t = result.rows[0];
const pri = PRIORITY_EMOJI[t.priority] || '⚪';
const sta = STATUS_EMOJI[t.status] || '⬡';
const embed = new EmbedBuilder()
.setTitle(`${sta} #${t.task_number}${t.title}`)
.setColor(
t.status === 'done' ? 0x22c55e :
t.status === 'blocked' ? 0xef4444 :
t.priority === 'critical' ? 0xef4444 :
t.priority === 'high' ? 0xff6b35 :
0x4ECDC4
)
.addFields(
{ name: 'Status', value: `${sta} ${t.status}`, inline: true },
{ name: 'Priority', value: `${pri} ${t.priority}`, inline: true },
{ name: 'Owner', value: t.owner || 'unassigned', inline: true }
)
.setTimestamp(new Date(t.updated_at));
if (t.description) {
embed.setDescription(t.description.length > 400 ? t.description.substring(0, 400) + '...' : t.description);
}
if (t.tags && t.tags.length > 0) {
embed.addFields({ name: 'Tags', value: t.tags.map(tag => `\`${tag}\``).join(' '), inline: false });
}
if (t.completed_at) {
const completedDate = new Date(t.completed_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
embed.addFields({ name: 'Completed', value: `${completedDate}${t.completed_by ? ` by ${t.completed_by}` : ''}`, inline: false });
}
embed.setFooter({ text: `Created ${new Date(t.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} · Updated ${new Date(t.updated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}` });
// Action buttons for active tasks
const rows = [];
if (t.status !== 'done' && t.status !== 'obsolete') {
const buttons = [
new ButtonBuilder()
.setCustomId(`task_done_${t.id}`)
.setLabel('Mark Done')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId(`task_progress_${t.id}`)
.setLabel('In Progress')
.setStyle(ButtonStyle.Primary)
];
if (t.owner === 'unassigned') {
buttons.push(
new ButtonBuilder()
.setCustomId(`task_take_${t.id}`)
.setLabel('Take Task')
.setStyle(ButtonStyle.Secondary)
);
}
rows.push(new ActionRowBuilder().addComponents(buttons));
}
await interaction.editReply({ embeds: [embed], components: rows });
} catch (err) {
console.error('Task detail error:', err);
await interaction.editReply('❌ Error loading task details.');
}
}
async function handleTasksCommand(interaction) {
if (!isStaff(interaction.member)) {
return interaction.reply({
@@ -133,6 +215,12 @@ async function handleTasksCommand(interaction) {
await interaction.deferReply();
// Check if viewing a specific task
const taskNumber = interaction.options.getInteger('number');
if (taskNumber) {
return handleTaskDetail(interaction, taskNumber);
}
const filter = interaction.options.getString('filter') || 'open';
try {
@@ -245,6 +333,32 @@ async function handleTaskButton(interaction) {
await interaction.reply({ content: '❌ Error updating task.', ephemeral: true });
}
}
if (customId.startsWith('task_progress_')) {
const taskId = customId.replace('task_progress_', '');
const owner = ownerFromDiscord(interaction.member);
try {
const result = await db.query(
`UPDATE tasks SET status = 'in_progress', owner = $1, updated_at = NOW()
WHERE id = $2 RETURNING task_number, title`,
[owner, taskId]
);
if (result.rows.length > 0) {
const t = result.rows[0];
await interaction.reply({
content: `🔄 **#${t.task_number}${t.title}** marked in progress by ${owner}!`,
ephemeral: false
});
} else {
await interaction.reply({ content: '❌ Task not found.', ephemeral: true });
}
} catch (err) {
console.error('Task progress error:', err);
await interaction.reply({ content: '❌ Error updating task.', ephemeral: true });
}
}
}
module.exports = { tasksCommand, handleTasksCommand, handleTaskButton };