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:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user