From e3b3f5ccdc73e4dd135ef4320493102b961e3b3a Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 13:26:19 +0700 Subject: [PATCH 01/16] feat(FE-345): Add Cost of Services expedition types and export ClosingSales --- src/types/api/closing/closing.d.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/types/api/closing/closing.d.ts b/src/types/api/closing/closing.d.ts index 64d0d465..eb5aa0e0 100644 --- a/src/types/api/closing/closing.d.ts +++ b/src/types/api/closing/closing.d.ts @@ -26,4 +26,18 @@ export type BaseClosingSales = { sales: BaseSales[]; }; +export type BaseCosExpedition = { + id: number; + expedition_name: string; + hpp: number; +}; + +export type BaseClosingCosExpedition = { + project_type: string; + flock_id: number; + period: number; + cos_expeditions: BaseCosExpedition[]; +}; + export type ClosingSales = BaseMetadata & BaseClosingSales; +export type ClosingCosExpedition = BaseMetadata & BaseClosingCosExpedition; From 44b9f94cec0152ff827fc1d58b6fd03eae2d5ea6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 13:27:15 +0700 Subject: [PATCH 02/16] feat(FE-345): Add getHppEkspedisi to ClosingApiService --- src/services/api/closing.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 66f88c76..8e2f784c 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -1,6 +1,9 @@ import { BaseApiService } from './base'; import { BaseApiResponse } from '@/types/api/api-general'; -import { ClosingSales } from '@/types/api/closing/closing'; +import { + ClosingSales, + ClosingCosExpedition, +} from '@/types/api/closing/closing'; export class ClosingApiService extends BaseApiService< ClosingSales, @@ -23,6 +26,19 @@ export class ClosingApiService extends BaseApiService< return undefined; } } + + async getHppEkspedisi( + id: number + ): Promise | undefined> { + try { + const getHppEkspedisiPath = `http://localhost:4010/api/closing/${id}/hpp-ekspedisi`; + return await this.customRequest>( + getHppEkspedisiPath + ); + } catch { + return undefined; + } + } } export const ClosingApi = new ClosingApiService('/closings'); From 80fd8bb7ba654ff0153a84dc5675873e1a839936 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 13:28:14 +0700 Subject: [PATCH 03/16] feat(FE-344,345): Add CosExpeditionReportTable component --- .../CosExpeditionReportTable.tsx | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx diff --git a/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx new file mode 100644 index 00000000..b5aac35f --- /dev/null +++ b/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx @@ -0,0 +1,154 @@ +'use client'; + +import React, { useMemo } from 'react'; +import { ColumnDef } from '@tanstack/react-table'; +import Table from '@/components/Table'; +import Card from '@/components/Card'; +import { formatCurrency } from '@/lib/helper'; +import { + BaseClosingCosExpedition, + BaseCosExpedition, +} from '@/types/api/closing/closing'; + +interface CosExpeditionReportTableProps { + type?: 'detail'; + initialValues?: BaseClosingCosExpedition; +} + +interface FooterCosExpeditionRow extends BaseCosExpedition { + _isFooter: true; +} + +const CosExpeditionReportTable = ({ + type = 'detail', + initialValues, +}: CosExpeditionReportTableProps) => { + const cosExpeditionData: BaseCosExpedition[] = useMemo(() => { + return initialValues?.cos_expeditions || []; + }, [initialValues]); + + const totals = useMemo(() => { + if (cosExpeditionData.length === 0) { + return { + totalHpp: 0, + }; + } + + const totalHpp = cosExpeditionData.reduce( + (sum, item) => sum + (item.hpp || 0), + 0 + ); + + return { + totalHpp, + }; + }, [cosExpeditionData]); + + const footerData = useMemo((): FooterCosExpeditionRow[] => { + if (cosExpeditionData.length === 0) return []; + + const footerRow: FooterCosExpeditionRow = { + id: -999, + expedition_name: 'Total HPP Ekspedisi', + hpp: totals.totalHpp, + _isFooter: true, + }; + + return [footerRow]; + }, [cosExpeditionData, totals]); + + const cosExpeditionColumns: ColumnDef[] = useMemo( + () => [ + { + id: 'id', + accessorKey: 'id', + header: 'No', + cell: (props) => { + const isFooter = '_isFooter' in props.row.original; + if (isFooter) { + return ( +
+ {props.row.original.expedition_name} +
+ ); + } + return props.getValue() || '-'; + }, + }, + { + id: 'expedition_name', + accessorKey: 'expedition_name', + header: 'Nama Ekspedisi', + cell: (props) => { + const isFooter = '_isFooter' in props.row.original; + if (isFooter) { + return null; + } + return props.getValue() || '-'; + }, + }, + { + id: 'hpp', + accessorKey: 'hpp', + header: 'HPP Ekspedisi', + cell: (props) => { + const value = props.getValue() as number; + const isFooter = '_isFooter' in props.row.original; + return ( +
+ {formatCurrency(value)} +
+ ); + }, + }, + ], + [] + ); + + return ( + <> +
+
+

HPP Ekspedisi

+ + 0} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + tableFooterClassName: + 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + }} + /> + + + + + ); +}; + +export default CosExpeditionReportTable; From dfecef2e0cdfa218a445e9eacd308eb5f4deb026 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 13:28:47 +0700 Subject: [PATCH 04/16] feat(FE-344,345): Fetch and render HPP Ekspedisi report --- src/app/_closing/detail/page.tsx | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/app/_closing/detail/page.tsx b/src/app/_closing/detail/page.tsx index 038e5072..7fb3c01d 100644 --- a/src/app/_closing/detail/page.tsx +++ b/src/app/_closing/detail/page.tsx @@ -2,20 +2,24 @@ import { useRouter, useSearchParams } from 'next/navigation'; import useSWR from 'swr'; +import { useId } from 'react'; import SalesReportTable from '@/components/pages/closing/sale/SalesReportTable'; +import CosExpeditionReportTable from '@/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable'; import { ClosingApi } from '@/services/api/closing'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; const ClosingDetailPage = () => { const router = useRouter(); const searchParams = useSearchParams(); + const penjualanId = useId(); + const hppEkspedisiId = useId(); const closingId = searchParams.get('closingId'); const { data: closing, isLoading: isLoadingClosing } = useSWR( - closingId, + closingId ? `${closingId}-${penjualanId}` : null, (id: string) => { - const numericId = parseInt(id, 10); + const numericId = parseInt(id.split('-')[0], 10); if (isNaN(numericId) || numericId <= 0) { throw new Error('Invalid closing ID'); } @@ -23,6 +27,19 @@ const ClosingDetailPage = () => { } ); + const { data: hpp_ekspedisi, isLoading: isLoadingHppEkspedisi } = useSWR( + closingId ? `${closingId}-${hppEkspedisiId}` : null, + (id: string) => { + const numericId = parseInt(id.split('-')[0], 10); + if (isNaN(numericId) || numericId <= 0) { + throw new Error('Invalid closing ID'); + } + return ClosingApi.getHppEkspedisi(numericId); + } + ); + + const isLoading = isLoadingClosing || isLoadingHppEkspedisi; + if (!closingId) { router.back(); @@ -33,21 +50,33 @@ const ClosingDetailPage = () => { ); } - if (!isLoadingClosing && (!closing || isResponseError(closing))) { + if ( + !isLoading || + !closing || + isResponseError(closing) || + !hpp_ekspedisi || + isResponseError(hpp_ekspedisi) + ) { router.replace('/404'); return; } return (
- {isLoadingClosing && ( + {isLoading && (
)} - {!isLoadingClosing && isResponseSuccess(closing) && ( + {!isLoading && isResponseSuccess(closing) && ( )} + {!isLoading && isResponseSuccess(hpp_ekspedisi) && ( + + )}
); }; From 5ad61d483adbd302377bc589570c2adbdaba93dd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 16:47:59 +0700 Subject: [PATCH 05/16] refactor(FE-344): update pagination UI and table footer handling --- src/components/Pagination.tsx | 514 ++++++++++-------- src/components/Table.tsx | 238 ++++---- .../pages/closing/sale/SalesReportTable.tsx | 205 ++----- 3 files changed, 499 insertions(+), 458 deletions(-) diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index e47e480d..43b26d90 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -1,7 +1,9 @@ 'use client'; -import { ReactNode } from 'react'; +import { ChangeEventHandler, ReactNode } from 'react'; + import { Icon } from '@iconify/react'; +import Button from '@/components/Button'; import { cn } from '@/lib/helper'; @@ -17,16 +19,18 @@ const PaginationButton = ({ disabled?: boolean; onClick?: () => void; }) => ( - + ); const EtcPaginationButton = ({ @@ -48,7 +52,7 @@ const EtcPaginationButton = ({ tabIndex={0} role='button' className={cn( - 'join-item btn btn-ghost p-2.5 rounded-lg text-sm font-medium text-gray-500 aspect-square' + 'join-item btn btn-ghost p-2.5 rounded-lg! text-sm font-medium text-gray-500 aspect-square' )} > ... @@ -57,7 +61,7 @@ const EtcPaginationButton = ({
    {pages.map((pageNumber) => (
  • @@ -76,7 +80,7 @@ const EtcPaginationButton = ({ + ); + + const PrevPageButton = () => ( + + ); + + const GoToLastPageButton = () => ( + + ); + + const NextPageButton = () => ( + + ); + + const PageInfo = () => ( + + Page {currentPage} of {totalPages} + + ); return ( -
    -
    - +
    +
    +
    + +
    - {totalPages <= 7 && ( -
    - {range(1, totalPages).map((pageNumber) => ( +
    +
    + +
    + +
    + +
    + + {totalPages <= 7 && + range(1, totalPages).map((pageNumber) => ( pageChangeHandler(pageNumber)} /> ))} -
    - )} - {totalPages > 7 && ( -
    - pageChangeHandler(1)} - /> - - {totalPages >= 2 && - (currentPage <= 3 || currentPage >= totalPages - 2) && ( - pageChangeHandler(2)} - /> - )} - - {totalPages >= 2 && - currentPage > 3 && - currentPage < totalPages - 2 && ( - - )} - - {totalPages >= 3 && - (currentPage <= 4 || currentPage >= totalPages - 2) && - currentPage !== totalPages - 2 && ( - pageChangeHandler(3)} - /> - )} - - {totalPages >= 7 && - (currentPage <= 2 || currentPage >= totalPages - 2) && ( - = totalPages - 1 - ? 4 - : 1 - } - endPage={ - currentPage <= 2 || currentPage >= totalPages - 1 - ? totalPages - 3 - : currentPage === totalPages - 2 - ? totalPages - 4 - : 2 - } - onPageItemClick={pageChangeHandler} - /> - )} - - {totalPages >= 3 && - currentPage > 4 && - currentPage < totalPages - 1 && ( - pageChangeHandler(currentPage - 1)} - /> - )} - - {totalPages >= 7 && - currentPage > 3 && - currentPage < totalPages - 2 && ( - - )} - - {totalPages >= 5 && - currentPage > 2 && - currentPage < totalPages - 2 && ( - pageChangeHandler(currentPage + 1)} - /> - )} - - {totalPages >= 5 && - (currentPage <= 2 || currentPage >= totalPages - 2) && ( - pageChangeHandler(totalPages - 2)} - /> - )} - - {totalPages >= 6 && - currentPage > 2 && - currentPage < totalPages - 3 && ( - = 4 - ? currentPage + 2 - : 1 - } - endPage={ - currentPage <= 3 - ? totalPages - 2 - : currentPage >= 4 - ? totalPages - 1 - : 0 - } - onPageItemClick={pageChangeHandler} - /> - )} - - {totalPages >= 6 && - (currentPage <= 3 || currentPage >= totalPages - 3) && ( - pageChangeHandler(totalPages - 1)} - /> - )} - - {totalPages >= 7 && ( + {totalPages > 7 && ( + <> pageChangeHandler(totalPages)} + content={1} + disabled={currentPage === 1} + onClick={() => pageChangeHandler(1)} /> - )} -
    - )} - + +
    + +
    + +
    + +
    +
    + +
    + +
    -
    - +
    +
    + + + + +
    - +
    + + + +
    ); diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 5c76f44e..67920ef2 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -14,6 +14,7 @@ import { SortingState, OnChangeFn, Row, + HeaderContext, } from '@tanstack/react-table'; import { rankItem } from '@tanstack/match-sorter-utils'; import { Icon } from '@iconify/react'; @@ -41,6 +42,7 @@ export interface TableProps { data: TData[]; columns: ColumnDef[]; pageSize?: number; + onPageSizeChange?: (pageSize: number) => void; totalItems?: number; page?: number; onPageChange?: (page: number) => void; @@ -56,8 +58,8 @@ export interface TableProps { setRowSelection?: OnChangeFn>; enableRowSelection?: boolean | ((row: Row) => boolean); renderFooter?: boolean; - footerContent?: ReactNode; - footerData?: TData[]; + withCheckbox?: boolean; + rowOptions?: number[]; } const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}]; @@ -70,31 +72,35 @@ const emptyContentDefaultValue = (
    ); +const TABLE_DEFAULT_STYLING = { + containerClassName: 'w-full mb-20', + tableWrapperClassName: + 'overflow-x-auto border border-solid border-base-content/10 rounded-lg', + tableClassName: 'font-inter w-full table-auto text-sm font-medium', + tableHeaderClassName: '', + headerRowClassName: '', + headerColumnClassName: 'px-4 py-3 text-base-content/50', + tableBodyClassName: '', + bodyRowClassName: 'border-t border-t-base-content/10', + bodyColumnClassName: 'px-4 py-3 text-base-content', + paginationClassName: '', + tableFooterClassName: 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: 'px-6 py-3 text-xs text-gray-900', +}; + const Table = ({ data = [], columns = [], pageSize = 10, + onPageSizeChange, totalItems, page, onPageChange, isLoading = false, fuzzySearchValue, onFuzzySearchValueChange, - className = { - containerClassName: '', - tableWrapperClassName: '', - tableClassName: '', - tableHeaderClassName: '', - headerRowClassName: '', - headerColumnClassName: '', - tableBodyClassName: '', - bodyRowClassName: '', - bodyColumnClassName: '', - tableFooterClassName: '', - footerRowClassName: '', - footerColumnClassName: '', - paginationClassName: '', - }, + className = TABLE_DEFAULT_STYLING, emptyContent = emptyContentDefaultValue, sorting, setSorting, @@ -103,14 +109,19 @@ const Table = ({ setRowSelection, enableRowSelection, renderFooter = false, - footerContent, - footerData = [], + withCheckbox = false, + rowOptions = [10, 20, 50, 100], }: TableProps) => { const isServerSideTable = totalItems !== undefined && page !== undefined && onPageChange !== undefined; + const tableClassNames = { + ...TABLE_DEFAULT_STYLING, + ...className, + }; + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: pageSize, @@ -172,14 +183,6 @@ const Table = ({ const table = useReactTable(tableOptions); const { setPageSize } = table; - const footerTableOptions: TableOptions = { - columns, - data: footerData, - getCoreRowModel: getCoreRowModel(), - }; - - const footerTable = useReactTable(footerTableOptions); - const prevPageClickHandler = () => { table.previousPage(); @@ -211,68 +214,104 @@ const Table = ({ }, [pageSize, setPageSize]); return ( -
    -
    -
- +
+
+
+ {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - + {headerGroup.headers.map((header) => { + const columnRelativeDepth = + header.depth - header.column.depth; + if ( + !header.isPlaceholder && + columnRelativeDepth > 1 && + header.id === header.column.id + ) { + return null; + } + let rowSpan = 1; + if (header.isPlaceholder) { + const leafs = header.getLeafHeaders(); + rowSpan = leafs[leafs.length - 1].depth - header.depth; + } + return ( + - ))} + {header.column.getCanSort() && ( +
+ + +
+ )} + + + ); + })} ))} - + {table.getRowModel().rows.map((row) => ( - + {row.getVisibleCells().map((cell) => ( - - {renderFooter && - (footerData && footerData.length > 0 - ? footerTable.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - )) - : footerContent)} + {renderFooter && ( + + {table.getAllLeafColumns().map((column) => ( + + ))} + + )}
-
- {flexRender( - header.column.columnDef.header, - header.getContext() +
+
1, + })} + > + {flexRender( + header.column.columnDef.header, + header.getContext() + )} - {header.column.getCanSort() && ( -
- - -
- )} -
-
+ {!isLoading && flexRender(cell.column.columnDef.cell, cell.getContext())} @@ -283,24 +322,23 @@ const Table = ({ ))}
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
+ {column.columnDef.footer && + flexRender(column.columnDef.footer, { + column, + header: column.columnDef, + table, + } as HeaderContext)} +
@@ -310,7 +348,7 @@ const Table = ({ emptyContent} {data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && ( -
+
({ onPrevPage={prevPageClickHandler} onNextPage={nextPageClickHandler} onPageChange={pageChangeHandler} + rowOptions={rowOptions} + onRowChange={onPageSizeChange} />
)} diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index f0810f15..c2f39845 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -16,10 +16,6 @@ interface SalesReportTableProps { initialValues?: BaseClosingSales; } -interface FooterSalesRow extends BaseSales { - _isFooter: true; -} - const SalesReportTable = ({ type = 'detail', initialValues, @@ -72,29 +68,6 @@ const SalesReportTable = ({ }; }, [salesData]); - const footerData = useMemo((): FooterSalesRow[] => { - if (salesData.length === 0) return []; - - const footerRow: FooterSalesRow = { - id: -999, - realization_date: 'Total Penjualan', - age: 0, - do_number: '', - product: {} as Product, - customer: {} as Customer, - qty: totals.totalQuantity, - weight: totals.totalWeight, - avg_weight: totals.avgWeight, - price: totals.avgPricePartner, - total_price: totals.totalPartner, - kandang: {} as Kandang, - payment_status: '', - _isFooter: true, - }; - - return [footerRow]; - }, [salesData, totals]); - const salesColumns: ColumnDef[] = useMemo( () => [ { @@ -102,43 +75,30 @@ const SalesReportTable = ({ accessorKey: 'realization_date', header: 'Tanggal Realisasi', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) { - return ( -
- {props.row.original.realization_date} -
- ); - } const date = props.row.original.realization_date; return date ? formatDate(date, 'DD MMM YYYY') : '-'; }, + footer: () => ( +
Total Penjualan
+ ), }, { id: 'age', accessorKey: 'age', header: 'Umur', - cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - return isFooter ? null : props.getValue() || '-'; - }, + cell: (props) => props.getValue() || '-', }, { id: 'do_number', accessorKey: 'do_number', header: 'No. DO', - cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - return isFooter ? null : props.getValue() || '-'; - }, + cell: (props) => props.getValue() || '-', }, { id: 'product', accessorKey: 'product', header: 'Produk', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) return null; const product = props.getValue() as Product; return product?.name || '-'; }, @@ -148,47 +108,43 @@ const SalesReportTable = ({ accessorKey: 'customer', header: 'Customer', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) return null; const customer = props.getValue() as Customer; return customer?.name || '-'; }, }, { - id: 'qty', - accessorKey: 'qty', - header: 'Kuantitas', - cell: (props) => { - const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatNumber(value)} -
- ); - }, - }, - { - id: 'weight', - accessorKey: 'weight', - header: 'Kg', - cell: (props) => { - const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatNumber(value)} -
- ); - }, + id: 'jumlah', + header: 'Jumlah', + columns: [ + { + id: 'qty', + accessorKey: 'qty', + header: 'Kuantitas', + cell: (props) => { + const value = props.getValue() as number; + return
{formatNumber(value)}
; + }, + footer: () => ( +
+ {formatNumber(totals.totalQuantity)} +
+ ), + }, + { + id: 'weight', + accessorKey: 'weight', + header: 'Kg', + cell: (props) => { + const value = props.getValue() as number; + return
{formatNumber(value)}
; + }, + footer: () => ( +
+ {formatNumber(totals.totalWeight)} +
+ ), + }, + ], }, { id: 'avg_weight', @@ -196,17 +152,13 @@ const SalesReportTable = ({ header: 'AVG (Kg)', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatNumber(value)} -
- ); + return
{formatNumber(value)}
; }, + footer: () => ( +
+ {formatNumber(totals.avgWeight)} +
+ ), }, { id: 'price_partner', @@ -214,19 +166,13 @@ const SalesReportTable = ({ header: 'Harga Mitra (Rp)', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatCurrency(value)} -
- ); + return
{formatCurrency(value)}
; }, + footer: () => ( +
+ {formatCurrency(totals.avgPricePartner)} +
+ ), }, { id: 'total_mitra', @@ -234,19 +180,13 @@ const SalesReportTable = ({ header: 'Total Mitra (Rp)', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatCurrency(value)} -
- ); + return
{formatCurrency(value)}
; }, + footer: () => ( +
+ {formatCurrency(totals.totalPartner)} +
+ ), }, { id: 'price_act', @@ -254,18 +194,7 @@ const SalesReportTable = ({ header: 'Harga Act (Rp)', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatCurrency(value)} -
- ); + return
{formatCurrency(value)}
; }, }, { @@ -274,18 +203,7 @@ const SalesReportTable = ({ header: 'Total Act (Rp)', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatCurrency(value)} -
- ); + return
{formatCurrency(value)}
; }, }, { @@ -293,8 +211,6 @@ const SalesReportTable = ({ accessorKey: 'kandang', header: 'Kandang', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) return null; const kandang = props.getValue() as Kandang; return kandang?.name || '-'; }, @@ -304,9 +220,6 @@ const SalesReportTable = ({ accessorKey: 'payment_status', header: 'Status Pembayaran', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) return null; - const status = props.getValue() as string; const getStatusColor = (status: string) => { if (!status) return 'neutral'; @@ -345,16 +258,14 @@ const SalesReportTable = ({ 0} className={{ tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto text-sm', - headerRowClassName: 'border-b border-b-gray-200', headerColumnClassName: 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap', bodyRowClassName: - 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200', bodyColumnClassName: 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', tableFooterClassName: From 5a3c7d71b0ffcaccecca2e7c4c42267fa66a69c1 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 17:48:03 +0700 Subject: [PATCH 06/16] refactor(FE-345): HPP ekspedisi API path to use /closings --- src/services/api/closing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 8e2f784c..88cdc68c 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -31,7 +31,7 @@ export class ClosingApiService extends BaseApiService< id: number ): Promise | undefined> { try { - const getHppEkspedisiPath = `http://localhost:4010/api/closing/${id}/hpp-ekspedisi`; + const getHppEkspedisiPath = `http://localhost:4010/api/closings/${id}/hpp-ekspedisi`; return await this.customRequest>( getHppEkspedisiPath ); From 68dd5b1121727a08f729b7cec78952a024f1621e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 17:49:50 +0700 Subject: [PATCH 07/16] refactor(FE-344): Add table cell side borders in sales report --- src/components/pages/closing/sale/SalesReportTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index c2f39845..4544c376 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -263,9 +263,9 @@ const SalesReportTable = ({ tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto text-sm', headerColumnClassName: - 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap', + 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', bodyRowClassName: - 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200', + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', bodyColumnClassName: 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', tableFooterClassName: From 6a926f881dfd2c2810d3b7dffac8b824f77dfe6c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 17:51:11 +0700 Subject: [PATCH 08/16] feat(FE-334): Use column footers and fix closing loading check --- src/app/_closing/detail/page.tsx | 8 +-- .../CosExpeditionReportTable.tsx | 59 ++++--------------- 2 files changed, 15 insertions(+), 52 deletions(-) diff --git a/src/app/_closing/detail/page.tsx b/src/app/_closing/detail/page.tsx index 7fb3c01d..fd11eeee 100644 --- a/src/app/_closing/detail/page.tsx +++ b/src/app/_closing/detail/page.tsx @@ -51,11 +51,9 @@ const ClosingDetailPage = () => { } if ( - !isLoading || - !closing || - isResponseError(closing) || - !hpp_ekspedisi || - isResponseError(hpp_ekspedisi) + !isLoadingClosing && + (!closing || isResponseError(closing)) && + (!hpp_ekspedisi || isResponseError(hpp_ekspedisi)) ) { router.replace('/404'); return; diff --git a/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx index b5aac35f..8abe3c0a 100644 --- a/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx +++ b/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx @@ -15,10 +15,6 @@ interface CosExpeditionReportTableProps { initialValues?: BaseClosingCosExpedition; } -interface FooterCosExpeditionRow extends BaseCosExpedition { - _isFooter: true; -} - const CosExpeditionReportTable = ({ type = 'detail', initialValues, @@ -44,19 +40,6 @@ const CosExpeditionReportTable = ({ }; }, [cosExpeditionData]); - const footerData = useMemo((): FooterCosExpeditionRow[] => { - if (cosExpeditionData.length === 0) return []; - - const footerRow: FooterCosExpeditionRow = { - id: -999, - expedition_name: 'Total HPP Ekspedisi', - hpp: totals.totalHpp, - _isFooter: true, - }; - - return [footerRow]; - }, [cosExpeditionData, totals]); - const cosExpeditionColumns: ColumnDef[] = useMemo( () => [ { @@ -64,28 +47,17 @@ const CosExpeditionReportTable = ({ accessorKey: 'id', header: 'No', cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) { - return ( -
- {props.row.original.expedition_name} -
- ); - } - return props.getValue() || '-'; + return
{props.row.index + 1}
; }, + footer: () => ( +
Total HPP Ekspedisi
+ ), }, { id: 'expedition_name', accessorKey: 'expedition_name', header: 'Nama Ekspedisi', - cell: (props) => { - const isFooter = '_isFooter' in props.row.original; - if (isFooter) { - return null; - } - return props.getValue() || '-'; - }, + cell: (props) => props.getValue() || '-', }, { id: 'hpp', @@ -93,22 +65,16 @@ const CosExpeditionReportTable = ({ header: 'HPP Ekspedisi', cell: (props) => { const value = props.getValue() as number; - const isFooter = '_isFooter' in props.row.original; - return ( -
- {formatCurrency(value)} -
- ); + return
{formatCurrency(value)}
; }, + footer: () => ( +
+ {formatCurrency(totals.totalHpp)} +
+ ), }, ], - [] + [totals] ); return ( @@ -125,7 +91,6 @@ const CosExpeditionReportTable = ({
0} className={{ tableWrapperClassName: 'overflow-x-auto', From 1ef7130661802d12628b83a4512600bd064b2b29 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 9 Dec 2025 18:45:38 +0700 Subject: [PATCH 09/16] refactor(FE-344): Export TABLE_DEFAULT_STYLING and refine classes --- src/components/Table.tsx | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 67920ef2..9feb33e2 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -72,21 +72,22 @@ const emptyContentDefaultValue = ( ); -const TABLE_DEFAULT_STYLING = { +export const TABLE_DEFAULT_STYLING = { containerClassName: 'w-full mb-20', tableWrapperClassName: 'overflow-x-auto border border-solid border-base-content/10 rounded-lg', tableClassName: 'font-inter w-full table-auto text-sm font-medium', tableHeaderClassName: '', headerRowClassName: '', - headerColumnClassName: 'px-4 py-3 text-base-content/50', + headerColumnClassName: + 'px-4 py-3 border-base-content/10 text-base-content/50', tableBodyClassName: '', - bodyRowClassName: 'border-t border-t-base-content/10', + bodyRowClassName: 'border-t border-base-content/10', bodyColumnClassName: 'px-4 py-3 text-base-content', paginationClassName: '', - tableFooterClassName: 'bg-gray-100 font-semibold border border-gray-200', - footerRowClassName: 'border-t-2 border-gray-300', - footerColumnClassName: 'px-6 py-3 text-xs text-gray-900', + tableFooterClassName: 'font-semibold border-base-content/10', + footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10', + footerColumnClassName: 'p-4 text-base-content whitespace-nowrap', }; const Table = ({ @@ -251,13 +252,15 @@ const Table = ({ { 'first:w-9 first:pr-0': withCheckbox, }, + { + 'border-b': header.colSpan > 1, + }, tableClassNames.headerColumnClassName )} >
1, + className={cn('flex items-center gap-1 min-h-full', { + 'justify-center': header.colSpan > 1, })} > {flexRender( @@ -321,13 +324,16 @@ const Table = ({ ))} -
+ {renderFooter && ( - + {table.getAllLeafColumns().map((column) => (
{column.columnDef.footer && flexRender(column.columnDef.footer, { From 4262e8e286042833c6e0c5b8e09eea76129937c1 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 11 Dec 2025 14:27:29 +0700 Subject: [PATCH 10/16] refactor(FE-344): Add Cost of Revenue Expedition table --- .../pages/closing/ClosingDetail.tsx | 10 +- .../CosExpeditionReportTable.tsx | 119 ----------------- .../CostOfRevenueExpeditionReportTable.tsx | 123 ++++++++++++++++++ src/types/api/closing.d.ts | 16 +++ 4 files changed, 148 insertions(+), 120 deletions(-) delete mode 100644 src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx create mode 100644 src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index 11e28e32..ffcbdac2 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -10,20 +10,24 @@ import ClosingGeneralInformationTable from '@/components/pages/closing/ClosingGe import { ClosingGeneralInformation, BaseClosingSales, + BaseClosingCostOfRevenueExpedition, } from '@/types/api/closing'; import ClosingSapronakTabContent from './ClosingSapronakTabContent'; import SalesReportTable from './sale/SalesReportTable'; +import CostOfRevenueExpeditionReportTable from './hpp-ekspedisi/CostOfRevenueExpeditionReportTable'; interface ClosingDetailProps { id: number; initialValue?: ClosingGeneralInformation; salesData?: BaseClosingSales; + costOfRevenueExpeditionData?: BaseClosingCostOfRevenueExpedition; } const ClosingDetail: React.FC = ({ id, initialValue, salesData, + costOfRevenueExpeditionData, }) => { const [activeTab, setActiveTab] = useState('sapronak'); @@ -52,7 +56,11 @@ const ClosingDetail: React.FC = ({ { id: 'hppEkspedisi', label: 'HPP Ekspedisi', - content: 'HPP Ekspedisi', + content: ( + + ), }, { id: 'dataProduksi', diff --git a/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx deleted file mode 100644 index 8abe3c0a..00000000 --- a/src/components/pages/closing/hpp-ekspedisi/CosExpeditionReportTable.tsx +++ /dev/null @@ -1,119 +0,0 @@ -'use client'; - -import React, { useMemo } from 'react'; -import { ColumnDef } from '@tanstack/react-table'; -import Table from '@/components/Table'; -import Card from '@/components/Card'; -import { formatCurrency } from '@/lib/helper'; -import { - BaseClosingCosExpedition, - BaseCosExpedition, -} from '@/types/api/closing/closing'; - -interface CosExpeditionReportTableProps { - type?: 'detail'; - initialValues?: BaseClosingCosExpedition; -} - -const CosExpeditionReportTable = ({ - type = 'detail', - initialValues, -}: CosExpeditionReportTableProps) => { - const cosExpeditionData: BaseCosExpedition[] = useMemo(() => { - return initialValues?.cos_expeditions || []; - }, [initialValues]); - - const totals = useMemo(() => { - if (cosExpeditionData.length === 0) { - return { - totalHpp: 0, - }; - } - - const totalHpp = cosExpeditionData.reduce( - (sum, item) => sum + (item.hpp || 0), - 0 - ); - - return { - totalHpp, - }; - }, [cosExpeditionData]); - - const cosExpeditionColumns: ColumnDef[] = useMemo( - () => [ - { - id: 'id', - accessorKey: 'id', - header: 'No', - cell: (props) => { - return
{props.row.index + 1}
; - }, - footer: () => ( -
Total HPP Ekspedisi
- ), - }, - { - id: 'expedition_name', - accessorKey: 'expedition_name', - header: 'Nama Ekspedisi', - cell: (props) => props.getValue() || '-', - }, - { - id: 'hpp', - accessorKey: 'hpp', - header: 'HPP Ekspedisi', - cell: (props) => { - const value = props.getValue() as number; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(totals.totalHpp)} -
- ), - }, - ], - [totals] - ); - - return ( - <> -
-
-

HPP Ekspedisi

- - 0} - className={{ - tableWrapperClassName: 'overflow-x-auto', - tableClassName: 'w-full table-auto text-sm', - headerRowClassName: 'border-b border-b-gray-200', - headerColumnClassName: - 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap', - bodyRowClassName: - 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', - bodyColumnClassName: - 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', - tableFooterClassName: - 'bg-gray-100 font-semibold border border-gray-200', - footerRowClassName: 'border-t-2 border-gray-300', - footerColumnClassName: - 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', - }} - /> - - - - - ); -}; - -export default CosExpeditionReportTable; diff --git a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx new file mode 100644 index 00000000..4ca0b5ac --- /dev/null +++ b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx @@ -0,0 +1,123 @@ +'use client'; + +import React, { useMemo } from 'react'; +import { ColumnDef } from '@tanstack/react-table'; +import Table from '@/components/Table'; +import Card from '@/components/Card'; +import { formatCurrency } from '@/lib/helper'; +import { + BaseClosingCostOfRevenueExpedition, + BaseCostOfRevenueExpedition, +} from '@/types/api/closing'; + +interface CostOfRevenueExpeditionReportTableProps { + type?: 'detail'; + initialValues?: BaseClosingCostOfRevenueExpedition; +} + +const CostOfRevenueExpeditionReportTable = ({ + type = 'detail', + initialValues, +}: CostOfRevenueExpeditionReportTableProps) => { + const costOfRevenueExpeditionData: BaseCostOfRevenueExpedition[] = + useMemo(() => { + return initialValues?.cos_expeditions || []; + }, [initialValues]); + + const totals = useMemo(() => { + if (costOfRevenueExpeditionData.length === 0) { + return { + totalHpp: 0, + }; + } + + const totalHpp = costOfRevenueExpeditionData.reduce( + (sum, item) => sum + (item.hpp || 0), + 0 + ); + + return { + totalHpp, + }; + }, [costOfRevenueExpeditionData]); + + const costOfRevenueExpeditionColumns: ColumnDef[] = + useMemo( + () => [ + { + id: 'id', + accessorKey: 'id', + header: 'No', + cell: (props) => { + return
{props.row.index + 1}
; + }, + footer: () => ( +
+ Total HPP Ekspedisi +
+ ), + }, + { + id: 'expedition_name', + accessorKey: 'expedition_name', + header: 'Nama Ekspedisi', + cell: (props) => props.getValue() || '-', + }, + { + id: 'hpp', + accessorKey: 'hpp', + header: 'HPP Ekspedisi', + cell: (props) => { + const value = props.getValue() as number; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(totals.totalHpp)} +
+ ), + }, + ], + [totals] + ); + + return ( + <> +
+
+

HPP Ekspedisi

+ +
0} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + tableFooterClassName: + 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + }} + /> + + + + + ); +}; + +export default CostOfRevenueExpeditionReportTable; diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 95b2f57f..6f8336c2 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -47,6 +47,22 @@ export type BaseClosing = { export type Closing = BaseMetadata & BaseClosing; +export type BaseCostOfRevenueExpedition = { + id: number; + expedition_name: string; + hpp: number; +}; + +export type BaseClosingCostOfRevenueExpedition = { + project_type: string; + flock_id: number; + period: number; + cos_expeditions: BaseCostOfRevenueExpedition[]; +}; + +export type ClosingCostOfRevenueExpedition = BaseMetadata & + BaseClosingCostOfRevenueExpedition; + export type BaseClosingGeneralInformation = BaseClosing & { flock_id: number; period: number; From 38b91a57f0bab6a81bc15feabb0e7f83660153d2 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 11 Dec 2025 14:43:54 +0700 Subject: [PATCH 11/16] refactor(FE-345): Remove unused fields from BaseClosingSales --- src/types/api/closing.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 6f8336c2..c3e1c280 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -24,9 +24,6 @@ export type BaseSales = { }; export type BaseClosingSales = { - project_type: string; - flock_id: number; - period: number; sales: BaseSales[]; }; From bd8d1211135ce032d6f45137278861e297d3130d Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 11 Dec 2025 15:04:37 +0700 Subject: [PATCH 12/16] feat(FE-344,345): Integrate HPP Ekspedisi into Closing detail --- src/app/closing/detail/page.tsx | 12 ++- .../CostOfRevenueExpeditionReportTable.tsx | 23 ++---- src/services/api/closing.ts | 23 ++++++ src/types/api/closing.d.ts | 74 ++++++++++--------- 4 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/app/closing/detail/page.tsx b/src/app/closing/detail/page.tsx index 1b4ebc45..f85dfbe3 100644 --- a/src/app/closing/detail/page.tsx +++ b/src/app/closing/detail/page.tsx @@ -24,6 +24,11 @@ const ClosingDetailPage = () => { () => ClosingApi.getPenjualan(Number(closingId)) ); + const { data: hppEkspedisiData, isLoading: isLoadingHppEkspedisi } = useSWR( + closingId ? `hpp-ekspedisi-${closingId}` : null, + () => ClosingApi.getHppEkspedisi(Number(closingId)) + ); + if (!closingId) { router.back(); @@ -39,7 +44,7 @@ const ClosingDetailPage = () => { return; } - const isLoading = isLoadingClosing || isLoadingSales; + const isLoading = isLoadingClosing || isLoadingSales || isLoadingHppEkspedisi; return (
@@ -50,6 +55,11 @@ const ClosingDetailPage = () => { id={Number(closingId)} initialValue={closing.data} salesData={isResponseSuccess(salesData) ? salesData.data : undefined} + costOfRevenueExpeditionData={ + isResponseSuccess(hppEkspedisiData) + ? hppEkspedisiData.data + : undefined + } /> )}
diff --git a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx index 4ca0b5ac..57cf70eb 100644 --- a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx +++ b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx @@ -21,25 +21,16 @@ const CostOfRevenueExpeditionReportTable = ({ }: CostOfRevenueExpeditionReportTableProps) => { const costOfRevenueExpeditionData: BaseCostOfRevenueExpedition[] = useMemo(() => { - return initialValues?.cos_expeditions || []; + return initialValues?.expedition_costs || []; }, [initialValues]); const totals = useMemo(() => { - if (costOfRevenueExpeditionData.length === 0) { - return { - totalHpp: 0, - }; - } - - const totalHpp = costOfRevenueExpeditionData.reduce( - (sum, item) => sum + (item.hpp || 0), - 0 - ); + const totalHpp = initialValues?.total_hpp_amount || 0; return { totalHpp, }; - }, [costOfRevenueExpeditionData]); + }, [initialValues]); const costOfRevenueExpeditionColumns: ColumnDef[] = useMemo( @@ -58,14 +49,14 @@ const CostOfRevenueExpeditionReportTable = ({ ), }, { - id: 'expedition_name', - accessorKey: 'expedition_name', + id: 'expedition_vendor_name', + accessorKey: 'expedition_vendor_name', header: 'Nama Ekspedisi', cell: (props) => props.getValue() || '-', }, { - id: 'hpp', - accessorKey: 'hpp', + id: 'hpp_amount', + accessorKey: 'hpp_amount', header: 'HPP Ekspedisi', cell: (props) => { const value = props.getValue() as number; diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 6ce32995..90b0ce5c 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -6,6 +6,7 @@ import { ClosingGeneralInformation, ClosingIncomingSapronak, ClosingOutgoingSapronak, + ClosingCostOfRevenueExpedition, } from '@/types/api/closing'; import { httpClient, httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; @@ -33,6 +34,28 @@ export class ClosingApiService extends BaseApiService { } } + async getHppEkspedisi( + id: number + ): Promise | undefined> { + try { + const getHppEkspedisiPath = `${this.basePath}/${id}/hpp-ekspedisi`; + const getHppEkspedisiRes = + await httpClient>( + getHppEkspedisiPath + ); + return getHppEkspedisiRes; + } catch (error) { + if ( + axios.isAxiosError>( + error + ) + ) { + return error.response?.data; + } + return undefined; + } + } + async getAllIncomingSapronakFetcher( endpoint: string ): Promise> { diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index c3e1c280..e55cb317 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -7,26 +7,6 @@ import { Product } from '@type/api/master-data/product'; import { Customer } from '@type/api/master-data/customer'; import { BaseMetadata } from '@/types/api/api-general'; -export type BaseSales = { - id: number; - realization_date: string; - age: number; - do_number: string; - product: Product; - customer: Customer; - qty: number; - weight: number; - avg_weight: number; - price: number; - total_price: number; - kandang: Kandang; - payment_status: string; -}; - -export type BaseClosingSales = { - sales: BaseSales[]; -}; - export type BaseClosing = { id: number; location_id: number; @@ -44,22 +24,6 @@ export type BaseClosing = { export type Closing = BaseMetadata & BaseClosing; -export type BaseCostOfRevenueExpedition = { - id: number; - expedition_name: string; - hpp: number; -}; - -export type BaseClosingCostOfRevenueExpedition = { - project_type: string; - flock_id: number; - period: number; - cos_expeditions: BaseCostOfRevenueExpedition[]; -}; - -export type ClosingCostOfRevenueExpedition = BaseMetadata & - BaseClosingCostOfRevenueExpedition; - export type BaseClosingGeneralInformation = BaseClosing & { flock_id: number; period: number; @@ -91,4 +55,42 @@ export type ClosingIncomingSapronak = { }; export type ClosingOutgoingSapronak = ClosingIncomingSapronak; + +export type BaseSales = { + id: number; + realization_date: string; + age: number; + do_number: string; + product: Product; + customer: Customer; + qty: number; + weight: number; + avg_weight: number; + price: number; + total_price: number; + kandang: Kandang; + payment_status: string; +}; + +export type BaseClosingSales = { + sales: BaseSales[]; +}; + export type ClosingSales = BaseMetadata & BaseClosingSales; + +export type BaseCostOfRevenueExpedition = { + id: number; + expedition_vendor_id: number; + expedition_vendor_name: string; + qty: number; + unit_price: number; + hpp_amount: number; +}; + +export type BaseClosingCostOfRevenueExpedition = { + expedition_costs: BaseCostOfRevenueExpedition[]; + total_hpp_amount: number; +}; + +export type ClosingCostOfRevenueExpedition = BaseMetadata & + BaseClosingCostOfRevenueExpedition; From 4a8f2b1e1d8d23327e40579f53c8efba874857f2 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 18 Dec 2025 15:49:01 +0700 Subject: [PATCH 13/16] feat(FE-345): Add HPP expedition types and API method --- src/services/api/closing.ts | 23 +++++++++++++++++++++++ src/types/api/closing.d.ts | 17 +++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 9bd8e7af..86091bac 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -8,6 +8,7 @@ import { ClosingOutgoingSapronak, ClosingOverhead, ClosingSapronakCalculation, + ClosingCostOfRevenueExpedition, } from '@/types/api/closing'; import { httpClient, httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; @@ -194,6 +195,28 @@ export class ClosingApiService extends BaseApiService { return undefined; } } + + async getHppEkspedisi( + id: number + ): Promise | undefined> { + try { + const getHppEkspedisiPath = `${this.basePath}/${id}/hpp-ekspedisi`; + const getHppEkspedisiRes = + await httpClient>( + getHppEkspedisiPath + ); + return getHppEkspedisiRes; + } catch (error) { + if ( + axios.isAxiosError>( + error + ) + ) { + return error.response?.data; + } + return undefined; + } + } } export const ClosingApi = new ClosingApiService('/closings'); diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 42f870bd..f5c8c507 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -142,3 +142,20 @@ export type OverheadTotal = { actual_total_amount: number; cost_per_bird: number; }; + +export type BaseCostOfRevenueExpedition = { + id: number; + expedition_vendor_id: number; + expedition_vendor_name: string; + qty: number; + unit_price: number; + hpp_amount: number; +}; + +export type BaseClosingCostOfRevenueExpedition = { + expedition_costs: BaseCostOfRevenueExpedition[]; + total_hpp_amount: number; +}; + +export type ClosingCostOfRevenueExpedition = BaseMetadata & + BaseClosingCostOfRevenueExpedition; From 447b8067f74a6fcda40eeda95356dd4fd3433deb Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 18 Dec 2025 15:55:20 +0700 Subject: [PATCH 14/16] refactor(FE-345): Rename hpp-ekspedisi endpoint to expedition-hpp --- src/services/api/closing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 86091bac..4426e7e7 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -200,7 +200,7 @@ export class ClosingApiService extends BaseApiService { id: number ): Promise | undefined> { try { - const getHppEkspedisiPath = `${this.basePath}/${id}/hpp-ekspedisi`; + const getHppEkspedisiPath = `${this.basePath}/${id}/expedition-hpp`; const getHppEkspedisiRes = await httpClient>( getHppEkspedisiPath From 8dc23f83cdd173c8ac69f92ec5efed0751ee5953 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 18 Dec 2025 16:26:09 +0700 Subject: [PATCH 15/16] refactor(FE-335): Rename expedition HPP types and update usages --- .../CostOfRevenueExpeditionReportTable.tsx | 16 ++++++---------- src/services/api/closing.ts | 12 ++++-------- src/types/api/closing.d.ts | 12 ++++-------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx index 57cf70eb..0e344599 100644 --- a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx +++ b/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx @@ -5,24 +5,20 @@ import { ColumnDef } from '@tanstack/react-table'; import Table from '@/components/Table'; import Card from '@/components/Card'; import { formatCurrency } from '@/lib/helper'; -import { - BaseClosingCostOfRevenueExpedition, - BaseCostOfRevenueExpedition, -} from '@/types/api/closing'; +import { BaseHppExpedition, BaseExpeditionCost } from '@/types/api/closing'; interface CostOfRevenueExpeditionReportTableProps { type?: 'detail'; - initialValues?: BaseClosingCostOfRevenueExpedition; + initialValues?: BaseHppExpedition; } const CostOfRevenueExpeditionReportTable = ({ type = 'detail', initialValues, }: CostOfRevenueExpeditionReportTableProps) => { - const costOfRevenueExpeditionData: BaseCostOfRevenueExpedition[] = - useMemo(() => { - return initialValues?.expedition_costs || []; - }, [initialValues]); + const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => { + return initialValues?.expedition_costs || []; + }, [initialValues]); const totals = useMemo(() => { const totalHpp = initialValues?.total_hpp_amount || 0; @@ -32,7 +28,7 @@ const CostOfRevenueExpeditionReportTable = ({ }; }, [initialValues]); - const costOfRevenueExpeditionColumns: ColumnDef[] = + const costOfRevenueExpeditionColumns: ColumnDef[] = useMemo( () => [ { diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 4426e7e7..e0b0bf89 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -8,7 +8,7 @@ import { ClosingOutgoingSapronak, ClosingOverhead, ClosingSapronakCalculation, - ClosingCostOfRevenueExpedition, + ClosingHppExpedition, } from '@/types/api/closing'; import { httpClient, httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; @@ -198,20 +198,16 @@ export class ClosingApiService extends BaseApiService { async getHppEkspedisi( id: number - ): Promise | undefined> { + ): Promise | undefined> { try { const getHppEkspedisiPath = `${this.basePath}/${id}/expedition-hpp`; const getHppEkspedisiRes = - await httpClient>( + await httpClient>( getHppEkspedisiPath ); return getHppEkspedisiRes; } catch (error) { - if ( - axios.isAxiosError>( - error - ) - ) { + if (axios.isAxiosError>(error)) { return error.response?.data; } return undefined; diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index f5c8c507..bc3cb0bc 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -143,19 +143,15 @@ export type OverheadTotal = { cost_per_bird: number; }; -export type BaseCostOfRevenueExpedition = { +export type BaseExpeditionCost = { id: number; - expedition_vendor_id: number; expedition_vendor_name: string; - qty: number; - unit_price: number; hpp_amount: number; }; -export type BaseClosingCostOfRevenueExpedition = { - expedition_costs: BaseCostOfRevenueExpedition[]; +export type BaseHppExpedition = { + expedition_costs: BaseExpeditionCost[]; total_hpp_amount: number; }; -export type ClosingCostOfRevenueExpedition = BaseMetadata & - BaseClosingCostOfRevenueExpedition; +export type ClosingHppExpedition = BaseMetadata & BaseHppExpedition; From 8ceca2cc59e0817e28a8c38f58d53734ba8a29bf Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 18 Dec 2025 16:38:39 +0700 Subject: [PATCH 16/16] refactor(FE-345): Rename CostOfRevenueExpedition to HppExpedition --- src/app/closing/detail/page.tsx | 2 +- src/components/pages/closing/ClosingDetail.tsx | 14 +++++--------- ...eportTable.tsx => HppExpeditionReportTable.tsx} | 8 ++++---- 3 files changed, 10 insertions(+), 14 deletions(-) rename src/components/pages/closing/hpp-ekspedisi/{CostOfRevenueExpeditionReportTable.tsx => HppExpeditionReportTable.tsx} (94%) diff --git a/src/app/closing/detail/page.tsx b/src/app/closing/detail/page.tsx index f85dfbe3..62f3fa20 100644 --- a/src/app/closing/detail/page.tsx +++ b/src/app/closing/detail/page.tsx @@ -55,7 +55,7 @@ const ClosingDetailPage = () => { id={Number(closingId)} initialValue={closing.data} salesData={isResponseSuccess(salesData) ? salesData.data : undefined} - costOfRevenueExpeditionData={ + hppExpeditionData={ isResponseSuccess(hppEkspedisiData) ? hppEkspedisiData.data : undefined diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index 17787591..336047e2 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -10,26 +10,26 @@ import ClosingGeneralInformationTable from '@/components/pages/closing/ClosingGe import { ClosingGeneralInformation, BaseClosingSales, - BaseClosingCostOfRevenueExpedition, + ClosingHppExpedition, } from '@/types/api/closing'; import ClosingSapronakTabContent from './ClosingSapronakTabContent'; import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent'; import ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverheadTabContent'; import SalesReportTable from './sale/SalesReportTable'; -import CostOfRevenueExpeditionReportTable from './hpp-ekspedisi/CostOfRevenueExpeditionReportTable'; +import HppExpeditionReportTable from './hpp-ekspedisi/HppExpeditionReportTable'; interface ClosingDetailProps { id: number; initialValue?: ClosingGeneralInformation; salesData?: BaseClosingSales; - costOfRevenueExpeditionData?: BaseClosingCostOfRevenueExpedition; + hppExpeditionData?: ClosingHppExpedition; } const ClosingDetail: React.FC = ({ id, initialValue, salesData, - costOfRevenueExpeditionData, + hppExpeditionData, }) => { const [activeTab, setActiveTab] = useState('sapronak'); @@ -58,11 +58,7 @@ const ClosingDetail: React.FC = ({ { id: 'hppEkspedisi', label: 'HPP Ekspedisi', - content: ( - - ), + content: , }, { id: 'dataProduksi', diff --git a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx b/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx similarity index 94% rename from src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx rename to src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx index 0e344599..f683ec58 100644 --- a/src/components/pages/closing/hpp-ekspedisi/CostOfRevenueExpeditionReportTable.tsx +++ b/src/components/pages/closing/hpp-ekspedisi/HppExpeditionReportTable.tsx @@ -7,15 +7,15 @@ import Card from '@/components/Card'; import { formatCurrency } from '@/lib/helper'; import { BaseHppExpedition, BaseExpeditionCost } from '@/types/api/closing'; -interface CostOfRevenueExpeditionReportTableProps { +interface HppExpeditionReportTableProps { type?: 'detail'; initialValues?: BaseHppExpedition; } -const CostOfRevenueExpeditionReportTable = ({ +const HppExpeditionReportTable = ({ type = 'detail', initialValues, -}: CostOfRevenueExpeditionReportTableProps) => { +}: HppExpeditionReportTableProps) => { const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => { return initialValues?.expedition_costs || []; }, [initialValues]); @@ -107,4 +107,4 @@ const CostOfRevenueExpeditionReportTable = ({ ); }; -export default CostOfRevenueExpeditionReportTable; +export default HppExpeditionReportTable;