feat: unanswered ticket badges, dashboard counts, cross-department ticket creation
- Add /tickets/unanswered-counts API endpoint for staff/admin - Show red badge with unanswered count on sidebar nav (staff tickets, admin tickets) - Add summary cards (unanswered/answered/total) to staff tickets page - Highlight unanswered count in admin tickets stats cards - Staff can create tickets to OTHER departments only (not their own) - Frontend hides own department from ticket creation dropdown - Sidebar badges auto-refresh every 30 seconds
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
Request,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
|
||||
@@ -29,8 +30,15 @@ export class TicketsController {
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new ticket (any user)' })
|
||||
@ApiOperation({ summary: 'Create a new ticket' })
|
||||
async create(@Request() req: any, @Body() dto: CreateTicketDto) {
|
||||
// Staff cannot create tickets to their own department
|
||||
if (req.user.role === UserRole.TECHNICAL && dto.department === 'technical') {
|
||||
throw new ForbiddenException('You cannot create a ticket to your own department');
|
||||
}
|
||||
if (req.user.role === UserRole.SALES && dto.department === 'sales') {
|
||||
throw new ForbiddenException('You cannot create a ticket to your own department');
|
||||
}
|
||||
return this.ticketsService.create(req.user.id, req.user.role, dto);
|
||||
}
|
||||
|
||||
@@ -77,6 +85,13 @@ export class TicketsController {
|
||||
return this.ticketsService.getStats();
|
||||
}
|
||||
|
||||
@Get('unanswered-counts')
|
||||
@Roles(UserRole.ADMIN, UserRole.TECHNICAL, UserRole.SALES)
|
||||
@ApiOperation({ summary: 'Get unanswered ticket counts per department (Staff/Admin)' })
|
||||
async getUnansweredCounts() {
|
||||
return this.ticketsService.getUnansweredCounts();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// PARAMETERIZED ROUTES (after static routes)
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
@@ -119,6 +119,19 @@ export class TicketsService {
|
||||
await this.ticketsRepo.update(ticketId, data);
|
||||
}
|
||||
|
||||
// ─── Unanswered ticket counts ───
|
||||
async getUnansweredCounts(): Promise<{ technical: number; sales: number; total: number }> {
|
||||
const unanswered = await this.ticketsRepo.find({
|
||||
where: [
|
||||
{ status: TicketStatus.OPEN },
|
||||
{ status: TicketStatus.WAITING },
|
||||
],
|
||||
});
|
||||
const technical = unanswered.filter((t) => t.department === TicketDepartment.TECHNICAL).length;
|
||||
const sales = unanswered.filter((t) => t.department === TicketDepartment.SALES).length;
|
||||
return { technical, sales, total: technical + sales };
|
||||
}
|
||||
|
||||
// ─── Staff: List department tickets ───
|
||||
async findByDepartment(department: TicketDepartment, status?: TicketStatus): Promise<Ticket[]> {
|
||||
const where: any = { department };
|
||||
|
||||
Reference in New Issue
Block a user