fix: remittances totalAmount+crash, create client URL, tickets comments endpoint #2
@@ -60,11 +60,11 @@ export default function ClientsPage() {
|
|||||||
|
|
||||||
const createClient = useMutation({
|
const createClient = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const res = await api.post("/api/v1/clients/onboard", {
|
const res = await api.post("/api/v1/clients", {
|
||||||
firstName: form.firstName, lastName: form.lastName,
|
firstName: form.firstName, lastName: form.lastName,
|
||||||
email: form.email, phone: form.phone, address: form.address,
|
email: form.email, phone: form.phone, address: form.address,
|
||||||
areaId: form.areaId || undefined, planId: form.planId,
|
areaId: form.areaId || undefined,
|
||||||
billingType: form.billingType,
|
planId: form.planId || undefined,
|
||||||
});
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
@@ -73,7 +73,7 @@ export default function ClientsPage() {
|
|||||||
qc.invalidateQueries({ queryKey: ["clients"] });
|
qc.invalidateQueries({ queryKey: ["clients"] });
|
||||||
setShowAdd(false);
|
setShowAdd(false);
|
||||||
setForm({ firstName: "", lastName: "", email: "", phone: "", address: "", areaId: "", planId: "", billingType: "POSTPAID" });
|
setForm({ firstName: "", lastName: "", email: "", phone: "", address: "", areaId: "", planId: "", billingType: "POSTPAID" });
|
||||||
if (data?.client?.id) router.push(`/clients/${data.client.id}`);
|
if (data?.id) router.push(`/clients/${data.id}`);
|
||||||
},
|
},
|
||||||
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create client"),
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create client"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export default function RemittancesPage() {
|
|||||||
<TableRow key={r.id} data-testid="remittance-row" onClick={() => setSelected(r)} className="cursor-pointer hover:bg-blue-50 transition-colors">
|
<TableRow key={r.id} data-testid="remittance-row" onClick={() => setSelected(r)} className="cursor-pointer hover:bg-blue-50 transition-colors">
|
||||||
<Td>{formatDate(r.createdAt)}</Td>
|
<Td>{formatDate(r.createdAt)}</Td>
|
||||||
<Td className="font-medium">{r.collector ? `${r.collector.firstName} ${r.collector.lastName}` : "—"}</Td>
|
<Td className="font-medium">{r.collector ? `${r.collector.firstName} ${r.collector.lastName}` : "—"}</Td>
|
||||||
<Td className="font-semibold text-blue-700">{formatCurrency(Number(r.amount))}</Td>
|
<Td className="font-semibold text-blue-700">{formatCurrency(Number(r.totalAmount))}</Td>
|
||||||
<Td><Badge variant={statusVariant[r.status] ?? "muted"}>{r.status}</Badge></Td>
|
<Td><Badge variant={statusVariant[r.status] ?? "muted"}>{r.status}</Badge></Td>
|
||||||
<Td className="text-gray-500 text-sm">{r.notes ?? "—"}</Td>
|
<Td className="text-gray-500 text-sm">{r.notes ?? "—"}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
@@ -152,7 +152,7 @@ export default function RemittancesPage() {
|
|||||||
<div className="space-y-1.5 max-h-48 overflow-y-auto">
|
<div className="space-y-1.5 max-h-48 overflow-y-auto">
|
||||||
{selected.payments.map(p => (
|
{selected.payments.map(p => (
|
||||||
<div key={p.id} className="flex justify-between text-sm bg-white border rounded-lg px-3 py-2">
|
<div key={p.id} className="flex justify-between text-sm bg-white border rounded-lg px-3 py-2">
|
||||||
<span className="text-gray-600">{p.client ? `${p.client.firstName} ${p.client.lastName}` : p.clientId.slice(0, 8)}</span>
|
<span className="text-gray-600">{p.client ? `${p.client.firstName} ${p.client.lastName}` : p.clientId ? p.clientId.slice(0, 8) : p.id.slice(0, 8)}</span>
|
||||||
<span className="font-medium text-green-700">{formatCurrency(Number(p.amount))}</span>
|
<span className="font-medium text-green-700">{formatCurrency(Number(p.amount))}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { formatDate } from "@/lib/utils";
|
|||||||
import api from "@/lib/api";
|
import api from "@/lib/api";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface TicketComment { id: string; body: string; createdAt: string; author?: { firstName: string; lastName: string }; }
|
interface TicketComment { id: string; body: string; createdAt: string; sender?: { firstName: string; lastName: string }; author?: { firstName: string; lastName: string }; }
|
||||||
interface TicketItem {
|
interface TicketItem {
|
||||||
id: string; ticketNumber?: string; subject: string; description?: string;
|
id: string; ticketNumber?: string; subject: string; description?: string;
|
||||||
type: string; priority: string; status: string; clientId?: string;
|
type: string; priority: string; status: string; clientId?: string;
|
||||||
@@ -86,7 +86,7 @@ export default function TicketsPage() {
|
|||||||
|
|
||||||
const addComment = useMutation({
|
const addComment = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
await api.post(`/api/v1/tickets/${selected!.id}/comments`, { body: comment });
|
await api.post(`/api/v1/tickets/${selected!.id}/messages`, { body: comment });
|
||||||
},
|
},
|
||||||
onSuccess: () => { toast.success("Comment added"); setComment(""); refetchDetail(); },
|
onSuccess: () => { toast.success("Comment added"); setComment(""); refetchDetail(); },
|
||||||
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed"),
|
onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed"),
|
||||||
@@ -112,7 +112,7 @@ export default function TicketsPage() {
|
|||||||
const tickets = data?.data ?? [];
|
const tickets = data?.data ?? [];
|
||||||
const total = data?.meta?.total ?? 0;
|
const total = data?.meta?.total ?? 0;
|
||||||
const detail = ticketDetail ?? selected;
|
const detail = ticketDetail ?? selected;
|
||||||
const comments = (ticketDetail as any)?.comments ?? [];
|
const comments = (ticketDetail as any)?.messages ?? [];
|
||||||
|
|
||||||
const nextStatus: Record<string, string> = { OPEN: "IN_PROGRESS", IN_PROGRESS: "RESOLVED", RESOLVED: "CLOSED" };
|
const nextStatus: Record<string, string> = { OPEN: "IN_PROGRESS", IN_PROGRESS: "RESOLVED", RESOLVED: "CLOSED" };
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ export default function TicketsPage() {
|
|||||||
{comments.length === 0 ? <p className="text-sm text-gray-400">No comments yet.</p> :
|
{comments.length === 0 ? <p className="text-sm text-gray-400">No comments yet.</p> :
|
||||||
comments.map((c: TicketComment) => (
|
comments.map((c: TicketComment) => (
|
||||||
<div key={c.id} className="bg-white border rounded-lg px-3 py-2 text-sm">
|
<div key={c.id} className="bg-white border rounded-lg px-3 py-2 text-sm">
|
||||||
<p className="font-medium text-gray-700 text-xs">{c.author ? `${c.author.firstName} ${c.author.lastName}` : "Staff"} · {formatDate(c.createdAt)}</p>
|
<p className="font-medium text-gray-700 text-xs">{(c.sender || c.author) ? `${(c.sender || c.author)!.firstName} ${(c.sender || c.author)!.lastName}` : "Staff"} · {formatDate(c.createdAt)}</p>
|
||||||
<p className="text-gray-800 mt-0.5">{c.body}</p>
|
<p className="text-gray-800 mt-0.5">{c.body}</p>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user