diff --git a/src/components/StatusFilterTab.tsx b/src/components/StatusFilterTab.tsx index 861d2ae..9108a8e 100644 --- a/src/components/StatusFilterTab.tsx +++ b/src/components/StatusFilterTab.tsx @@ -7,6 +7,14 @@ import Menu from '@mui/material/Menu' import MenuItem from '@mui/material/MenuItem' import { styled } from '@mui/material/styles' +function toTitleCase(str: string): string { + return str + .toLowerCase() + .split(/\s+/) // split by spaces + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') +} + const DropdownButton = styled(Button)(({ theme }) => ({ textTransform: 'none', fontWeight: 400, @@ -102,7 +110,7 @@ const StatusFilterTabs: React.FC = ({ }) }} > - {status} + {toTitleCase(status)} ))} @@ -135,7 +143,7 @@ const StatusFilterTabs: React.FC = ({ }) }} > - {status} + {toTitleCase(status)} ))} @@ -158,7 +166,7 @@ const StatusFilterTabs: React.FC = ({ }) }} > - {isDropdownItemSelected ? selectedStatus : dropdownLabel} + {isDropdownItemSelected ? toTitleCase(selectedStatus) : dropdownLabel} = ({ color: selectedStatus === status ? 'primary.main' : 'text.primary' }} > - {status} + {toTitleCase(status)} ))} diff --git a/src/services/queries/purchaseOrder.ts b/src/services/queries/purchaseOrder.ts new file mode 100644 index 0000000..916f8b5 --- /dev/null +++ b/src/services/queries/purchaseOrder.ts @@ -0,0 +1,41 @@ +import { PurchaseOrders } from '@/types/services/purchaseOrder' +import { useQuery } from '@tanstack/react-query' +import { api } from '../api' + +interface PurchaseOrderQueryParams { + page?: number + limit?: number + search?: string + status?: string +} + +export function usePurchaseOrders(params: PurchaseOrderQueryParams = {}) { + const { page = 1, limit = 10, search = '', status = '', ...filters } = params + + return useQuery({ + queryKey: ['purchase-orders', { page, limit, search, status, ...filters }], + queryFn: async () => { + const queryParams = new URLSearchParams() + + queryParams.append('page', page.toString()) + queryParams.append('limit', limit.toString()) + + if (search) { + queryParams.append('search', search) + } + + if (status) { + queryParams.append('status', status) + } + + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + queryParams.append(key, value.toString()) + } + }) + + const res = await api.get(`/purchase-orders?${queryParams.toString()}`) + return res.data.data + } + }) +} diff --git a/src/types/services/purchaseOrder.ts b/src/types/services/purchaseOrder.ts index 6337ad7..ee9153b 100644 --- a/src/types/services/purchaseOrder.ts +++ b/src/types/services/purchaseOrder.ts @@ -1,3 +1,5 @@ +import { Vendor } from './vendor' + export interface PurchaseOrderRequest { vendor_id: string // uuid.UUID po_number: string @@ -17,3 +19,77 @@ export interface PurchaseOrderItemRequest { unit_id: string // uuid.UUID amount: number } + +export interface PurchaseOrders { + purchase_orders: PurchaseOrder[] + total_count: number + page: number + limit: number + total_pages: number +} + +export interface PurchaseOrder { + id: string + organization_id: string + vendor_id: string + po_number: string + transaction_date: string // RFC3339 + due_date: string // RFC3339 + reference: string | null + status: string + message: string | null + total_amount: number + created_at: string + updated_at: string + vendor: Vendor + items: PurchaseOrderItem[] + attachments: PurchaseOrderAttachment[] +} + +export interface PurchaseOrderItem { + id: string + purchase_order_id: string + ingredient_id: string + description: string + quantity: number + unit_id: string + amount: number + created_at: string + updated_at: string + ingredient: PurchaseOrderIngredient + unit: PurchaseOrderUnit +} + +export interface PurchaseOrderIngredient { + id: string + name: string +} + +export interface PurchaseOrderUnit { + id: string + name: string +} + +export interface PurchaseOrderAttachment { + id: string + purchase_order_id: string + file_id: string + created_at: string + file: PurchaseOrderFile +} + +export interface PurchaseOrderFile { + id: string + organization_id: string + user_id: string + file_name: string + original_name: string + file_url: string + file_size: number + mime_type: string + file_type: string + upload_path: string + is_public: boolean + created_at: string + updated_at: string +} diff --git a/src/views/apps/purchase/purchase-orders/list/PurchaseOrderListTable.tsx b/src/views/apps/purchase/purchase-orders/list/PurchaseOrderListTable.tsx index 33cbab8..4591bf9 100644 --- a/src/views/apps/purchase/purchase-orders/list/PurchaseOrderListTable.tsx +++ b/src/views/apps/purchase/purchase-orders/list/PurchaseOrderListTable.tsx @@ -42,6 +42,9 @@ import Loading from '@/components/layout/shared/Loading' import { PurchaseOrderType } from '@/types/apps/purchaseOrderTypes' import { purchaseOrdersData } from '@/data/dummy/purchase-order' import { getLocalizedUrl } from '@/utils/i18n' +import { PurchaseOrder } from '@/types/services/purchaseOrder' +import { usePurchaseOrders } from '@/services/queries/purchaseOrder' +import StatusFilterTabs from '@/components/StatusFilterTab' declare module '@tanstack/table-core' { interface FilterFns { @@ -52,7 +55,7 @@ declare module '@tanstack/table-core' { } } -type PurchaseOrderTypeWithAction = PurchaseOrderType & { +type PurchaseOrderTypeWithAction = PurchaseOrder & { actions?: string } @@ -135,46 +138,24 @@ const PurchaseOrderListTable = () => { // States const [addPOOpen, setAddPOOpen] = useState(false) const [rowSelection, setRowSelection] = useState({}) - const [currentPage, setCurrentPage] = useState(0) + const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) const [openConfirm, setOpenConfirm] = useState(false) const [poId, setPOId] = useState('') const [search, setSearch] = useState('') const [statusFilter, setStatusFilter] = useState('Semua') - const [filteredData, setFilteredData] = useState(purchaseOrdersData) - // Hooks const { lang: locale } = useParams() - // Filter data based on search and status - useEffect(() => { - let filtered = purchaseOrdersData + const { data, isLoading, error, isFetching } = usePurchaseOrders({ + page: currentPage, + limit: pageSize, + search, + status: statusFilter === 'Semua' ? '' : statusFilter + }) - // Filter by search - if (search) { - filtered = filtered.filter( - po => - po.number.toLowerCase().includes(search.toLowerCase()) || - po.vendorName.toLowerCase().includes(search.toLowerCase()) || - po.vendorCompany.toLowerCase().includes(search.toLowerCase()) || - po.status.toLowerCase().includes(search.toLowerCase()) - ) - } - - // Filter by status - if (statusFilter !== 'Semua') { - filtered = filtered.filter(po => po.status === statusFilter) - } - - setFilteredData(filtered) - setCurrentPage(0) - }, [search, statusFilter]) - - const totalCount = filteredData.length - const paginatedData = useMemo(() => { - const startIndex = currentPage * pageSize - return filteredData.slice(startIndex, startIndex + pageSize) - }, [filteredData, currentPage, pageSize]) + const purchaseOrders = data?.purchase_orders ?? [] + const totalCount = data?.total_count ?? 0 const handlePageChange = useCallback((event: unknown, newPage: number) => { setCurrentPage(newPage) @@ -222,7 +203,7 @@ const PurchaseOrderListTable = () => { /> ) }, - columnHelper.accessor('number', { + columnHelper.accessor('po_number', { header: 'Nomor PO', cell: ({ row }) => ( ) }), - columnHelper.accessor('vendorName', { + columnHelper.accessor('vendor.name', { header: 'Vendor', cell: ({ row }) => (
- {row.original.vendorName} + {row.original.vendor.contact_person} - {row.original.vendorCompany} + {row.original.vendor.name}
) @@ -260,13 +241,13 @@ const PurchaseOrderListTable = () => { header: 'Referensi', cell: ({ row }) => {row.original.reference || '-'} }), - columnHelper.accessor('date', { + columnHelper.accessor('transaction_date', { header: 'Tanggal', - cell: ({ row }) => {row.original.date} + cell: ({ row }) => {row.original.transaction_date} }), - columnHelper.accessor('dueDate', { + columnHelper.accessor('due_date', { header: 'Tanggal Jatuh Tempo', - cell: ({ row }) => {row.original.dueDate} + cell: ({ row }) => {row.original.due_date} }), columnHelper.accessor('status', { header: 'Status', @@ -282,16 +263,16 @@ const PurchaseOrderListTable = () => { ) }), - columnHelper.accessor('total', { + columnHelper.accessor('total_amount', { header: 'Total', - cell: ({ row }) => {formatCurrency(row.original.total)} + cell: ({ row }) => {formatCurrency(row.original.total_amount)} }) ], [] ) const table = useReactTable({ - data: paginatedData as PurchaseOrderType[], + data: purchaseOrders as PurchaseOrder[], columns, filterFns: { fuzzy: fuzzyFilter @@ -316,27 +297,11 @@ const PurchaseOrderListTable = () => { {/* Filter Status Tabs */}
- {['Semua', 'Draft', 'Disetujui', 'Dikirim Sebagian', 'Selesai', 'Lainnya'].map(status => ( - - ))} +
@@ -378,56 +343,60 @@ const PurchaseOrderListTable = () => {
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - ))} - - {filteredData.length === 0 ? ( - - - - - - ) : ( - - {table.getRowModel().rows.map(row => { - return ( - - {row.getVisibleCells().map(cell => ( - - ))} - - ) - })} - - )} -
- {header.isPlaceholder ? null : ( - <> -
- {flexRender(header.column.columnDef.header, header.getContext())} - {{ - asc: , - desc: - }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} -
- - )} -
- Tidak ada data tersedia -
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {purchaseOrders.length === 0 ? ( + + + + + + ) : ( + + {table.getRowModel().rows.map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ Tidak ada data tersedia +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )}
{ onPageChange={handlePageChange} onRowsPerPageChange={handlePageSizeChange} rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} />