diff --git a/frontend/src/app/[lang]/dashboard/admin/tickets/page.tsx b/frontend/src/app/[lang]/dashboard/admin/tickets/page.tsx index 4c3df94..b06d053 100644 --- a/frontend/src/app/[lang]/dashboard/admin/tickets/page.tsx +++ b/frontend/src/app/[lang]/dashboard/admin/tickets/page.tsx @@ -2,7 +2,8 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import Link from 'next/link'; +import { Link } from '@/i18n/Link'; +import { useT, useLocale } from '@/i18n/I18nProvider'; import api from '@/lib/api'; import type { Ticket, TicketStats, TicketDepartment, TicketStatus } from '@/types'; import { ClipboardList, Wrench, Briefcase, User, Inbox } from 'lucide-react'; @@ -21,6 +22,9 @@ const priorityColors: Record = { }; export default function AdminTicketsPage() { + const t = useT(); + const tk = t.dashboard.tickets; + const locale = useLocale(); const [deptFilter, setDeptFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); @@ -41,46 +45,46 @@ export default function AdminTicketsPage() { }); const formatResponseTime = (minutes: number) => { - if (minutes < 60) return `${minutes} min`; - if (minutes < 1440) return `${Math.round(minutes / 60)} hours`; - return `${Math.round(minutes / 1440)} days`; + if (minutes < 60) return tk.minutes.replace('{n}', String(minutes)); + if (minutes < 1440) return tk.hours.replace('{n}', String(Math.round(minutes / 60))); + return tk.days.replace('{n}', String(Math.round(minutes / 1440))); }; return (
-

All Tickets

-

Overview of all support tickets across departments

+

{tk.allTickets}

+

{tk.allTicketsSubtitle}

{/* Stats Cards */} {stats && (
-

Total Tickets

+

{tk.totalTicketsStat}

{stats.totalTickets}

-

Unanswered

+

{tk.unanswered}

0 ? 'text-red-600' : 'text-green-600'}`}> {stats.openTickets}

-

Need staff response

+

{tk.needStaffResponse}

-

Avg Response Time

+

{tk.avgResponseTime}

{stats.avgResponseTimeMinutes > 0 ? formatResponseTime(stats.avgResponseTimeMinutes) : '—'}

-

By Department

+

{tk.byDepartment}

{Object.entries(stats.byDepartment).map(([dept, data]) => (
- {dept === 'technical' ? : } {dept} + {dept === 'technical' ? : } {(tk.department as Record)[dept] ?? dept} - 0 ? 'text-red-600' : 'text-green-600'}>{data.open} unanswered + 0 ? 'text-red-600' : 'text-green-600'}>{data.open} {tk.unanswered} / {data.total}
@@ -93,7 +97,7 @@ export default function AdminTicketsPage() { {/* Filters */}
- Department: + {tk.departmentLabel} {(['' as const, 'technical' as TicketDepartment, 'sales' as TicketDepartment]).map((dept) => ( ))}
- Status: + {tk.statusLabel} {(['', 'open', 'waiting', 'answered', 'closed'] as const).map((status) => ( ))}
@@ -134,7 +138,7 @@ export default function AdminTicketsPage() { ) : tickets.length === 0 ? (
-

No tickets found

+

{tk.noTicketsFound}

) : (
@@ -149,13 +153,13 @@ export default function AdminTicketsPage() {

{ticket.subject}

- {ticket.status} + {(tk.status as Record)[ticket.status] ?? ticket.status} - {ticket.priority} + {(tk.priority as Record)[ticket.priority] ?? ticket.priority} - {ticket.department === 'technical' ? : } {ticket.department === 'technical' ? 'Technical' : 'Sales'} + {ticket.department === 'technical' ? : } {(tk.department as Record)[ticket.department] ?? ticket.department}
@@ -165,12 +169,12 @@ export default function AdminTicketsPage() { )} - {new Date(ticket.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} + {new Date(ticket.createdAt).toLocaleDateString(locale, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} - {ticket.messages?.length || 0} msg{(ticket.messages?.length || 0) !== 1 ? 's' : ''} + {tk.msg.replace('{n}', String(ticket.messages?.length || 0))}
- +
))} diff --git a/frontend/src/app/[lang]/dashboard/staff/tickets/page.tsx b/frontend/src/app/[lang]/dashboard/staff/tickets/page.tsx index f7d1d18..3852385 100644 --- a/frontend/src/app/[lang]/dashboard/staff/tickets/page.tsx +++ b/frontend/src/app/[lang]/dashboard/staff/tickets/page.tsx @@ -2,7 +2,8 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import Link from 'next/link'; +import { Link } from '@/i18n/Link'; +import { useT, useLocale } from '@/i18n/I18nProvider'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; import type { Ticket, TicketStatus } from '@/types'; @@ -22,12 +23,15 @@ const priorityColors: Record = { }; export default function StaffTicketsPage() { + const t = useT(); + const tk = t.dashboard.tickets; + const locale = useLocale(); const user = useAuthStore((s) => s.user); const [statusFilter, setStatusFilter] = useState(''); // Determine which department this staff member handles const department = user?.role === 'sales' ? 'sales' : 'technical'; - const departmentLabel = department === 'technical' ? 'Technical' : 'Sales'; + const departmentLabel = department === 'technical' ? tk.department.technical : tk.department.sales; const DeptIcon = department === 'technical' ? Wrench : Briefcase; const { data: tickets = [], isLoading } = useQuery({ @@ -44,32 +48,30 @@ export default function StaffTicketsPage() { return (
-

{departmentLabel} Tickets

-

- {tickets.length} total tickets -

+

{tk.ticketsTitle.replace('{dept}', departmentLabel)}

+

{tk.totalTickets.replace('{n}', String(tickets.length))}

{/* Summary Cards */}
-

Unanswered

+

{tk.unanswered}

0 ? 'text-red-600' : 'text-green-600'}`}> {unansweredCount}

-

Need response

+

{tk.needResponse}

-

Answered

+

{tk.answered}

{answeredCount}

-

Waiting for user

+

{tk.waitingForUser}

-

Total Open

+

{tk.totalOpen}

{tickets.filter((t) => t.status !== 'closed').length}

-

Excluding closed

+

{tk.excludingClosed}

@@ -85,7 +87,7 @@ export default function StaffTicketsPage() { : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }`} > - {status === '' ? 'All' : status.charAt(0).toUpperCase() + status.slice(1)} + {status === '' ? tk.all : (tk.status as Record)[status]} ))}
@@ -98,9 +100,11 @@ export default function StaffTicketsPage() { ) : tickets.length === 0 ? (
-

No tickets

+

{tk.noTicketsStaff}

- {statusFilter ? `No ${statusFilter} tickets` : 'No tickets in this department'} + {statusFilter + ? tk.noStatusTickets.replace('{status}', (tk.status as Record)[statusFilter]) + : tk.noDeptTickets}

) : ( @@ -116,10 +120,10 @@ export default function StaffTicketsPage() {

{ticket.subject}

- {ticket.status} + {(tk.status as Record)[ticket.status] ?? ticket.status} - {ticket.priority} + {(tk.priority as Record)[ticket.priority] ?? ticket.priority}
@@ -129,12 +133,12 @@ export default function StaffTicketsPage() { )} - {new Date(ticket.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} + {new Date(ticket.createdAt).toLocaleDateString(locale, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} - {ticket.messages?.length || 0} msg{(ticket.messages?.length || 0) !== 1 ? 's' : ''} + {tk.msg.replace('{n}', String(ticket.messages?.length || 0))}
- +
))} diff --git a/frontend/src/app/[lang]/dashboard/tickets/[id]/page.tsx b/frontend/src/app/[lang]/dashboard/tickets/[id]/page.tsx index b9dbaf6..dc0adb9 100644 --- a/frontend/src/app/[lang]/dashboard/tickets/[id]/page.tsx +++ b/frontend/src/app/[lang]/dashboard/tickets/[id]/page.tsx @@ -2,7 +2,9 @@ import { useState, useRef, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams } from 'next/navigation'; +import { useLocalizedRouter } from '@/i18n/navigation'; +import { useT, useLocale } from '@/i18n/I18nProvider'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; import { toast } from 'react-toastify'; @@ -17,8 +19,11 @@ const statusColors: Record = { }; export default function TicketDetailPage() { + const t = useT(); + const tk = t.dashboard.tickets; + const locale = useLocale(); const { id } = useParams(); - const router = useRouter(); + const router = useLocalizedRouter(); const queryClient = useQueryClient(); const user = useAuthStore((s) => s.user); const [reply, setReply] = useState(''); @@ -35,16 +40,16 @@ export default function TicketDetailPage() { onSuccess: () => { setReply(''); queryClient.invalidateQueries({ queryKey: ['ticket', id] }); - toast.success('Reply sent'); + toast.success(tk.replySent); }, - onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to send reply'), + onError: (err: any) => toast.error(err.response?.data?.message || tk.replyFailed), }); const closeMutation = useMutation({ mutationFn: () => api.patch(`/tickets/${id}/close`).then((r) => r.data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ticket', id] }); - toast.success('Ticket closed'); + toast.success(tk.ticketClosed); }, }); @@ -68,7 +73,7 @@ export default function TicketDetailPage() { } if (!ticket) { - return
Ticket not found
; + return
{tk.notFound}
; } return ( @@ -77,22 +82,22 @@ export default function TicketDetailPage() {

{ticket.subject}

- {ticket.status} + {(tk.status as Record)[ticket.status] ?? ticket.status} - {ticket.department === 'technical' ? <> Technical : <> Sales} + {ticket.department === 'technical' ? <> {tk.department.technical} : <> {tk.department.sales}} - {new Date(ticket.createdAt).toLocaleString()} + {new Date(ticket.createdAt).toLocaleString(locale)} {ticket.user && ( - by {ticket.user.firstName} {ticket.user.lastName} + {tk.by} {ticket.user.firstName} {ticket.user.lastName} )}
@@ -103,7 +108,7 @@ export default function TicketDetailPage() { className="btn-ghost text-sm text-red-600 hover:text-red-700 hover:bg-red-50" disabled={closeMutation.isPending} > - Close Ticket + {tk.closeTicket} )}
@@ -124,17 +129,17 @@ export default function TicketDetailPage() { }`}> {!isMe && (

- {msg.sender ? `${msg.sender.firstName} ${msg.sender.lastName}` : 'Unknown'} + {msg.sender ? `${msg.sender.firstName} ${msg.sender.lastName}` : tk.unknown} {isStaff && ( - - {msg.senderRole === 'admin' ? 'Admin' : msg.senderRole === 'technical' ? 'Technical' : 'Sales'} + + {(tk.role as Record)[msg.senderRole] ?? msg.senderRole} )}

)}

{msg.message}

- {new Date(msg.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + {new Date(msg.createdAt).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' })}

@@ -149,7 +154,7 @@ export default function TicketDetailPage() {