feat(257,253): revert status, assign-to-me, map pin, activate client
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { RefreshCw, Ticket, Plus, Send, RotateCcw, User } from "lucide-react";
|
import { RefreshCw, Ticket, Plus, Send, RotateCcw, User, MapPin, Zap } from "lucide-react";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/Card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/Card";
|
||||||
import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table";
|
import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table";
|
||||||
@@ -120,6 +120,14 @@ export default function TicketsPage() {
|
|||||||
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create ticket"),
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create ticket"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const activateClient = useMutation({
|
||||||
|
mutationFn: async (clientId: string) => {
|
||||||
|
await api.patch(`/api/v1/clients/${clientId}`, { isActive: true });
|
||||||
|
},
|
||||||
|
onSuccess: () => { toast.success("Client activated! 🎉"); refetch(); refetchDetail(); },
|
||||||
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to activate client"),
|
||||||
|
});
|
||||||
|
|
||||||
const revertStatus = useMutation({
|
const revertStatus = useMutation({
|
||||||
mutationFn: async ({ id, status }: { id: string; status: string }) => {
|
mutationFn: async ({ id, status }: { id: string; status: string }) => {
|
||||||
await api.patch(`/api/v1/tickets/${id}`, { status });
|
await api.patch(`/api/v1/tickets/${id}`, { status });
|
||||||
@@ -226,6 +234,21 @@ export default function TicketsPage() {
|
|||||||
<div><span className="text-gray-500">Status</span><p><Badge variant={statusVariant[detail.status] ?? "muted"}>{detail.status}</Badge></p></div>
|
<div><span className="text-gray-500">Status</span><p><Badge variant={statusVariant[detail.status] ?? "muted"}>{detail.status}</Badge></p></div>
|
||||||
<div className="col-span-2"><span className="text-gray-500">Created</span><p>{formatDate(detail.createdAt)}</p></div>
|
<div className="col-span-2"><span className="text-gray-500">Created</span><p>{formatDate(detail.createdAt)}</p></div>
|
||||||
{detail.description && <div className="col-span-2"><span className="text-gray-500">Description</span><p className="mt-1 text-gray-800 whitespace-pre-wrap">{detail.description}</p></div>}
|
{detail.description && <div className="col-span-2"><span className="text-gray-500">Description</span><p className="mt-1 text-gray-800 whitespace-pre-wrap">{detail.description}</p></div>}
|
||||||
|
{/* Installation: show Google Maps link using client address */}
|
||||||
|
{detail.type === "INSTALLATION" && detail.client && (
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="text-gray-500">Map</span>
|
||||||
|
<p className="mt-0.5">
|
||||||
|
<a
|
||||||
|
href={`https://www.google.com/maps/search/${encodeURIComponent([(ticketDetail as any)?.client?.address, detail.client.firstName + " " + detail.client.lastName].filter(Boolean).join(", "))}`}
|
||||||
|
target="_blank" rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-1 text-blue-600 hover:underline text-sm"
|
||||||
|
>
|
||||||
|
<MapPin size={13} /> View on Google Maps
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status actions */}
|
{/* Status actions */}
|
||||||
@@ -240,6 +263,13 @@ export default function TicketsPage() {
|
|||||||
<RotateCcw size={13} className="mr-1" /> Revert to {prevStatus[detail.status].replace("_", " ")}
|
<RotateCcw size={13} className="mr-1" /> Revert to {prevStatus[detail.status].replace("_", " ")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{/* Installation ticket RESOLVED → show Activate Client button */}
|
||||||
|
{detail.type === "INSTALLATION" && detail.status === "RESOLVED" && detail.clientId && (
|
||||||
|
<Button size="sm" variant="outline" onClick={() => activateClient.mutate(detail.clientId!)} isLoading={activateClient.isPending}
|
||||||
|
className="text-green-700 border-green-300 hover:bg-green-50">
|
||||||
|
<Zap size={13} className="mr-1" /> Activate Client
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Comments */}
|
{/* Comments */}
|
||||||
|
|||||||
Reference in New Issue
Block a user