feat(257): add Revert Status + Assigned to me filter

This commit is contained in:
2026-04-01 07:54:56 +00:00
parent b212bb1d36
commit 3b7057a294

View File

@@ -2,7 +2,8 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { RefreshCw, Ticket, Plus, Send } from "lucide-react";
import { RefreshCw, Ticket, Plus, Send, RotateCcw, User } from "lucide-react";
import { useAuth } from "@/contexts/AuthContext";
import { Card, CardContent, CardHeader } from "@/components/ui/Card";
import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table";
import { Badge } from "@/components/ui/Badge";
@@ -34,11 +35,20 @@ const priorityVariant: Record<string, "danger" | "warning" | "muted"> = {
const statusFilters = ["", "OPEN", "IN_PROGRESS", "RESOLVED", "CLOSED"];
const typeFilters = ["", "SUPPORT", "BILLING", "INSTALLATION"];
// Previous status mapping (revert flow)
const prevStatus: Record<string, string> = {
CLOSED: "RESOLVED",
RESOLVED: "IN_PROGRESS",
IN_PROGRESS: "OPEN",
};
export default function TicketsPage() {
const qc = useQueryClient();
const { user } = useAuth();
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState("");
const [typeFilter, setTypeFilter] = useState("");
const [assignedToMe, setAssignedToMe] = useState(false);
const [page, setPage] = useState(1);
const [selected, setSelected] = useState<TicketItem | null>(null);
const [showCreate, setShowCreate] = useState(false);
@@ -46,12 +56,13 @@ export default function TicketsPage() {
const [newForm, setNewForm] = useState({ subject: "", description: "", type: "SUPPORT", priority: "NORMAL", clientSearch: "", clientId: "" });
const { data, isLoading, refetch } = useQuery<PaginatedResponse<TicketItem>>({
queryKey: ["tickets", search, statusFilter, typeFilter, page],
queryKey: ["tickets", search, statusFilter, typeFilter, assignedToMe, page],
queryFn: async () => {
const params = new URLSearchParams({ page: String(page), limit: "20" });
if (search) params.set("search", search);
if (statusFilter) params.set("status", statusFilter);
if (typeFilter) params.set("type", typeFilter);
if (assignedToMe && user?.id) params.set("assignedToId", user.id);
const res = await api.get<PaginatedResponse<TicketItem>>(`/api/v1/tickets?${params}`);
return res.data;
},
@@ -109,6 +120,14 @@ export default function TicketsPage() {
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create ticket"),
});
const revertStatus = useMutation({
mutationFn: async ({ id, status }: { id: string; status: string }) => {
await api.patch(`/api/v1/tickets/${id}`, { status });
},
onSuccess: () => { toast.success("Status reverted"); refetch(); refetchDetail(); qc.invalidateQueries({ queryKey: ["ticket"] }); },
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed"),
});
const tickets = data?.data ?? [];
const total = data?.meta?.total ?? 0;
const detail = ticketDetail ?? selected;
@@ -150,6 +169,13 @@ export default function TicketsPage() {
</button>
))}
</div>
<button
onClick={() => { setAssignedToMe(v => !v); setPage(1); }}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${assignedToMe ? "bg-green-600 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`}
title="Show only tickets assigned to me"
>
<User size={12} /> Assigned to me
</button>
</div>
</CardHeader>
<CardContent className="p-0">
@@ -203,11 +229,18 @@ export default function TicketsPage() {
</div>
{/* Status actions */}
<div className="flex gap-2 flex-wrap">
{nextStatus[detail.status] && (
<Button size="sm" onClick={() => updateStatus.mutate({ id: detail.id, status: nextStatus[detail.status] })} isLoading={updateStatus.isPending}>
Move to {nextStatus[detail.status].replace("_", " ")}
</Button>
)}
{prevStatus[detail.status] && (
<Button size="sm" variant="outline" onClick={() => revertStatus.mutate({ id: detail.id, status: prevStatus[detail.status] })} isLoading={revertStatus.isPending}>
<RotateCcw size={13} className="mr-1" /> Revert to {prevStatus[detail.status].replace("_", " ")}
</Button>
)}
</div>
{/* Comments */}
<div>