From 1c474914f2ca91d2c507e87d0365ac0a928934be Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Fri, 12 Jun 2026 11:55:14 -0300 Subject: [PATCH] feat: add vendor purchase orders module --- src/app/(protected)/vendor-pos/[id].tsx | 345 ++++++++++++++++++ src/app/(protected)/vendor-pos/index.tsx | 281 ++++++++++++++ src/domain/vendor-pos/api/vendor-pos-api.ts | 57 +++ .../vendor-pos/mappers/vendor-po-mapper.ts | 196 ++++++++++ src/domain/vendor-pos/types/vendor-po.ts | 127 +++++++ .../use-cases/use-vendor-po-detail.ts | 13 + .../use-cases/use-vendor-pos-list.ts | 14 + 7 files changed, 1033 insertions(+) create mode 100644 src/app/(protected)/vendor-pos/[id].tsx create mode 100644 src/app/(protected)/vendor-pos/index.tsx create mode 100644 src/domain/vendor-pos/api/vendor-pos-api.ts create mode 100644 src/domain/vendor-pos/mappers/vendor-po-mapper.ts create mode 100644 src/domain/vendor-pos/types/vendor-po.ts create mode 100644 src/domain/vendor-pos/use-cases/use-vendor-po-detail.ts create mode 100644 src/domain/vendor-pos/use-cases/use-vendor-pos-list.ts diff --git a/src/app/(protected)/vendor-pos/[id].tsx b/src/app/(protected)/vendor-pos/[id].tsx new file mode 100644 index 00000000..b64971d2 --- /dev/null +++ b/src/app/(protected)/vendor-pos/[id].tsx @@ -0,0 +1,345 @@ +import { useState, type ReactNode } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TextField, + Typography, +} from "@mui/material"; +import { useApproveUplift, useDenyUplift } from "@/domain/uplifts/use-cases/use-uplift-actions"; +import { useVendorPoDetail } from "@/domain/vendor-pos/use-cases/use-vendor-po-detail"; +import type { UpliftRequest } from "@/domain/work-orders/types/work-order"; +import { formatDateTime, toDateInputValue } from "@/lib/time-utils"; + +function formatMoney(value: number | null | undefined): string { + if (value == null) return "—"; + return `$${Number(value).toFixed(2)}`; +} + +function InfoRow({ label, children }: { label: string; children: ReactNode }) { + return ( + + + {label} + + + {children == null || children === "" ? "—" : children} + + + ); +} + +export default function VendorPoViewPage() { + const { id } = useParams(); + const navigate = useNavigate(); + const { data: dispatch, isLoading, error, refetch } = useVendorPoDetail(id); + + const approveUplift = useApproveUplift(id); + const denyUplift = useDenyUplift(id); + + const [upliftAction, setUpliftAction] = useState<{ + type: "approve" | "deny"; + uplift: UpliftRequest; + } | null>(null); + const [note, setNote] = useState(""); + + const handleUpliftConfirm = () => { + if (!upliftAction) return; + if (upliftAction.type === "approve") { + approveUplift.mutate( + { id: upliftAction.uplift.id, note: note || undefined }, + { + onSuccess: () => { + setUpliftAction(null); + setNote(""); + void refetch(); + }, + }, + ); + } else { + if (!note.trim()) return; + denyUplift.mutate( + { id: upliftAction.uplift.id, note: note.trim() }, + { + onSuccess: () => { + setUpliftAction(null); + setNote(""); + void refetch(); + }, + }, + ); + } + }; + + if (isLoading) { + return ( + + + + ); + } + + if (error || !dispatch) { + return ( + + + {error instanceof Error ? error.message : "Vendor PO not found"} + + + ); + } + + const vendor = dispatch.vendor; + + return ( + + + + + + + + {dispatch.poNumber || "(no PO #)"} + + + Dispatch {dispatch.dispatchNumber || "—"} + + + {vendor?.companyName && ( + + · {vendor.companyName} + + )} + {dispatch.nteAmount != null && ( + + · NTE {formatMoney(dispatch.nteAmount)} + + )} + + + + + + + Vendor + + {vendor ? ( + + {vendor.companyName} + {vendor.contactName} + {vendor.email} + {vendor.phone} + + {[vendor.address, vendor.city, vendor.state, vendor.zip].filter(Boolean).join(", ")} + + {vendor.tradeSpecialties} + + ) : ( + No vendor attached. + )} + + + + + PO Details + + + {dispatch.poNumber} + {dispatch.dispatchNumber} + {dispatch.status} + {formatMoney(dispatch.nteAmount)} + {formatDateTime(dispatch.dispatchedAt)} + {formatDateTime(dispatch.acknowledgedAt)} + {toDateInputValue(dispatch.scheduledDate) || "—"} + {toDateInputValue(dispatch.completedDate) || "—"} + {formatDateTime(dispatch.verifiedAt)} + {dispatch.emailSent ? "Yes" : "No"} + + {dispatch.description && ( + + + Description + + + {dispatch.description} + + + )} + + + {dispatch.primaryLocation && ( + + + Site + + + {dispatch.primaryLocation.name} + + {[ + dispatch.primaryLocation.address, + dispatch.primaryLocation.city, + dispatch.primaryLocation.state, + dispatch.primaryLocation.zipCode, + ] + .filter(Boolean) + .join(", ")} + + + + )} + + + + Linked Work Orders ({dispatch.workOrders.length}) + + {dispatch.workOrders.length === 0 ? ( + No work orders linked. + ) : ( + + + + WO # + Title + Trade + Status + Location + + + + {dispatch.workOrders.map((wo) => ( + + + + + {wo.title} + {wo.trade} + {wo.status} + {wo.locationName} + + ))} + +
+ )} +
+ + + + NTE Uplift History ({dispatch.upliftRequests.length}) + + {dispatch.upliftRequests.length === 0 ? ( + No uplift requests on this PO. + ) : ( + + {dispatch.upliftRequests.map((u) => ( + + + + {formatMoney(u.currentNTE)} → {formatMoney(u.requestedNTE)} + + (Δ {formatMoney(u.delta)}, Tier {u.requiredTier}) + + + + + {u.vendorReason && ( + + Reason: {u.vendorReason} + + )} + {u.status === "Pending" && ( + + + + + )} + + ))} + + )} + +
+ + setUpliftAction(null)}> + + {upliftAction?.type === "approve" ? "Approve Uplift" : "Deny Uplift"} + + + setNote(e.target.value)} + required={upliftAction?.type === "deny"} + /> + + + + + + +
+ ); +} diff --git a/src/app/(protected)/vendor-pos/index.tsx b/src/app/(protected)/vendor-pos/index.tsx new file mode 100644 index 00000000..ff5971f6 --- /dev/null +++ b/src/app/(protected)/vendor-pos/index.tsx @@ -0,0 +1,281 @@ +import { useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + FormControl, + InputLabel, + MenuItem, + Paper, + Select, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, + TextField, + Typography, +} from "@mui/material"; +import { useVendorPosList } from "@/domain/vendor-pos/use-cases/use-vendor-pos-list"; +import { useVendorsList } from "@/domain/vendors/use-cases/use-vendors-list"; +import { useDebounce } from "@/hooks/use-debounce"; +import { toDateInputValue } from "@/lib/time-utils"; + +const STATUS_OPTIONS = [ + "", + "Sent", + "Acknowledged", + "In Progress", + "Completed", + "Verified", + "Cancelled", +]; + +function formatMoney(value: number | null): string { + if (value == null) return "—"; + return `$${value.toFixed(2)}`; +} + +function formatDate(value: string): string { + if (!value) return "—"; + return toDateInputValue(value) || "—"; +} + +export default function VendorPosListPage() { + const navigate = useNavigate(); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(12); + const [status, setStatus] = useState(""); + const [vendorId, setVendorId] = useState(""); + const [dateFrom, setDateFrom] = useState(""); + const [dateTo, setDateTo] = useState(""); + + const debouncedSearch = useDebounce(search, 300); + + const listParams = useMemo( + () => ({ + search: debouncedSearch, + page: page + 1, + pageSize, + status, + vendorId, + dateFrom, + dateTo, + }), + [debouncedSearch, page, pageSize, status, vendorId, dateFrom, dateTo], + ); + + const { data, isLoading, isFetching, error } = useVendorPosList(listParams); + const { data: vendorsData } = useVendorsList({ page: 1, pageSize: 500 }); + + const rows = data?.items ?? []; + const totalCount = data?.totalCount ?? 0; + const vendors = vendorsData?.items ?? []; + + const clearFilters = () => { + setStatus(""); + setVendorId(""); + setDateFrom(""); + setDateTo(""); + setPage(0); + }; + + return ( + + + + + Vendor POs + + + {totalCount} total + + {isFetching && !isLoading && } + + + { + setSearch(e.target.value); + setPage(0); + }} + placeholder="Search PO #, dispatch, vendor, WO..." + className="min-w-[280px]" + /> + + + + + Status + + + + + Vendor + + + + { + setDateFrom(e.target.value); + setPage(0); + }} + slotProps={{ inputLabel: { shrink: true } }} + /> + { + setDateTo(e.target.value); + setPage(0); + }} + slotProps={{ inputLabel: { shrink: true } }} + /> + + + + {error && ( + + {error instanceof Error ? error.message : "Failed to load vendor POs"} + + )} + + + + + + PO # + Dispatch # + Vendor + WO # + Location + NTE + Status + Uplifts + Dispatched + + + + {isLoading ? ( + + + + + + ) : rows.length === 0 ? ( + + + No vendor POs found + + + ) : ( + rows.map((row) => ( + navigate(`/vendor-pos/${row.id}`)} + > + + + + {row.dispatchNumber || "—"} + {row.vendorName || "—"} + {row.primaryWOInternalNumber || "—"} + {row.locationName || "—"} + {formatMoney(row.nteAmount)} + + + + + {row.pendingUpliftCount > 0 ? ( + + ) : ( + "—" + )} + + {formatDate(row.dispatchedAt)} + + )) + )} + +
+
+ + setPage(nextPage)} + rowsPerPage={pageSize} + onRowsPerPageChange={(e) => { + setPageSize(Number(e.target.value)); + setPage(0); + }} + rowsPerPageOptions={[12, 24, 48]} + /> +
+ ); +} diff --git a/src/domain/vendor-pos/api/vendor-pos-api.ts b/src/domain/vendor-pos/api/vendor-pos-api.ts new file mode 100644 index 00000000..ef8b1d72 --- /dev/null +++ b/src/domain/vendor-pos/api/vendor-pos-api.ts @@ -0,0 +1,57 @@ +import { API_PATHS } from "@/api/api-paths"; +import { apiGet } from "@/api/api"; +import { handleApiResponse } from "@/api/handle-api-response"; +import { + mapVendorPoDetail, + mapVendorPoListItem, +} from "@/domain/vendor-pos/mappers/vendor-po-mapper"; +import type { + VendorPoDetail, + VendorPoListParams, + VendorPoListResult, +} from "@/domain/vendor-pos/types/vendor-po"; + +function buildListParams(params: VendorPoListParams): Record { + const searchParams: Record = { + page: params.page ?? 1, + pageSize: params.pageSize ?? 12, + sortBy: params.sortBy ?? "dispatchedAt", + sortDir: params.sortDir ?? "desc", + }; + if (params.search) searchParams.search = params.search; + if (params.status) searchParams.status = params.status; + if (params.vendorId) searchParams.vendorId = params.vendorId; + if (params.dateFrom) searchParams.dateFrom = params.dateFrom; + if (params.dateTo) searchParams.dateTo = params.dateTo; + return searchParams; +} + +export const vendorPosApi = { + getList: async (params: VendorPoListParams = {}): Promise => { + const data = await apiGet(API_PATHS.rest.dispatches, { + searchParams: buildListParams(params), + }); + const response = handleApiResponse>(data); + const items = Array.isArray(response?.items) + ? response.items + : Array.isArray((response as { data?: unknown[] })?.data) + ? (response as { data: unknown[] }).data + : []; + const total = + (typeof response?.total === "number" ? response.total : undefined) ?? + (typeof response?.totalCount === "number" ? response.totalCount : undefined) ?? + items.length; + + return { + items: items.map(mapVendorPoListItem), + totalCount: total, + page: params.page ?? 1, + pageSize: params.pageSize ?? 12, + }; + }, + + getById: async (id: string | number): Promise => { + const data = await apiGet(`${API_PATHS.rest.dispatches}/${id}`); + return mapVendorPoDetail(handleApiResponse(data)); + }, +}; diff --git a/src/domain/vendor-pos/mappers/vendor-po-mapper.ts b/src/domain/vendor-pos/mappers/vendor-po-mapper.ts new file mode 100644 index 00000000..e20fe7ff --- /dev/null +++ b/src/domain/vendor-pos/mappers/vendor-po-mapper.ts @@ -0,0 +1,196 @@ +import type { + VendorPoAuditEntry, + VendorPoChecklistItem, + VendorPoComment, + VendorPoDetail, + VendorPoListItem, + VendorPoLocation, + VendorPoSignoff, + VendorPoVendor, + VendorPoWorkOrder, +} from "@/domain/vendor-pos/types/vendor-po"; +import { mapUpliftRequests } from "@/domain/work-orders/mappers/work-order-mapper"; + +function asRecord(raw: unknown): Record { + return typeof raw === "object" && raw !== null ? (raw as Record) : {}; +} + +function readString(record: Record, ...keys: string[]): string { + for (const key of keys) { + const value = record[key]; + if (typeof value === "string") return value; + if (typeof value === "number") return String(value); + } + return ""; +} + +function readId(record: Record): string | number { + const value = record.id ?? record.Id; + if (typeof value === "string" || typeof value === "number") return value; + return ""; +} + +function readNumber(record: Record, ...keys: string[]): number | null { + for (const key of keys) { + const value = record[key]; + if (typeof value === "number") return value; + if (typeof value === "string" && value !== "") { + const parsed = Number(value); + if (!Number.isNaN(parsed)) return parsed; + } + } + return null; +} + +function readBool(record: Record, ...keys: string[]): boolean { + for (const key of keys) { + const value = record[key]; + if (typeof value === "boolean") return value; + } + return false; +} + +export function mapVendorPoListItem(raw: unknown): VendorPoListItem { + const item = asRecord(raw); + return { + id: readId(item), + poNumber: readString(item, "poNumber", "PONumber"), + dispatchNumber: readString(item, "dispatchNumber", "DispatchNumber"), + vendorName: readString(item, "vendorName", "VendorName"), + primaryWOInternalNumber: readString(item, "primaryWOInternalNumber", "PrimaryWOInternalNumber"), + workOrderCount: readNumber(item, "workOrderCount", "WorkOrderCount") ?? 0, + locationName: readString(item, "locationName", "LocationName"), + nteAmount: readNumber(item, "nteAmount", "NTEAmount"), + status: readString(item, "status", "Status"), + pendingUpliftCount: readNumber(item, "pendingUpliftCount", "PendingUpliftCount") ?? 0, + dispatchedAt: readString(item, "dispatchedAt", "DispatchedAt"), + scheduledDate: readString(item, "scheduledDate", "ScheduledDate"), + completedDate: readString(item, "completedDate", "CompletedDate"), + }; +} + +function mapVendor(raw: unknown): VendorPoVendor | null { + if (!raw) return null; + const item = asRecord(raw); + return { + companyName: readString(item, "companyName", "CompanyName"), + contactName: readString(item, "contactName", "ContactName"), + email: readString(item, "email", "Email"), + phone: readString(item, "phone", "Phone"), + address: readString(item, "address", "Address"), + city: readString(item, "city", "City"), + state: readString(item, "state", "State"), + zip: readString(item, "zip", "Zip", "zipCode"), + tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"), + }; +} + +function mapLocation(raw: unknown): VendorPoLocation | null { + if (!raw) return null; + const item = asRecord(raw); + return { + name: readString(item, "name", "Name"), + address: readString(item, "address", "Address"), + city: readString(item, "city", "City"), + state: readString(item, "state", "State"), + zipCode: readString(item, "zipCode", "ZipCode", "zip"), + }; +} + +function mapWorkOrder(raw: unknown): VendorPoWorkOrder { + const item = asRecord(raw); + return { + workOrderId: readId(item) || readString(item, "workOrderId", "WorkOrderId"), + internalWONumber: readString(item, "internalWONumber", "InternalWONumber"), + title: readString(item, "title", "Title"), + trade: readString(item, "trade", "Trade"), + subTrade: readString(item, "subTrade", "SubTrade"), + problem: readString(item, "problem", "Problem"), + priority: readString(item, "priority", "Priority"), + status: readString(item, "status", "Status"), + locationName: readString(item, "locationName", "LocationName"), + }; +} + +function mapChecklistItem(raw: unknown): VendorPoChecklistItem { + const item = asRecord(raw); + return { + id: readId(item), + itemText: readString(item, "itemText", "ItemText"), + isCompleted: readBool(item, "isCompleted", "IsCompleted"), + completedBy: readString(item, "completedBy", "CompletedBy"), + completedAt: readString(item, "completedAt", "CompletedAt"), + }; +} + +function mapSignoff(raw: unknown): VendorPoSignoff { + const item = asRecord(raw); + return { + id: readId(item), + signoffType: readString(item, "signoffType", "SignoffType"), + name: readString(item, "name", "Name"), + signatureMethod: readString(item, "signatureMethod", "SignatureMethod"), + signedAt: readString(item, "signedAt", "SignedAt"), + signature: readString(item, "signature", "Signature"), + }; +} + +function mapComment(raw: unknown): VendorPoComment { + const item = asRecord(raw); + return { + id: readId(item), + userName: readString(item, "userName", "UserName"), + commenter: readString(item, "commenter", "Commenter"), + commentType: readString(item, "commentType", "CommentType"), + createdDate: readString(item, "createdDate", "CreatedDate"), + commenttext: readString(item, "commenttext", "CommentText", "commentText"), + }; +} + +function mapAuditEntry(raw: unknown): VendorPoAuditEntry { + const item = asRecord(raw); + return { + id: readId(item), + createdAt: readString(item, "createdAt", "CreatedAt"), + action: readString(item, "action", "Action"), + fieldName: readString(item, "fieldName", "FieldName"), + oldValue: readString(item, "oldValue", "OldValue"), + newValue: readString(item, "newValue", "NewValue"), + userName: readString(item, "userName", "UserName"), + }; +} + +export function mapVendorPoDetail(raw: unknown): VendorPoDetail { + const item = asRecord(raw); + const vendor = item.vendor ?? item.Vendor; + const location = item.primaryLocation ?? item.PrimaryLocation; + const workOrders = (item.workOrders ?? item.WorkOrders) as unknown[]; + const checklist = (item.checklist ?? item.Checklist) as unknown[]; + const signoffs = (item.signoffs ?? item.Signoffs) as unknown[]; + const comments = (item.comments ?? item.Comments) as unknown[]; + const uplifts = (item.upliftRequests ?? item.UpliftRequests) as unknown[]; + const auditLog = (item.auditLog ?? item.AuditLog) as unknown[]; + + return { + id: readId(item), + poNumber: readString(item, "poNumber", "PONumber"), + dispatchNumber: readString(item, "dispatchNumber", "DispatchNumber"), + status: readString(item, "status", "Status"), + nteAmount: readNumber(item, "nteAmount", "NTEAmount"), + dispatchedAt: readString(item, "dispatchedAt", "DispatchedAt"), + acknowledgedAt: readString(item, "acknowledgedAt", "AcknowledgedAt"), + scheduledDate: readString(item, "scheduledDate", "ScheduledDate"), + completedDate: readString(item, "completedDate", "CompletedDate"), + verifiedAt: readString(item, "verifiedAt", "VerifiedAt"), + emailSent: readBool(item, "emailSent", "EmailSent"), + description: readString(item, "description", "Description"), + vendor: mapVendor(vendor), + primaryLocation: mapLocation(location), + workOrders: Array.isArray(workOrders) ? workOrders.map(mapWorkOrder) : [], + checklist: Array.isArray(checklist) ? checklist.map(mapChecklistItem) : [], + signoffs: Array.isArray(signoffs) ? signoffs.map(mapSignoff) : [], + comments: Array.isArray(comments) ? comments.map(mapComment) : [], + upliftRequests: Array.isArray(uplifts) ? mapUpliftRequests(uplifts) : [], + auditLog: Array.isArray(auditLog) ? auditLog.map(mapAuditEntry) : [], + }; +} diff --git a/src/domain/vendor-pos/types/vendor-po.ts b/src/domain/vendor-pos/types/vendor-po.ts new file mode 100644 index 00000000..d1fef92b --- /dev/null +++ b/src/domain/vendor-pos/types/vendor-po.ts @@ -0,0 +1,127 @@ +import type { UpliftRequest } from "@/domain/work-orders/types/work-order"; + +export interface VendorPoListItem { + id: string | number; + poNumber: string; + dispatchNumber: string; + vendorName: string; + primaryWOInternalNumber: string; + workOrderCount: number; + locationName: string; + nteAmount: number | null; + status: string; + pendingUpliftCount: number; + dispatchedAt: string; + scheduledDate: string; + completedDate: string; +} + +export interface VendorPoListParams { + search?: string; + page?: number; + pageSize?: number; + sortBy?: string; + sortDir?: string; + status?: string; + vendorId?: string; + dateFrom?: string; + dateTo?: string; +} + +export interface VendorPoListResult { + items: VendorPoListItem[]; + totalCount: number; + page: number; + pageSize: number; +} + +export interface VendorPoVendor { + companyName: string; + contactName: string; + email: string; + phone: string; + address: string; + city: string; + state: string; + zip: string; + tradeSpecialties: string; +} + +export interface VendorPoLocation { + name: string; + address: string; + city: string; + state: string; + zipCode: string; +} + +export interface VendorPoWorkOrder { + workOrderId: string | number; + internalWONumber: string; + title: string; + trade: string; + subTrade: string; + problem: string; + priority: string; + status: string; + locationName: string; +} + +export interface VendorPoChecklistItem { + id: string | number; + itemText: string; + isCompleted: boolean; + completedBy: string; + completedAt: string; +} + +export interface VendorPoSignoff { + id: string | number; + signoffType: string; + name: string; + signatureMethod: string; + signedAt: string; + signature: string; +} + +export interface VendorPoComment { + id: string | number; + userName: string; + commenter: string; + commentType: string; + createdDate: string; + commenttext: string; +} + +export interface VendorPoAuditEntry { + id: string | number; + createdAt: string; + action: string; + fieldName: string; + oldValue: string; + newValue: string; + userName: string; +} + +export interface VendorPoDetail { + id: string | number; + poNumber: string; + dispatchNumber: string; + status: string; + nteAmount: number | null; + dispatchedAt: string; + acknowledgedAt: string; + scheduledDate: string; + completedDate: string; + verifiedAt: string; + emailSent: boolean; + description: string; + vendor: VendorPoVendor | null; + primaryLocation: VendorPoLocation | null; + workOrders: VendorPoWorkOrder[]; + checklist: VendorPoChecklistItem[]; + signoffs: VendorPoSignoff[]; + comments: VendorPoComment[]; + upliftRequests: UpliftRequest[]; + auditLog: VendorPoAuditEntry[]; +} diff --git a/src/domain/vendor-pos/use-cases/use-vendor-po-detail.ts b/src/domain/vendor-pos/use-cases/use-vendor-po-detail.ts new file mode 100644 index 00000000..d68eca77 --- /dev/null +++ b/src/domain/vendor-pos/use-cases/use-vendor-po-detail.ts @@ -0,0 +1,13 @@ +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { vendorPosApi } from "@/domain/vendor-pos/api/vendor-pos-api"; +import type { VendorPoDetail } from "@/domain/vendor-pos/types/vendor-po"; +import { queryKeys } from "@/infra/query-key/query-key"; +import { requireQueryParam } from "@/lib/query/require-query-param"; + +export function useVendorPoDetail(id: string | undefined): UseQueryResult { + return useQuery({ + queryKey: queryKeys.vendorPos.detail(id ?? ""), + queryFn: () => vendorPosApi.getById(requireQueryParam(id, "Vendor PO id")), + enabled: Boolean(id), + }); +} diff --git a/src/domain/vendor-pos/use-cases/use-vendor-pos-list.ts b/src/domain/vendor-pos/use-cases/use-vendor-pos-list.ts new file mode 100644 index 00000000..3cda45d1 --- /dev/null +++ b/src/domain/vendor-pos/use-cases/use-vendor-pos-list.ts @@ -0,0 +1,14 @@ +import { keepPreviousData, useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { vendorPosApi } from "@/domain/vendor-pos/api/vendor-pos-api"; +import type { VendorPoListParams, VendorPoListResult } from "@/domain/vendor-pos/types/vendor-po"; +import { queryKeys } from "@/infra/query-key/query-key"; + +export function useVendorPosList( + params: VendorPoListParams, +): UseQueryResult { + return useQuery({ + queryKey: queryKeys.vendorPos.list(params as Record), + queryFn: () => vendorPosApi.getList(params), + placeholderData: keepPreviousData, + }); +}