diff --git a/services/arbiter-3.0/src/discord/tasks.js b/services/arbiter-3.0/src/discord/tasks.js index b45ae0a..eb66e98 100644 --- a/services/arbiter-3.0/src/discord/tasks.js +++ b/services/arbiter-3.0/src/discord/tasks.js @@ -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 };