mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-181-179-220): Slicing UI, Client Side Validation and API Integration for Delivery Order
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import Table from '@/components/Table';
|
||||
import { DeliveryOrderProductFormValues } from '../repeater/delivery-order/DeliverOrderProduct.schema';
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
import * as TanStack from '@tanstack/react-table';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import {
|
||||
cn,
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatNumber,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { SalesOrderProductFormValues } from '../repeater/sales-order/SalesOrderProduct.schema';
|
||||
|
||||
type DeliveryOrderProductTableProps = {
|
||||
data: DeliveryOrderProductFormValues[];
|
||||
salesOrder: SalesOrderProductFormValues[];
|
||||
formType?: 'add' | 'edit' | 'deliver';
|
||||
rowSelection: Record<string, boolean>;
|
||||
setRowSelection: React.Dispatch<
|
||||
React.SetStateAction<Record<string, boolean>>
|
||||
>;
|
||||
selectedRowIds: number[];
|
||||
onDelete: (id: number) => void;
|
||||
onEdit: (id: number) => void;
|
||||
onBulkDelete: () => void;
|
||||
onAddProductClick: () => void;
|
||||
};
|
||||
|
||||
const DeliveryOrderProductTable = ({
|
||||
data,
|
||||
salesOrder,
|
||||
formType,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
selectedRowIds,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onBulkDelete,
|
||||
onAddProductClick,
|
||||
}: DeliveryOrderProductTableProps) => {
|
||||
const onDeleteRef = useRef(onDelete);
|
||||
const onEditRef = useRef(onDelete);
|
||||
onDeleteRef.current = onDelete;
|
||||
onEditRef.current = onEdit;
|
||||
|
||||
const canAddData = salesOrder.reduce((acc, curr) => {
|
||||
const deliveredQty = data.filter(
|
||||
(deliveryItem) => deliveryItem.marketing_product_id == curr.id
|
||||
);
|
||||
return acc && deliveredQty.length != salesOrder.length;
|
||||
}, true);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({
|
||||
table,
|
||||
}: {
|
||||
table: TanStack.Table<DeliveryOrderProductFormValues>;
|
||||
}) => (
|
||||
<div className='w-full flex flex-row justify-center'>
|
||||
<CheckboxInput
|
||||
name='allRow'
|
||||
checked={table.getIsAllRowsSelected()}
|
||||
indeterminate={table.getIsSomeRowsSelected()}
|
||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({
|
||||
row,
|
||||
}: {
|
||||
row: TanStack.Row<DeliveryOrderProductFormValues>;
|
||||
}) => (
|
||||
<div>
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
|
||||
header: 'No. Pengiriman',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) => props.row.original.do_number ?? '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatDate(row.delivery_date as string, 'DD MMM YYYY'),
|
||||
header: 'Tanggal Delivery',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatVechicleNumber(row.vehicle_number as string),
|
||||
header: 'No. Polisi',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
row.marketing_product?.kandang?.label,
|
||||
header: 'Kandang',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
row.marketing_product?.product_warehouse?.label,
|
||||
header: 'Produk',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatCurrency(parseFloat(row.unit_price as string)),
|
||||
header: 'Harga Satuan (Rp)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.total_weight as string)),
|
||||
header: 'Total Bobot (Kg)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.qty as string)),
|
||||
header: 'Kuantitas',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.avg_weight as string)),
|
||||
header: 'Avg. Bobot (Kg)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
formatCurrency(parseFloat(row.total_price as string)),
|
||||
header: 'Total Penjualan (Rp)',
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) => (
|
||||
<div className='flex flex-row gap-1 items-center justify-end h-full mt-2'>
|
||||
<Button
|
||||
color='success'
|
||||
className='p-1'
|
||||
onClick={() => onEditRef.current(props.row.original.id as number)}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='mdi:edit' width={16} height={16} />
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
className='p-1'
|
||||
onClick={() =>
|
||||
onDeleteRef.current(props.row.original.id as number)
|
||||
}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table<DeliveryOrderProductFormValues>
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
data={data}
|
||||
columns={columns}
|
||||
className={{
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-2 py-2 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end first:flex first:flex-row first:justify-start',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-2 py-2 last:flex last:flex-row last:justify-end first:flex first:flex-row first:justify-start',
|
||||
paginationClassName: 'hidden',
|
||||
}}
|
||||
emptyContent={
|
||||
<div
|
||||
className={cn(
|
||||
'w-full h-16 flex flex-col justify-center items-center gap-2'
|
||||
)}
|
||||
>
|
||||
<span className='text-gray-500'>Belum ada data pengiriman</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className='flex flex-row gap-3 mt-3'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onAddProductClick}
|
||||
disabled={!canAddData}
|
||||
>
|
||||
<Icon icon='mdi:plus' width={16} height={16} />
|
||||
Tambah Pengiriman
|
||||
</Button>
|
||||
{selectedRowIds.length > 0 && (
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
color='error'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onBulkDelete}
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
Hapus
|
||||
{selectedRowIds.length > 0
|
||||
? ` (${selectedRowIds.length})`
|
||||
: ''}{' '}
|
||||
Pengiriman
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeliveryOrderProductTable;
|
||||
@@ -16,6 +16,7 @@ import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
|
||||
type SalesOrderProductTableProps = {
|
||||
data: SalesOrderProductFormValues[];
|
||||
formType?: 'add' | 'edit' | 'deliver';
|
||||
rowSelection: Record<string, boolean>;
|
||||
setRowSelection: React.Dispatch<
|
||||
React.SetStateAction<Record<string, boolean>>
|
||||
@@ -28,6 +29,7 @@ type SalesOrderProductTableProps = {
|
||||
|
||||
const SalesOrderProductTable = ({
|
||||
data,
|
||||
formType,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
selectedRowIds,
|
||||
@@ -137,7 +139,13 @@ const SalesOrderProductTable = ({
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
data={data}
|
||||
columns={columns}
|
||||
columns={
|
||||
formType == 'deliver'
|
||||
? columns.filter(
|
||||
(col) => col.header != 'Aksi' && col.id != 'select'
|
||||
)
|
||||
: columns
|
||||
}
|
||||
className={{
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
@@ -159,33 +167,35 @@ const SalesOrderProductTable = ({
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className='flex flex-row gap-3 mt-3'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onAddProductClick}
|
||||
>
|
||||
<Icon icon='mdi:plus' width={16} height={16} />
|
||||
Tambah Produk
|
||||
</Button>
|
||||
{selectedRowIds.length > 0 && (
|
||||
{formType != 'deliver' && (
|
||||
<div className='flex flex-row gap-3 mt-3'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
color='error'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onBulkDelete}
|
||||
onClick={onAddProductClick}
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
Hapus
|
||||
{selectedRowIds.length > 0
|
||||
? ` (${selectedRowIds.length})`
|
||||
: ''}{' '}
|
||||
Produk
|
||||
<Icon icon='mdi:plus' width={16} height={16} />
|
||||
Tambah Produk
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{selectedRowIds.length > 0 && (
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
color='error'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onBulkDelete}
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
Hapus
|
||||
{selectedRowIds.length > 0
|
||||
? ` (${selectedRowIds.length})`
|
||||
: ''}{' '}
|
||||
Produk
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user