Four new independent features: /setup (guided onboarding wizard for module/channel/automod-strength config), /lockdown (raid protection with per-channel permission snapshot/restore and join-kick), /task (staff task tracker with status buttons + dashboard view), /embed create (modal-driven embed builder with optional link button). Also fixes the Admin dashboard, which always showed "-" because loadAdminData() double-wrapped the overview response and discarded activity data, plus mismatched field names against adminService's actual shape (guildCount/activeGuilds24/uptimeMs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
import {
|
|
ActionRowBuilder,
|
|
ButtonBuilder,
|
|
ButtonStyle,
|
|
ChatInputCommandInteraction,
|
|
EmbedBuilder,
|
|
PermissionFlagsBits,
|
|
SlashCommandBuilder
|
|
} from 'discord.js';
|
|
import { SlashCommand } from '../../utils/types';
|
|
import { context } from '../../config/context';
|
|
|
|
const STATUS_LABELS: Record<string, string> = { open: 'Offen', in_progress: 'In Bearbeitung', done: 'Erledigt' };
|
|
|
|
export function buildTaskCard(task: { id: string; title: string; description: string | null; status: string; assigneeId: string | null; createdByTag: string }) {
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(task.title)
|
|
.setDescription(task.description || 'Keine Beschreibung')
|
|
.setColor(0xf97316)
|
|
.addFields(
|
|
{ name: 'Status', value: STATUS_LABELS[task.status] || task.status, inline: true },
|
|
{ name: 'Zugewiesen an', value: task.assigneeId ? `<@${task.assigneeId}>` : 'Niemand', inline: true },
|
|
{ name: 'Erstellt von', value: task.createdByTag, inline: true }
|
|
);
|
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder().setCustomId(`task:status:${task.id}:open`).setLabel('Offen').setStyle(task.status === 'open' ? ButtonStyle.Primary : ButtonStyle.Secondary),
|
|
new ButtonBuilder().setCustomId(`task:status:${task.id}:in_progress`).setLabel('In Bearbeitung').setStyle(task.status === 'in_progress' ? ButtonStyle.Primary : ButtonStyle.Secondary),
|
|
new ButtonBuilder().setCustomId(`task:status:${task.id}:done`).setLabel('Erledigt').setStyle(task.status === 'done' ? ButtonStyle.Success : ButtonStyle.Secondary)
|
|
);
|
|
return { embeds: [embed], components: [row] };
|
|
}
|
|
|
|
const command: SlashCommand = {
|
|
guildOnly: true,
|
|
data: new SlashCommandBuilder()
|
|
.setName('task')
|
|
.setDescription('Verwaltet Team-Aufgaben.')
|
|
.addSubcommand((sub) =>
|
|
sub
|
|
.setName('create')
|
|
.setDescription('Erstellt eine neue Team-Aufgabe.')
|
|
.addStringOption((opt) => opt.setName('title').setDescription('Titel der Aufgabe').setRequired(true))
|
|
.addStringOption((opt) => opt.setName('description').setDescription('Beschreibung'))
|
|
.addUserOption((opt) => opt.setName('assignee').setDescription('Zuständige Person'))
|
|
)
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
|
|
async execute(interaction: ChatInputCommandInteraction) {
|
|
if (!interaction.guildId) return;
|
|
const title = interaction.options.getString('title', true);
|
|
const description = interaction.options.getString('description') ?? undefined;
|
|
const assignee = interaction.options.getUser('assignee');
|
|
|
|
const task = await context.tasks.createTask({
|
|
guildId: interaction.guildId,
|
|
title,
|
|
description,
|
|
assigneeId: assignee?.id,
|
|
createdBy: interaction.user.id,
|
|
createdByTag: interaction.user.tag
|
|
});
|
|
|
|
await interaction.reply(buildTaskCard(task));
|
|
}
|
|
};
|
|
|
|
export default command;
|