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:
keyhan
2026-04-06 13:55:15 +03:30
parent a1dbd0a85f
commit a64f8407b9
6 changed files with 123 additions and 23 deletions
+23 -8
View File
@@ -4,6 +4,7 @@ import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';
import api from '@/lib/api';
import { useAuthStore } from '@/lib/store';
import toast from 'react-hot-toast';
import type { Ticket, CreateTicketDto, TicketDepartment, TicketPriority } from '@/types';
@@ -22,10 +23,21 @@ const priorityColors: Record<string, string> = {
export default function TicketsPage() {
const queryClient = useQueryClient();
const user = useAuthStore((s) => s.user);
const [showCreate, setShowCreate] = useState(false);
// Staff can only create tickets to other departments
const availableDepartments: { value: TicketDepartment; label: string }[] = [];
if (user?.role !== 'technical') {
availableDepartments.push({ value: 'technical', label: '🔧 Technical Support' });
}
if (user?.role !== 'sales') {
availableDepartments.push({ value: 'sales', label: '💼 Sales' });
}
const canCreateTicket = availableDepartments.length > 0;
const [form, setForm] = useState<CreateTicketDto>({
subject: '',
department: 'technical',
department: availableDepartments[0]?.value || 'technical',
priority: 'medium',
message: '',
});
@@ -41,7 +53,7 @@ export default function TicketsPage() {
toast.success('Ticket created successfully');
queryClient.invalidateQueries({ queryKey: ['my-tickets'] });
setShowCreate(false);
setForm({ subject: '', department: 'technical', priority: 'medium', message: '' });
setForm({ subject: '', department: availableDepartments[0]?.value || 'technical', priority: 'medium', message: '' });
},
onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to create ticket'),
});
@@ -58,13 +70,15 @@ export default function TicketsPage() {
<h1 className="text-2xl font-bold text-gray-900">My Tickets</h1>
<p className="text-sm text-gray-500 mt-1">Support tickets and their status</p>
</div>
<button onClick={() => setShowCreate(!showCreate)} className="btn-primary">
{showCreate ? '✕ Cancel' : '+ New Ticket'}
</button>
{canCreateTicket && (
<button onClick={() => setShowCreate(!showCreate)} className="btn-primary">
{showCreate ? '✕ Cancel' : '+ New Ticket'}
</button>
)}
</div>
{/* Create Ticket Form */}
{showCreate && (
{showCreate && canCreateTicket && (
<form onSubmit={handleSubmit} className="card p-6 space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Create New Ticket</h2>
@@ -89,8 +103,9 @@ export default function TicketsPage() {
value={form.department}
onChange={(e) => setForm({ ...form, department: e.target.value as TicketDepartment })}
>
<option value="technical">🔧 Technical Support</option>
<option value="sales">💼 Sales</option>
{availableDepartments.map((dept) => (
<option key={dept.value} value={dept.value}>{dept.label}</option>
))}
</select>
</div>
<div>