From bf3d333408a032c93b24c37b56a807da6a8bf30c Mon Sep 17 00:00:00 2001 From: LucasB25 <50886682+LucasB25@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:50:21 +0200 Subject: [PATCH] v0.26.6 - Add Ratings ticket - Add new cmd ticketstats --- package.json | 2 +- prisma/aikouticket.db | Bin 24576 -> 32768 bytes prisma/example.mongodb.schema.prisma | 5 ++ prisma/example.postgresql.schema.prisma | 5 ++ prisma/schema.prisma | 5 ++ src/commands/tickets/Ticketstats.ts | 58 ++++++++++++++++++++++++ src/database/server.ts | 38 +++++++++++++++- src/events/client/InteractionCreate.ts | 58 +++++++++++++++++++++++- src/utils/TicketManager.ts | 1 + 9 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 src/commands/tickets/Ticketstats.ts diff --git a/package.json b/package.json index 6a4f91a..29b11a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "AikouTicket", - "version": "0.25.5", + "version": "0.26.6", "description": "A simple AikouTicket bot for discord", "type": "module", "main": "dist/index.js", diff --git a/prisma/aikouticket.db b/prisma/aikouticket.db index 805ef44d65355db0ada2af8f7b21f01ec03a076e..788a2850d75e17b1aeb77fca13836ea28f18017c 100644 GIT binary patch delta 588 zcmZoTz}V2hG(lQWf`Ng71BhXOWulI;v;>1*+ACiE9}Mg~OBwhz`S@m~Z5(M6yUNBQ-|;VMx) L#v}$e8N~zu;3 { + try { + const allTicketStats = await this.client.db.getAllTicketStats(); + + if (allTicketStats.length === 0) { + await ctx.sendMessage({ content: "No ticket statistics available." }); + return; + } + + const totalRatings = allTicketStats.reduce((sum, stat) => sum + stat.rating, 0); + const averageRating = totalRatings / allTicketStats.length; + + const embed = new EmbedBuilder() + .setTitle("Ticket Statistics") + .setColor("#FFD700") + .addFields( + { name: "Total Tickets", value: `${allTicketStats.length}`, inline: true }, + { name: "Total Ratings", value: `${totalRatings}`, inline: true }, + { name: "Average Rating", value: `${averageRating.toFixed(2)}`, inline: true }, + ) + .setTimestamp(); + + await ctx.sendMessage({ embeds: [embed] }); + } catch (error) { + this.client.logger.error(`Failed to retrieve ticket statistics: ${error.message}`); + await ctx.sendMessage({ content: "There was an error retrieving the ticket statistics." }); + } + } +} diff --git a/src/database/server.ts b/src/database/server.ts index 75b2059..dea756d 100644 --- a/src/database/server.ts +++ b/src/database/server.ts @@ -1,4 +1,4 @@ -import { PrismaClient, type ticketsGuild, type ticketsInfo } from "@prisma/client"; +import { PrismaClient, type ticketsGuild, type ticketsInfo, type ticketStats } from "@prisma/client"; export default class ServerData { private prisma: PrismaClient; @@ -70,4 +70,40 @@ export default class ServerData { return []; }); } + + public async getTicketStats(channelId: string): Promise { + return await this.prisma.ticketStats.findUnique({ where: { channelId } }); + } + + public async saveTicketStats(channelId: string, rating: number = 0): Promise { + await this.prisma.ticketStats + .upsert({ + where: { channelId }, + update: { rating }, + create: { channelId, rating }, + }) + .catch((error) => console.error("Error saving ticket stats:", error)); + } + + public async updateTicketStats(channelId: string, rating: number): Promise { + await this.prisma.ticketStats + .update({ + where: { channelId }, + data: { rating }, + }) + .catch((error) => console.error(`Error updating ticket stats for channel ${channelId}:`, error)); + } + + public async deleteTicketStats(channelId: string): Promise { + await this.prisma.ticketStats + .delete({ where: { channelId } }) + .catch((error) => console.error("Error deleting ticket stats:", error)); + } + + public async getAllTicketStats(): Promise { + return this.prisma.ticketStats.findMany().catch((error) => { + console.error("Error retrieving all ticket stats:", error); + return []; + }); + } } diff --git a/src/events/client/InteractionCreate.ts b/src/events/client/InteractionCreate.ts index 1159c88..bb6094e 100644 --- a/src/events/client/InteractionCreate.ts +++ b/src/events/client/InteractionCreate.ts @@ -29,6 +29,8 @@ export default class InteractionCreate extends Event { await this.handleSelectMenuInteraction(interaction); } else if (interaction.isButton()) { await this.handleButtonInteraction(interaction); + } else if (interaction.isStringSelectMenu() && interaction.customId.startsWith("ratingMenu-")) { + await this.handleRatingSelectMenu(interaction); } } catch (error) { this.client.logger.error(`Failed to handle interaction: ${error.message}`); @@ -259,6 +261,7 @@ export default class InteractionCreate extends Event { const creator = interaction.guild.members.cache.find((member) => member.user.username === ticket.creator); if (enableNotifyTicketCreator && creator) { await this.notifyTicketCreator(interaction, creator, reason, ticketChannel); + await this.sendRatingMenu(creator, ticketChannel); } else if (!creator) { this.client.logger.error(`Failed to find creator of ticket ${ticketChannel.id}.`); } @@ -298,6 +301,7 @@ export default class InteractionCreate extends Event { const creator = interaction.guild.members.cache.find((member) => member.user.username === ticket.creator); if (enableNotifyTicketCreator && creator) { await this.notifyTicketCreator(interaction, creator, reason, ticketChannel); + await this.sendRatingMenu(creator, ticketChannel); } else if (!creator) { this.client.logger.error(`Failed to find creator of ticket ${ticketChannel.id}.`); } @@ -347,13 +351,65 @@ export default class InteractionCreate extends Event { .setThumbnail(interaction.guild.iconURL({ format: "png", size: 1024 })) .setFooter({ text: "Ticket System", iconURL: interaction.user.displayAvatarURL({ extension: "png", size: 1024 }) }) .setTimestamp(); - await user.send({ embeds: [embed] }); } catch (error) { this.client.logger.error("Failed to send DM to ticket creator:", error); } } + private async sendRatingMenu(user: any, ticketChannel: TextChannel): Promise { + const ratingMenu = new StringSelectMenuBuilder() + .setCustomId(`ratingMenu-${ticketChannel.id}`) + .setPlaceholder("Rate your support ticket experience") + .addOptions( + { label: "⭐ 1 Star", value: "1" }, + { label: "⭐⭐ 2 Stars", value: "2" }, + { label: "⭐⭐⭐ 3 Stars", value: "3" }, + { label: "⭐⭐⭐⭐ 4 Stars", value: "4" }, + { label: "⭐⭐⭐⭐⭐ 5 Stars", value: "5" }, + ); + + const row = new ActionRowBuilder().addComponents(ratingMenu); + + const embed = new EmbedBuilder() + .setColor("#FFD700") + .setTitle("Rate Your Ticket Experience") + .setDescription("Please rate your experience with our support team by selecting a rating below."); + + try { + await user.send({ embeds: [embed], components: [row] }); + } catch (error) { + this.client.logger.error("Failed to send rating menu:", error); + } + } + + private async handleRatingSelectMenu(interaction: SelectMenuInteraction): Promise { + const channelId = interaction.customId.split("-")[1]; + const rating = parseInt(interaction.values[0], 10); + + if (isNaN(rating) || rating < 1 || rating > 5) { + await interaction.reply({ + content: "Invalid rating. Please select a rating between 1 and 5 stars.", + ephemeral: true, + }); + return; + } + + try { + await this.client.db.updateTicketStats(channelId, rating); + await interaction.reply({ + content: "Thank you for rating your support ticket experience!", + ephemeral: true, + }); + } catch (error) { + this.client.logger.error("Failed to save ticket rating:", error); + await interaction.reply({ + content: `An error occurred: ${error.message}`, + ephemeral: true, + }); + } + } + private async handleTranscriptTicketButton(interaction: any): Promise { const ticketChannel = interaction.channel; await LogsManager.logTicketTranscript(interaction, this.client, ticketChannel); diff --git a/src/utils/TicketManager.ts b/src/utils/TicketManager.ts index 0ef4f98..10ea0c9 100644 --- a/src/utils/TicketManager.ts +++ b/src/utils/TicketManager.ts @@ -90,6 +90,7 @@ export class TicketManager { await LogsManager.logTicketCreation(interaction, categoryLabel, client, channel); await client.db.saveTicketInfo(channel.id, userName); + await client.db.saveTicketStats(channel.id); return channel; } catch (error) {