mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +00:00
fix(resolve): fix resolve merge
This commit is contained in:
Generated
+11
-16
@@ -13,7 +13,6 @@
|
|||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"inputmask": "^5.0.9",
|
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
@@ -31,7 +30,6 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/inputmask": "^5.0.7",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -1639,13 +1637,6 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/inputmask": {
|
|
||||||
"version": "5.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/inputmask/-/inputmask-5.0.7.tgz",
|
|
||||||
"integrity": "sha512-uojbVPWzBQ/n/0jc/d16fLqmGasFIptbrLD2WrCPWArlk+5PgblOqH4EDkI3AoobXLAlOK5yF01V8jMmvMG5qg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1681,6 +1672,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -1750,6 +1742,7 @@
|
|||||||
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.2",
|
"@typescript-eslint/scope-manager": "8.46.2",
|
||||||
"@typescript-eslint/types": "8.46.2",
|
"@typescript-eslint/types": "8.46.2",
|
||||||
@@ -2267,6 +2260,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2800,7 +2794,8 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.3.10",
|
"version": "5.3.10",
|
||||||
@@ -3228,6 +3223,7 @@
|
|||||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3401,6 +3397,7 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -4203,12 +4200,6 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inputmask": {
|
|
||||||
"version": "5.0.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/inputmask/-/inputmask-5.0.9.tgz",
|
|
||||||
"integrity": "sha512-s0lUfqcEbel+EQXtehXqwCJGShutgieOaIImFKC/r4reYNvX3foyrChl6LOEvaEgxEbesePIrw1Zi2jhZaDZbQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/internal-slot": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||||
@@ -5745,6 +5736,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -5754,6 +5746,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -6552,6 +6545,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -6719,6 +6713,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"inputmask": "^5.0.9",
|
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
@@ -34,7 +33,6 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/inputmask": "^5.0.7",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
@@ -1,24 +1,46 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { ChangeEventHandler, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { SortingState } from '@tanstack/react-table';
|
import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table';
|
||||||
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { useModal } from '@/components/Modal';
|
import { Icon } from '@iconify/react';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
|
||||||
import { Movement } from '@/types/api/inventory/movement';
|
import { Movement } from '@/types/api/inventory/movement';
|
||||||
import { MovementApi } from '@/services/api/inventory';
|
import { MovementApi } from '@/services/api/inventory';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
import { Product } from '@/types/api/master-data/product';
|
||||||
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { TableToolbar } from '@/components/table/TableToolbar';
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
import Button from '@/components/Button';
|
||||||
import { OptionType } from '@/components/input/SelectInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
|
import SelectInput from '@/components/input/SelectInput';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import { TableRowOptions } from '@/components/table/TableRowOptions';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
|
||||||
|
const RowOptionsMenu = ({
|
||||||
|
type = 'dropdown',
|
||||||
|
props,
|
||||||
|
}: {
|
||||||
|
type: 'dropdown' | 'collapse';
|
||||||
|
props: CellContext<Movement, unknown>;
|
||||||
|
}) => (
|
||||||
|
<RowOptionsMenuWrapper type={type}>
|
||||||
|
<Button
|
||||||
|
href={`/inventory/movement/detail/?movementId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='primary'
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</RowOptionsMenuWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
const MovementTable = () => {
|
const MovementTable = () => {
|
||||||
const {
|
const {
|
||||||
@@ -28,30 +50,47 @@ const MovementTable = () => {
|
|||||||
setPageSize,
|
setPageSize,
|
||||||
toQueryString: getTableFilterQueryString,
|
toQueryString: getTableFilterQueryString,
|
||||||
} = useTableFilter({
|
} = useTableFilter({
|
||||||
initial: { search: '' },
|
initial: {
|
||||||
paramMap: { page: 'page', pageSize: 'limit' },
|
search: '',
|
||||||
|
product: '',
|
||||||
|
warehouse: '',
|
||||||
|
},
|
||||||
|
paramMap: {
|
||||||
|
page: 'page',
|
||||||
|
pageSize: 'limit',
|
||||||
|
product: 'product_id',
|
||||||
|
warehouse: 'warehouse_id',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [selectedMovement, setSelectedMovement] = useState<
|
|
||||||
Movement | undefined
|
|
||||||
>(undefined);
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
||||||
|
|
||||||
const deleteModal = useModal();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: movements,
|
setInputValue: setProductInputValue,
|
||||||
isLoading,
|
options: productOptions,
|
||||||
mutate: refreshMovements,
|
isLoadingOptions: isLoadingProductOptions,
|
||||||
} = useSWR(
|
} = useSelect<Product>('/products', 'id', 'name');
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setWarehouseInputValue,
|
||||||
|
options: warehouseOptions,
|
||||||
|
isLoadingOptions: isLoadingWarehouseOptions,
|
||||||
|
} = useSelect<Warehouse>('/warehouses', 'id', 'name');
|
||||||
|
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedWarehouse, setSelectedWarehouse] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: movements, isLoading } = useSWR(
|
||||||
`${MovementApi.basePath}${getTableFilterQueryString()}`,
|
`${MovementApi.basePath}${getTableFilterQueryString()}`,
|
||||||
MovementApi.getAllFetcher
|
MovementApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
setPage(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -60,167 +99,179 @@ const MovementTable = () => {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const productChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setIsDeleteLoading(true);
|
setSelectedProduct(val as OptionType);
|
||||||
try {
|
updateFilter('product', val ? ((val as OptionType).value as string) : '');
|
||||||
await MovementApi.delete(selectedMovement?.id as number);
|
|
||||||
refreshMovements();
|
|
||||||
deleteModal.closeModal();
|
|
||||||
} finally {
|
|
||||||
setIsDeleteLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedWarehouse(val as OptionType);
|
||||||
|
updateFilter('warehouse', val ? ((val as OptionType).value as string) : '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const movementColumns: ColumnDef<Movement>[] = [
|
||||||
|
{
|
||||||
|
header: '#',
|
||||||
|
cell: (props) =>
|
||||||
|
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
||||||
|
props.row.index +
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => row.source_warehouse?.name,
|
||||||
|
header: 'Gudang Asal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => row.destination_warehouse?.name,
|
||||||
|
header: 'Gudang Tujuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'transfer_reason',
|
||||||
|
header: 'Catatan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'transfer_date',
|
||||||
|
header: 'Tanggal',
|
||||||
|
cell: (props) =>
|
||||||
|
new Date(props.row.original.transfer_date).toLocaleDateString('id-ID'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
const totalCost = row.deliveries?.reduce(
|
||||||
|
(sum, d) => sum + (d.shipping_cost_total || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return totalCost?.toLocaleString('id-ID');
|
||||||
|
},
|
||||||
|
header: 'Biaya Pengiriman',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Aksi',
|
||||||
|
cell: (props) => {
|
||||||
|
const currentPageSize = props.table.getPaginationRowModel().rows.length;
|
||||||
|
const currentPageRows = props.table.getPaginationRowModel().flatRows;
|
||||||
|
const currentRowRelativeIndex =
|
||||||
|
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||||
|
|
||||||
|
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{currentPageSize > 2 && (
|
||||||
|
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||||
|
<RowOptionsMenu type='dropdown' props={props} />
|
||||||
|
</RowDropdownOptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentPageSize <= 2 && (
|
||||||
|
<RowCollapseOptions>
|
||||||
|
<RowOptionsMenu type='collapse' props={props} />
|
||||||
|
</RowCollapseOptions>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-4'>
|
<>
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='w-full p-0 sm:p-4'>
|
||||||
<TableToolbar
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
addButton={{
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||||
href: '/inventory/movement/add',
|
<div className='w-full flex flex-row gap-2'>
|
||||||
label: 'Tambah',
|
<Button
|
||||||
|
href='/inventory/movement/add'
|
||||||
|
variant='outline'
|
||||||
|
color='primary'
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
|
Tambah
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DebouncedTextInput
|
||||||
|
name='search'
|
||||||
|
placeholder='Cari Movement'
|
||||||
|
value={tableFilterState.search}
|
||||||
|
onChange={searchChangeHandler}
|
||||||
|
className={{ wrapper: 'sm:max-w-3xs' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-12 justify-end gap-4'>
|
||||||
|
<SelectInput
|
||||||
|
label='Produk'
|
||||||
|
options={productOptions}
|
||||||
|
isLoading={isLoadingProductOptions}
|
||||||
|
value={selectedProduct}
|
||||||
|
onChange={productChangeHandler}
|
||||||
|
onInputChange={setProductInputValue}
|
||||||
|
isClearable
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 sm:col-span-4',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
label='Gudang'
|
||||||
|
options={warehouseOptions}
|
||||||
|
isLoading={isLoadingWarehouseOptions}
|
||||||
|
value={selectedWarehouse}
|
||||||
|
onChange={warehouseChangeHandler}
|
||||||
|
onInputChange={setWarehouseInputValue}
|
||||||
|
isClearable
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 sm:col-span-4',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
label='Baris'
|
||||||
|
options={ROWS_OPTIONS}
|
||||||
|
value={{
|
||||||
|
label: String(tableFilterState.pageSize),
|
||||||
|
value: tableFilterState.pageSize,
|
||||||
|
}}
|
||||||
|
onChange={pageSizeChangeHandler}
|
||||||
|
className={{
|
||||||
|
wrapper:
|
||||||
|
'col-span-6 sm:col-span-4 max-w-28 sm:justify-self-end',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table<Movement>
|
||||||
|
data={isResponseSuccess(movements) ? movements?.data : []}
|
||||||
|
columns={movementColumns}
|
||||||
|
pageSize={tableFilterState.pageSize}
|
||||||
|
page={isResponseSuccess(movements) ? movements?.meta?.page : 0}
|
||||||
|
totalItems={
|
||||||
|
isResponseSuccess(movements) ? movements?.meta?.total_results : 0
|
||||||
|
}
|
||||||
|
onPageChange={setPage}
|
||||||
|
isLoading={isLoading}
|
||||||
|
sorting={sorting}
|
||||||
|
setSorting={setSorting}
|
||||||
|
className={{
|
||||||
|
containerClassName: cn({
|
||||||
|
'mb-20':
|
||||||
|
isResponseSuccess(movements) && movements?.data?.length === 0,
|
||||||
|
}),
|
||||||
|
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-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||||
}}
|
}}
|
||||||
search={{
|
|
||||||
value: tableFilterState.search,
|
|
||||||
onChange: searchChangeHandler,
|
|
||||||
placeholder: 'Cari Movement',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TableRowSizeSelector
|
|
||||||
value={tableFilterState.pageSize}
|
|
||||||
onChange={pageSizeChangeHandler}
|
|
||||||
options={ROWS_OPTIONS}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
<Table<Movement>
|
|
||||||
data={isResponseSuccess(movements) ? movements?.data : []}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: '#',
|
|
||||||
cell: (props) =>
|
|
||||||
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
|
||||||
props.row.index +
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.source_warehouse?.name,
|
|
||||||
header: 'Gudang Asal',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => row.destination_warehouse?.name,
|
|
||||||
header: 'Gudang Tujuan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'transfer_reason',
|
|
||||||
header: 'Catatan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'transfer_date',
|
|
||||||
header: 'Tanggal',
|
|
||||||
cell: (props) =>
|
|
||||||
new Date(props.row.original.transfer_date).toLocaleDateString(
|
|
||||||
'id-ID'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorFn: (row) => {
|
|
||||||
const totalCost = row.deliveries?.reduce(
|
|
||||||
(sum, d) => sum + (d.shipping_cost_total || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return totalCost?.toLocaleString('id-ID');
|
|
||||||
},
|
|
||||||
header: 'Biaya Pengiriman',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Aksi',
|
|
||||||
cell: (props) => {
|
|
||||||
const currentPageSize =
|
|
||||||
props.table.getPaginationRowModel().rows.length;
|
|
||||||
const currentPageRows =
|
|
||||||
props.table.getPaginationRowModel().flatRows;
|
|
||||||
const currentRowRelativeIndex =
|
|
||||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
|
||||||
|
|
||||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
|
||||||
|
|
||||||
const deleteClickHandler = () => {
|
|
||||||
setSelectedMovement(props.row.original);
|
|
||||||
deleteModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{currentPageSize > 2 && (
|
|
||||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
|
||||||
<TableRowOptions
|
|
||||||
type='dropdown'
|
|
||||||
recordId={props.row.original.id}
|
|
||||||
basePath='/inventory/movement'
|
|
||||||
queryParam='movementId'
|
|
||||||
showEdit={false}
|
|
||||||
showDelete={false}
|
|
||||||
/>
|
|
||||||
</RowDropdownOptions>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentPageSize <= 2 && (
|
|
||||||
<RowCollapseOptions>
|
|
||||||
<TableRowOptions
|
|
||||||
type='collapse'
|
|
||||||
recordId={props.row.original.id}
|
|
||||||
basePath='/inventory/movement'
|
|
||||||
queryParam='movementId'
|
|
||||||
showEdit={false}
|
|
||||||
showDelete={false}
|
|
||||||
/>
|
|
||||||
</RowCollapseOptions>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
pageSize={tableFilterState.pageSize}
|
|
||||||
page={isResponseSuccess(movements) ? movements?.meta?.page : 0}
|
|
||||||
totalItems={
|
|
||||||
isResponseSuccess(movements) ? movements?.meta?.total_results : 0
|
|
||||||
}
|
|
||||||
onPageChange={setPage}
|
|
||||||
isLoading={isLoading}
|
|
||||||
sorting={sorting}
|
|
||||||
setSorting={setSorting}
|
|
||||||
className={{
|
|
||||||
containerClassName: cn({
|
|
||||||
'mb-20':
|
|
||||||
isResponseSuccess(movements) && movements?.data?.length === 0,
|
|
||||||
}),
|
|
||||||
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-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConfirmationModal
|
|
||||||
ref={deleteModal.ref}
|
|
||||||
type='error'
|
|
||||||
text={`Apakah anda yakin ingin menghapus data Movement ini?`}
|
|
||||||
secondaryButton={{
|
|
||||||
text: 'Tidak',
|
|
||||||
}}
|
|
||||||
primaryButton={{
|
|
||||||
text: 'Ya',
|
|
||||||
color: 'error',
|
|
||||||
isLoading: isDeleteLoading,
|
|
||||||
onClick: confirmationModalDeleteClickHandler,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,82 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { Movement } from '@/types/api/inventory/movement';
|
import { Movement } from '@/types/api/inventory/movement';
|
||||||
|
|
||||||
|
type MovementFormSchemaType = {
|
||||||
|
transfer_reason: string;
|
||||||
|
transfer_date: string;
|
||||||
|
source_warehouse?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
area?: string;
|
||||||
|
location?: string;
|
||||||
|
} | null;
|
||||||
|
source_warehouse_id: number;
|
||||||
|
destination_warehouse?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
area?: string;
|
||||||
|
location?: string;
|
||||||
|
} | null;
|
||||||
|
destination_warehouse_id: number;
|
||||||
|
products: {
|
||||||
|
product?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
} | null;
|
||||||
|
product_id: number;
|
||||||
|
product_qty: number | string;
|
||||||
|
}[];
|
||||||
|
deliveries: {
|
||||||
|
delivery_cost?: number | string;
|
||||||
|
delivery_cost_per_item?: number | string;
|
||||||
|
document?: File | string | null;
|
||||||
|
document_path?: string | null;
|
||||||
|
driver_name: string;
|
||||||
|
vehicle_plate: string;
|
||||||
|
supplier?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
} | null;
|
||||||
|
supplier_id: number;
|
||||||
|
products: {
|
||||||
|
product?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
} | null;
|
||||||
|
product_id: number;
|
||||||
|
product_qty: number | string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ProductSchema = {
|
export type ProductSchema = {
|
||||||
product: {
|
product?: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
product_qty: number;
|
product_qty: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeliverySchema = {
|
export type DeliverySchema = {
|
||||||
delivery_cost?: number | undefined;
|
delivery_cost?: number | string;
|
||||||
delivery_cost_per_item?: number | undefined;
|
delivery_cost_per_item?: number | string;
|
||||||
document?: File | string | null;
|
document?: File | string | null;
|
||||||
document_path?: string | null;
|
document_path?: string | null;
|
||||||
driver_name: string;
|
driver_name: string;
|
||||||
vehicle_plate: string;
|
vehicle_plate: string;
|
||||||
supplier: {
|
supplier?: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
supplier_id: number;
|
supplier_id: number;
|
||||||
products: {
|
products: {
|
||||||
product: {
|
product?: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
product_qty: number;
|
product_qty: number | string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,7 +150,7 @@ const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({
|
|||||||
.required('Produk wajib diisi!'),
|
.required('Produk wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MovementFormSchema = Yup.object({
|
export const MovementFormSchema: Yup.ObjectSchema<MovementFormSchemaType> = Yup.object({
|
||||||
transfer_reason: Yup.string().required('Alasan transfer wajib diisi!'),
|
transfer_reason: Yup.string().required('Alasan transfer wajib diisi!'),
|
||||||
transfer_date: Yup.string().required('Tanggal transfer wajib diisi!'),
|
transfer_date: Yup.string().required('Tanggal transfer wajib diisi!'),
|
||||||
source_warehouse: Yup.object({
|
source_warehouse: Yup.object({
|
||||||
@@ -133,8 +181,6 @@ export const MovementFormSchema = Yup.object({
|
|||||||
.required('Pengiriman wajib diisi!'),
|
.required('Pengiriman wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateMovementFormSchema = MovementFormSchema;
|
|
||||||
|
|
||||||
export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
|
export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
|
||||||
|
|
||||||
export const getMovementFormInitialValues = (
|
export const getMovementFormInitialValues = (
|
||||||
|
|||||||
@@ -8,26 +8,27 @@ import { Icon } from '@iconify/react';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
OptionType,
|
||||||
import { FormActions } from '@/components/helper/form/FormActions';
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import {
|
import {
|
||||||
CreateMovementPayload,
|
CreateMovementPayload,
|
||||||
Movement,
|
Movement,
|
||||||
} from '@/types/api/inventory/movement';
|
} from '@/types/api/inventory/movement';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
MovementFormSchema,
|
MovementFormSchema,
|
||||||
MovementFormValues,
|
MovementFormValues,
|
||||||
UpdateMovementFormSchema,
|
|
||||||
getMovementFormInitialValues,
|
getMovementFormInitialValues,
|
||||||
ProductSchema,
|
ProductSchema,
|
||||||
DeliverySchema,
|
DeliverySchema,
|
||||||
} from '@/components/pages/inventory/movement/form/MovementForm.schema';
|
} from '@/components/pages/inventory/movement/form/MovementForm.schema';
|
||||||
import { useMovementFormHandlers } from './useMovementFormHandlers';
|
|
||||||
import { SupplierApi, WarehouseApi } from '@/services/api/master-data';
|
import { SupplierApi, WarehouseApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { MovementApi } from '@/services/api/inventory';
|
||||||
import FileInput from '@/components/input/FileInput';
|
import FileInput from '@/components/input/FileInput';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
@@ -38,24 +39,38 @@ interface MovementFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [, setMovementFormErrorMessage] = useState('');
|
const [movementFormErrorMessage, setMovementFormErrorMessage] = useState('');
|
||||||
const [
|
const [
|
||||||
productWarehouseSelectInputValue,
|
productWarehouseSelectInputValue,
|
||||||
setProductWarehouseSelectInputValue,
|
setProductWarehouseSelectInputValue,
|
||||||
] = useState('');
|
] = useState('');
|
||||||
const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
|
const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
|
||||||
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
|
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
|
||||||
const [warehouseSelectInputValue, setWarehouseSelectInputValue] =
|
|
||||||
useState('');
|
|
||||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
|
||||||
|
|
||||||
// ===== FORM HANDLERS =====
|
// ===== FORM HANDLERS =====
|
||||||
const {
|
const createMovementHandler = useCallback(
|
||||||
movementFormErrorMessage,
|
async (payload: CreateMovementPayload, documents: File[] = []) => {
|
||||||
createMovementHandler,
|
const formData = new FormData();
|
||||||
updateMovementHandler,
|
formData.append('data', JSON.stringify(payload));
|
||||||
} = useMovementFormHandlers(initialValues?.id);
|
documents.forEach((file, index) => {
|
||||||
|
formData.append(`documents[${index}]`, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await MovementApi.create(
|
||||||
|
formData as unknown as CreateMovementPayload
|
||||||
|
);
|
||||||
|
if (isResponseError(res)) {
|
||||||
|
setMovementFormErrorMessage(res.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.success(res?.message as string);
|
||||||
|
router.push('/inventory/movement');
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
// ===== INTERFACES =====
|
// ===== INTERFACES =====
|
||||||
interface WarehouseOptionType extends OptionType {
|
interface WarehouseOptionType extends OptionType {
|
||||||
@@ -77,18 +92,25 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
ProductWarehouseApi.getAllFetcher
|
ProductWarehouseApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ===== USE SELECT HOOKS =====
|
||||||
|
const {
|
||||||
|
inputValue: warehouseSelectInputValue,
|
||||||
|
setInputValue: setWarehouseSelectInputValue,
|
||||||
|
isLoadingOptions: isLoadingWarehouses,
|
||||||
|
} = useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setSupplierSelectInputValue,
|
||||||
|
options: supplierOptions,
|
||||||
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
|
} = useSelect(SupplierApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const warehousesUrl = `${WarehouseApi.basePath}?${new URLSearchParams({ search: warehouseSelectInputValue }).toString()}`;
|
const warehousesUrl = `${WarehouseApi.basePath}?${new URLSearchParams({ search: warehouseSelectInputValue }).toString()}`;
|
||||||
const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR(
|
const { data: warehouses } = useSWR(
|
||||||
warehousesUrl,
|
warehousesUrl,
|
||||||
WarehouseApi.getAllFetcher
|
WarehouseApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue }).toString()}`;
|
|
||||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
|
||||||
suppliersUrl,
|
|
||||||
SupplierApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== DATA PROCESSING =====
|
// ===== DATA PROCESSING =====
|
||||||
const warehouseStockMap = useMemo(() => {
|
const warehouseStockMap = useMemo(() => {
|
||||||
if (!isResponseSuccess(allProductWarehouses)) return new Map();
|
if (!isResponseSuccess(allProductWarehouses)) return new Map();
|
||||||
@@ -114,8 +136,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
return stockMap;
|
return stockMap;
|
||||||
}, [allProductWarehouses]);
|
}, [allProductWarehouses]);
|
||||||
|
|
||||||
const warehouseOptions = isResponseSuccess(warehouses)
|
const warehouseOptions = useMemo(() => {
|
||||||
? warehouses?.data.map((w) => {
|
if (!isResponseSuccess(warehouses)) return [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
warehouses?.data.map((w) => {
|
||||||
warehouseStockMap.get(w.id);
|
warehouseStockMap.get(w.id);
|
||||||
return {
|
return {
|
||||||
value: w.id,
|
value: w.id,
|
||||||
@@ -126,12 +151,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
? w.location?.name
|
? w.location?.name
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
})
|
}) || []
|
||||||
: [];
|
);
|
||||||
|
}, [warehouses, warehouseStockMap]);
|
||||||
const supplierOptions = isResponseSuccess(suppliers)
|
|
||||||
? suppliers?.data.map((s) => ({ value: s.id, label: s.name }))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// ===== FORM INITIALIZATION =====
|
// ===== FORM INITIALIZATION =====
|
||||||
const formikInitialValues = useMemo<MovementFormValues>(
|
const formikInitialValues = useMemo<MovementFormValues>(
|
||||||
@@ -141,8 +163,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
|
|
||||||
const formik = useFormik<MovementFormValues>({
|
const formik = useFormik<MovementFormValues>({
|
||||||
initialValues: formikInitialValues,
|
initialValues: formikInitialValues,
|
||||||
validationSchema:
|
validationSchema: MovementFormSchema,
|
||||||
type === 'edit' ? UpdateMovementFormSchema : MovementFormSchema,
|
|
||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
validateOnMount: false,
|
validateOnMount: false,
|
||||||
@@ -150,7 +171,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
setMovementFormErrorMessage('');
|
setMovementFormErrorMessage('');
|
||||||
const documents: File[] = [];
|
const documents: File[] = [];
|
||||||
const deliveriesPayload = values.deliveries.map((d, idx) => {
|
const deliveriesPayload = values.deliveries.map((d) => {
|
||||||
let documentIndex = 0;
|
let documentIndex = 0;
|
||||||
|
|
||||||
if (d.document && d.document instanceof File) {
|
if (d.document && d.document instanceof File) {
|
||||||
@@ -159,8 +180,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
delivery_cost: d.delivery_cost ?? 0,
|
delivery_cost: parseInt((d.delivery_cost || '').toString()) || 0,
|
||||||
delivery_cost_per_item: d.delivery_cost_per_item ?? 0,
|
delivery_cost_per_item:
|
||||||
|
parseInt((d.delivery_cost_per_item || '').toString()) || 0,
|
||||||
document_index: documentIndex,
|
document_index: documentIndex,
|
||||||
document_path: d.document_path,
|
document_path: d.document_path,
|
||||||
driver_name: d.driver_name,
|
driver_name: d.driver_name,
|
||||||
@@ -168,7 +190,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
supplier_id: d.supplier_id,
|
supplier_id: d.supplier_id,
|
||||||
products: d.products.map((p) => ({
|
products: d.products.map((p) => ({
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
product_qty: p.product_qty,
|
product_qty: parseInt(p.product_qty.toString()) || 0,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -180,7 +202,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
destination_warehouse_id: values.destination_warehouse_id,
|
destination_warehouse_id: values.destination_warehouse_id,
|
||||||
products: values.products.map((p) => ({
|
products: values.products.map((p) => ({
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
product_qty: p.product_qty,
|
product_qty: parseInt(p.product_qty.toString()) || 0,
|
||||||
})),
|
})),
|
||||||
deliveries: deliveriesPayload,
|
deliveries: deliveriesPayload,
|
||||||
};
|
};
|
||||||
@@ -189,13 +211,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
case 'add':
|
case 'add':
|
||||||
await createMovementHandler(payload, documents);
|
await createMovementHandler(payload, documents);
|
||||||
break;
|
break;
|
||||||
case 'edit':
|
|
||||||
await updateMovementHandler(
|
|
||||||
initialValues?.id as number,
|
|
||||||
payload,
|
|
||||||
documents
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -297,7 +312,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
{
|
{
|
||||||
product: null,
|
product: null,
|
||||||
product_id: 0,
|
product_id: 0,
|
||||||
product_qty: 0,
|
product_qty: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formik.setFieldValue('products', newProducts);
|
formik.setFieldValue('products', newProducts);
|
||||||
@@ -332,8 +347,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
formik.setFieldValue('deliveries', [
|
formik.setFieldValue('deliveries', [
|
||||||
...(formik.values.deliveries || []),
|
...(formik.values.deliveries || []),
|
||||||
{
|
{
|
||||||
delivery_cost: undefined,
|
delivery_cost: '',
|
||||||
delivery_cost_per_item: undefined,
|
delivery_cost_per_item: '',
|
||||||
document: null,
|
document: null,
|
||||||
driver_name: '',
|
driver_name: '',
|
||||||
vehicle_plate: '',
|
vehicle_plate: '',
|
||||||
@@ -343,7 +358,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
{
|
{
|
||||||
product: null,
|
product: null,
|
||||||
product_id: 0,
|
product_id: 0,
|
||||||
product_qty: 0,
|
product_qty: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -385,7 +400,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
const delivery = formik.values.deliveries?.[idx];
|
const delivery = formik.values.deliveries?.[idx];
|
||||||
if (delivery) {
|
if (delivery) {
|
||||||
const productQty = delivery.products.reduce(
|
const productQty = delivery.products.reduce(
|
||||||
(sum, p) => sum + p.product_qty,
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
if (productQty > 0 && value > 0) {
|
if (productQty > 0 && value > 0) {
|
||||||
@@ -409,7 +424,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
const delivery = formik.values.deliveries?.[idx];
|
const delivery = formik.values.deliveries?.[idx];
|
||||||
if (delivery) {
|
if (delivery) {
|
||||||
const productQty = delivery.products.reduce(
|
const productQty = delivery.products.reduce(
|
||||||
(sum, p) => sum + p.product_qty,
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
if (productQty > 0 && value > 0) {
|
if (productQty > 0 && value > 0) {
|
||||||
@@ -683,36 +698,38 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formik.values.deliveries?.forEach((delivery, idx) => {
|
formik.values.deliveries?.forEach((delivery, idx) => {
|
||||||
const productQty = delivery.products.reduce(
|
const productQty = delivery.products.reduce(
|
||||||
(sum, p) => sum + p.product_qty,
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
const deliveryCost =
|
||||||
delivery.delivery_cost &&
|
parseInt((delivery.delivery_cost || '').toString()) || 0;
|
||||||
delivery.delivery_cost > 0 &&
|
const deliveryCostPerItem =
|
||||||
productQty > 0
|
parseInt((delivery.delivery_cost_per_item || '').toString()) || 0;
|
||||||
) {
|
|
||||||
const perItem = delivery.delivery_cost / productQty;
|
if (deliveryCost > 0 && productQty > 0) {
|
||||||
if (Math.abs((delivery.delivery_cost_per_item || 0) - perItem) > 0.01) {
|
const perItem = deliveryCost / productQty;
|
||||||
|
if (Math.abs(deliveryCostPerItem - perItem) > 0.01) {
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
`deliveries.${idx}.delivery_cost_per_item`,
|
`deliveries.${idx}.delivery_cost_per_item`,
|
||||||
perItem
|
perItem
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (deliveryCostPerItem > 0 && productQty > 0) {
|
||||||
delivery.delivery_cost_per_item &&
|
const totalCost = deliveryCostPerItem * productQty;
|
||||||
delivery.delivery_cost_per_item > 0 &&
|
if (Math.abs(deliveryCost - totalCost) > 0.01) {
|
||||||
productQty > 0
|
|
||||||
) {
|
|
||||||
const totalCost = delivery.delivery_cost_per_item * productQty;
|
|
||||||
if (Math.abs((delivery.delivery_cost || 0) - totalCost) > 0.01) {
|
|
||||||
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
formik.values.deliveries
|
formik.values.deliveries
|
||||||
?.map((d) => d.products.reduce((sum, p) => sum + p.product_qty, 0))
|
?.map((d) =>
|
||||||
|
d.products.reduce(
|
||||||
|
(sum, p) => sum + (parseInt(p.product_qty.toString()) || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
.join(','),
|
.join(','),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -730,11 +747,21 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full'>
|
<section className='w-full'>
|
||||||
<FormHeader
|
<header className='flex flex-col gap-4'>
|
||||||
type={type}
|
<Button
|
||||||
title='Movement'
|
href='/inventory/movement'
|
||||||
backUrl='/inventory/movement'
|
variant='link'
|
||||||
/>
|
className='w-fit p-0 text-primary'
|
||||||
|
>
|
||||||
|
<Icon icon='uil:arrow-left' width={24} height={24} />
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
<h1 className='text-2xl font-bold text-center'>
|
||||||
|
{type === 'add' && 'Tambah Movement'}
|
||||||
|
{type === 'edit' && 'Edit Movement'}
|
||||||
|
{type === 'detail' && 'Detail Movement'}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
<form
|
<form
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
onReset={formik.handleReset}
|
onReset={formik.handleReset}
|
||||||
@@ -748,6 +775,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
required
|
required
|
||||||
label='Alasan Transfer'
|
label='Alasan Transfer'
|
||||||
name='transfer_reason'
|
name='transfer_reason'
|
||||||
|
placeholder='Masukkan alasan transfer...'
|
||||||
value={formik.values.transfer_reason}
|
value={formik.values.transfer_reason}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -785,6 +813,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Gudang'
|
label='Gudang'
|
||||||
|
placeholder='Pilih gudang asal...'
|
||||||
value={formik.values.source_warehouse}
|
value={formik.values.source_warehouse}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldTouched('source_warehouse', true);
|
formik.setFieldTouched('source_warehouse', true);
|
||||||
@@ -852,6 +881,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Gudang'
|
label='Gudang'
|
||||||
|
placeholder='Pilih gudang tujuan...'
|
||||||
value={formik.values.destination_warehouse}
|
value={formik.values.destination_warehouse}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldTouched('destination_warehouse', true);
|
formik.setFieldTouched('destination_warehouse', true);
|
||||||
@@ -1038,8 +1068,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
}
|
}
|
||||||
placeholder={
|
placeholder={
|
||||||
!formik.values.source_warehouse_id
|
!formik.values.source_warehouse_id
|
||||||
? 'Pilih gudang asal terlebih dahulu'
|
? 'Pilih gudang asal terlebih dahulu...'
|
||||||
: 'Pilih produk'
|
: 'Pilih produk...'
|
||||||
}
|
}
|
||||||
isClearable
|
isClearable
|
||||||
{...isRepeaterInputError(
|
{...isRepeaterInputError(
|
||||||
@@ -1057,6 +1087,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`products.${idx}.product_qty`}
|
name={`products.${idx}.product_qty`}
|
||||||
|
placeholder='Masukkan kuantitas...'
|
||||||
value={product.product_qty ?? ''}
|
value={product.product_qty ?? ''}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -1277,6 +1308,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
|
placeholder='Pilih produk...'
|
||||||
value={delivery.products[0]?.product ?? undefined}
|
value={delivery.products[0]?.product ?? undefined}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldTouched(
|
formik.setFieldTouched(
|
||||||
@@ -1317,6 +1349,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`deliveries.${idx}.products.0.product_qty`}
|
name={`deliveries.${idx}.products.0.product_qty`}
|
||||||
|
placeholder='Masukkan kuantitas...'
|
||||||
value={delivery.products[0]?.product_qty ?? ''}
|
value={delivery.products[0]?.product_qty ?? ''}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -1347,6 +1380,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
|
placeholder='Pilih supplier...'
|
||||||
value={delivery.supplier}
|
value={delivery.supplier}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldTouched(
|
formik.setFieldTouched(
|
||||||
@@ -1386,6 +1420,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
name={`deliveries.${idx}.vehicle_plate`}
|
name={`deliveries.${idx}.vehicle_plate`}
|
||||||
|
placeholder='Masukkan plat nomor...'
|
||||||
value={delivery.vehicle_plate}
|
value={delivery.vehicle_plate}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -1463,6 +1498,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`deliveries.${idx}.delivery_cost`}
|
name={`deliveries.${idx}.delivery_cost`}
|
||||||
|
placeholder='Masukkan biaya pengiriman...'
|
||||||
value={delivery.delivery_cost || ''}
|
value={delivery.delivery_cost || ''}
|
||||||
onChange={handleDeliveryCostChangeWrapper(idx)}
|
onChange={handleDeliveryCostChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -1487,6 +1523,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
name={`deliveries.${idx}.delivery_cost_per_item`}
|
name={`deliveries.${idx}.delivery_cost_per_item`}
|
||||||
|
placeholder='Masukkan biaya per item...'
|
||||||
value={delivery.delivery_cost_per_item || ''}
|
value={delivery.delivery_cost_per_item || ''}
|
||||||
onChange={handleDeliveryCostPerItemChangeWrapper(
|
onChange={handleDeliveryCostPerItemChangeWrapper(
|
||||||
idx
|
idx
|
||||||
@@ -1513,6 +1550,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
name={`deliveries.${idx}.driver_name`}
|
name={`deliveries.${idx}.driver_name`}
|
||||||
|
placeholder='Masukkan nama sopir...'
|
||||||
value={delivery.driver_name}
|
value={delivery.driver_name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -1582,11 +1620,30 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<FormActions<MovementFormValues>
|
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||||
type={type}
|
{type !== 'detail' && (
|
||||||
formik={formik}
|
<div className='flex flex-row justify-end gap-2 w-full'>
|
||||||
disableSubmit={hasInvalidQty || hasExceededStock}
|
<Button type='reset' color='warning' className='px-4'>
|
||||||
/>
|
Reset
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
color='primary'
|
||||||
|
className='px-4'
|
||||||
|
isLoading={formik.isSubmitting}
|
||||||
|
disabled={
|
||||||
|
hasInvalidQty ||
|
||||||
|
hasExceededStock ||
|
||||||
|
!formik.isValid ||
|
||||||
|
formik.isSubmitting
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{movementFormErrorMessage && (
|
{movementFormErrorMessage && (
|
||||||
<div role='alert' className='alert alert-error'>
|
<div role='alert' className='alert alert-error'>
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import { useModal } from '@/components/Modal';
|
|
||||||
import { MovementApi } from '@/services/api/inventory';
|
|
||||||
import {
|
|
||||||
CreateMovementPayload,
|
|
||||||
UpdateMovementPayload,
|
|
||||||
} from '@/types/api/inventory/movement';
|
|
||||||
import { isResponseError } from '@/lib/api-helper';
|
|
||||||
|
|
||||||
export const useMovementFormHandlers = (initialValuesId?: number) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const deleteModal = useModal();
|
|
||||||
const [movementFormErrorMessage, setMovementFormErrorMessage] = useState('');
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
||||||
|
|
||||||
const createMovementHandler = useCallback(
|
|
||||||
async (payload: CreateMovementPayload, documents: File[] = []) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('data', JSON.stringify(payload));
|
|
||||||
documents.forEach((file, index) => {
|
|
||||||
formData.append(`documents[${index}]`, file);
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await MovementApi.create(
|
|
||||||
formData as unknown as CreateMovementPayload
|
|
||||||
);
|
|
||||||
if (isResponseError(res)) {
|
|
||||||
setMovementFormErrorMessage(res.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.success(res?.message as string);
|
|
||||||
router.push('/inventory/movement');
|
|
||||||
},
|
|
||||||
[router]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateMovementHandler = useCallback(
|
|
||||||
async (
|
|
||||||
movementId: number,
|
|
||||||
payload: UpdateMovementPayload,
|
|
||||||
documents: File[] = []
|
|
||||||
) => {
|
|
||||||
let finalPayload: UpdateMovementPayload | FormData;
|
|
||||||
|
|
||||||
if (documents.length > 0) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('data', JSON.stringify(payload));
|
|
||||||
documents.forEach((file, index) => {
|
|
||||||
formData.append(`documents[${index}]`, file);
|
|
||||||
});
|
|
||||||
|
|
||||||
finalPayload = formData as unknown as UpdateMovementPayload;
|
|
||||||
} else {
|
|
||||||
finalPayload = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await MovementApi.update(movementId, finalPayload);
|
|
||||||
if (res?.status === 'error') {
|
|
||||||
setMovementFormErrorMessage(res.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.success(res?.message as string);
|
|
||||||
router.refresh();
|
|
||||||
router.push('/inventory/movement');
|
|
||||||
},
|
|
||||||
[router]
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteMovementClickHandler = useCallback(() => {
|
|
||||||
deleteModal.openModal();
|
|
||||||
}, [deleteModal]);
|
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = useCallback(async () => {
|
|
||||||
if (!initialValuesId) return;
|
|
||||||
|
|
||||||
setIsDeleteLoading(true);
|
|
||||||
await MovementApi.delete(initialValuesId);
|
|
||||||
deleteModal.closeModal();
|
|
||||||
toast.success('Successfully delete Movement!');
|
|
||||||
setIsDeleteLoading(false);
|
|
||||||
router.push('/inventory/movement');
|
|
||||||
}, [deleteModal, initialValuesId, router]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
deleteModal,
|
|
||||||
movementFormErrorMessage,
|
|
||||||
isDeleteLoading,
|
|
||||||
createMovementHandler,
|
|
||||||
updateMovementHandler,
|
|
||||||
deleteMovementClickHandler,
|
|
||||||
confirmationModalDeleteClickHandler,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
+13
-9
@@ -1,14 +1,18 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export const ProductCategoryFormSchema = Yup.object({
|
type ProductCategoryFormSchemaType = {
|
||||||
code: Yup.string()
|
code: string;
|
||||||
.required('Kode wajib diisi!')
|
name: string;
|
||||||
.max(3, 'Kode kategori produk melebihi 3 karakter!'),
|
};
|
||||||
name: Yup.string().required('Nama wajib diisi!'),
|
|
||||||
});
|
export const ProductCategoryFormSchema: Yup.ObjectSchema<ProductCategoryFormSchemaType> =
|
||||||
|
Yup.object({
|
||||||
|
code: Yup.string()
|
||||||
|
.required('Kode wajib diisi!')
|
||||||
|
.max(3, 'Kode kategori produk melebihi 3 karakter!'),
|
||||||
|
name: Yup.string().required('Nama wajib diisi!'),
|
||||||
|
});
|
||||||
|
|
||||||
export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema;
|
export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema;
|
||||||
|
|
||||||
export type ProductCategoryFormValues = Yup.InferType<
|
export type ProductCategoryFormValues = Yup.InferType<typeof ProductCategoryFormSchema>;
|
||||||
typeof ProductCategoryFormSchema
|
|
||||||
>;
|
|
||||||
@@ -71,12 +71,13 @@ const ProductCategoryForm = ({
|
|||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const formikInitialValues = useMemo<ProductCategoryFormValues>(() => {
|
const formikInitialValues = useMemo<ProductCategoryFormValues>(
|
||||||
return {
|
() => ({
|
||||||
code: initialValues?.code ?? '',
|
code: initialValues?.code ?? '',
|
||||||
name: initialValues?.name ?? '',
|
name: initialValues?.name ?? '',
|
||||||
};
|
}),
|
||||||
}, [initialValues]);
|
[initialValues]
|
||||||
|
);
|
||||||
|
|
||||||
const formik = useFormik<ProductCategoryFormValues>({
|
const formik = useFormik<ProductCategoryFormValues>({
|
||||||
initialValues: formikInitialValues,
|
initialValues: formikInitialValues,
|
||||||
@@ -118,7 +119,7 @@ const ProductCategoryForm = ({
|
|||||||
await ProductCategoryApi.delete(initialValues?.id as number);
|
await ProductCategoryApi.delete(initialValues?.id as number);
|
||||||
|
|
||||||
deleteModal.closeModal();
|
deleteModal.closeModal();
|
||||||
toast.success('Successfully delete Product Category!');
|
toast.success('Berhasil menghapus data Kategori Produk!');
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
router.push('/master-data/product-category');
|
router.push('/master-data/product-category');
|
||||||
};
|
};
|
||||||
@@ -129,7 +130,7 @@ const ProductCategoryForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full max-w-xl'>
|
<section className='w-full max-w-2xl'>
|
||||||
<header className='flex flex-col gap-4'>
|
<header className='flex flex-col gap-4'>
|
||||||
<Button
|
<Button
|
||||||
href='/master-data/product-category'
|
href='/master-data/product-category'
|
||||||
@@ -141,9 +142,9 @@ const ProductCategoryForm = ({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<h1 className='text-2xl font-bold text-center'>
|
<h1 className='text-2xl font-bold text-center'>
|
||||||
{type === 'add' && 'Tambah Product Category'}
|
{type === 'add' && 'Tambah Kategori Produk'}
|
||||||
{type === 'edit' && 'Edit Product Category'}
|
{type === 'edit' && 'Edit Kategori Produk'}
|
||||||
{type === 'detail' && 'Detail Product Category'}
|
{type === 'detail' && 'Detail Kategori Produk'}
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ const ProductCategoryForm = ({
|
|||||||
required
|
required
|
||||||
label='Kode'
|
label='Kode'
|
||||||
name='code'
|
name='code'
|
||||||
placeholder='Masukkan kode kategori produk'
|
placeholder='Masukkan kode...'
|
||||||
value={formik.values.code}
|
value={formik.values.code}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -169,7 +170,7 @@ const ProductCategoryForm = ({
|
|||||||
required
|
required
|
||||||
label='Nama'
|
label='Nama'
|
||||||
name='name'
|
name='name'
|
||||||
placeholder='Masukkan nama kategori produk'
|
placeholder='Masukkan nama...'
|
||||||
value={formik.values.name}
|
value={formik.values.name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -256,7 +257,7 @@ const ProductCategoryForm = ({
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={`Apakah anda yakin ingin menghapus data Product Category ini (${initialValues?.name})?`}
|
text={`Apakah anda yakin ingin menghapus data Kategori Produk ini (${initialValues?.name})?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,53 +1,82 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
export const ProductFormSchema = Yup.object({
|
type ProductFormSchemaType = {
|
||||||
name: Yup.string().required('Nama wajib diisi!'),
|
name: string;
|
||||||
brand: Yup.string().required('Merek wajib diisi!'),
|
brand: string;
|
||||||
sku: Yup.string().required('SKU wajib diisi!'),
|
sku: string;
|
||||||
uom: Yup.object({
|
uom?: {
|
||||||
value: Yup.number().min(1).required(),
|
value: number;
|
||||||
label: Yup.string().required(),
|
label: string;
|
||||||
}).nullable(),
|
} | null;
|
||||||
uom_id: Yup.number()
|
uom_id: number;
|
||||||
.required('Satuan wajib diisi!')
|
product_category?: {
|
||||||
.typeError('Satuan wajib diisi!'),
|
value: number;
|
||||||
product_category: Yup.object({
|
label: string;
|
||||||
value: Yup.number().min(1).required(),
|
} | null;
|
||||||
label: Yup.string().required(),
|
product_category_id: number;
|
||||||
}).nullable(),
|
product_price: number | string;
|
||||||
product_category_id: Yup.number()
|
selling_price: number | string;
|
||||||
.required('Kategori produk wajib diisi!')
|
tax: number | string;
|
||||||
.typeError('Kategori produk wajib diisi!'),
|
expiry_period: number | string;
|
||||||
product_price: Yup.number()
|
supplier_ids: number[];
|
||||||
.required('Harga produk wajib diisi!')
|
flags: string[];
|
||||||
.typeError('Harga produk wajib diisi!')
|
};
|
||||||
.min(0, 'Harga produk tidak boleh kurang dari 0!'),
|
|
||||||
selling_price: Yup.number()
|
export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
|
||||||
.required('Harga jual wajib diisi!')
|
Yup.object({
|
||||||
.typeError('Harga jual wajib diisi!')
|
name: Yup.string().required('Nama wajib diisi!'),
|
||||||
.min(0, 'Harga jual tidak boleh kurang dari 0!'),
|
brand: Yup.string().required('Merek wajib diisi!'),
|
||||||
tax: Yup.number()
|
sku: Yup.string().required('SKU wajib diisi!'),
|
||||||
.required('Pajak wajib diisi!')
|
|
||||||
.typeError('Pajak wajib diisi!')
|
uom: Yup.object({
|
||||||
.min(0, 'Pajak tidak boleh kurang dari 0!')
|
value: Yup.number().min(1).required(),
|
||||||
.max(100, 'Pajak tidak boleh lebih dari 100%!'),
|
label: Yup.string().required(),
|
||||||
expiry_period: Yup.number()
|
}).nullable().required('Satuan wajib diisi!'),
|
||||||
.required('Periode kadaluarsa wajib diisi!')
|
|
||||||
.typeError('Periode kadaluarsa wajib diisi!')
|
uom_id: Yup.number()
|
||||||
.min(0, 'Periode kadaluarsa tidak boleh kurang dari 0!'),
|
.required('Satuan wajib diisi!')
|
||||||
supplier: Yup.object({
|
.typeError('Satuan wajib diisi!'),
|
||||||
value: Yup.number().min(1).required(),
|
|
||||||
label: Yup.string().required(),
|
product_category: Yup.object({
|
||||||
}).nullable(),
|
value: Yup.number().min(1).required(),
|
||||||
supplier_ids: Yup.array()
|
label: Yup.string().required(),
|
||||||
.of(Yup.number().typeError('Supplier tidak valid!'))
|
}).nullable().required('Kategori produk wajib diisi!'),
|
||||||
.min(1, 'Minimal harus ada 1 supplier!')
|
|
||||||
.required('Supplier wajib diisi!'),
|
product_category_id: Yup.number()
|
||||||
flags: Yup.array()
|
.required('Kategori produk wajib diisi!')
|
||||||
.of(Yup.string())
|
.typeError('Kategori produk wajib diisi!'),
|
||||||
.min(1, 'Minimal harus ada 1 flag!')
|
|
||||||
.required('Flag wajib diisi!'),
|
product_price: Yup.number()
|
||||||
});
|
.required('Harga produk wajib diisi!')
|
||||||
|
.typeError('Harga produk wajib diisi!')
|
||||||
|
.min(0, 'Harga produk tidak boleh kurang dari 0!'),
|
||||||
|
|
||||||
|
selling_price: Yup.number()
|
||||||
|
.required('Harga jual wajib diisi!')
|
||||||
|
.typeError('Harga jual wajib diisi!')
|
||||||
|
.min(0, 'Harga jual tidak boleh kurang dari 0!'),
|
||||||
|
|
||||||
|
tax: Yup.number()
|
||||||
|
.required('Pajak wajib diisi!')
|
||||||
|
.typeError('Pajak wajib diisi!')
|
||||||
|
.min(0, 'Pajak tidak boleh kurang dari 0!')
|
||||||
|
.max(100, 'Pajak tidak boleh lebih dari 100%!'),
|
||||||
|
|
||||||
|
expiry_period: Yup.number()
|
||||||
|
.required('Periode kadaluarsa wajib diisi!')
|
||||||
|
.typeError('Periode kadaluarsa wajib diisi!')
|
||||||
|
.min(0, 'Periode kadaluarsa tidak boleh kurang dari 0!'),
|
||||||
|
|
||||||
|
supplier_ids: Yup.array()
|
||||||
|
.of(Yup.number().required().typeError('Supplier tidak valid!'))
|
||||||
|
.min(1, 'Minimal harus ada 1 supplier!')
|
||||||
|
.required('Supplier wajib diisi!'),
|
||||||
|
|
||||||
|
flags: Yup.array()
|
||||||
|
.of(Yup.string().required())
|
||||||
|
.min(1, 'Minimal harus ada 1 flag!')
|
||||||
|
.required('Flag wajib diisi!'),
|
||||||
|
});
|
||||||
|
|
||||||
export const UpdateProductFormSchema = ProductFormSchema;
|
export const UpdateProductFormSchema = ProductFormSchema;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
|
|
||||||
@@ -79,20 +83,19 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
sku: initialValues?.sku ?? '',
|
sku: initialValues?.sku ?? '',
|
||||||
uom: initialValues?.uom
|
uom: initialValues?.uom
|
||||||
? { value: initialValues.uom.id, label: initialValues.uom.name }
|
? { value: initialValues.uom.id, label: initialValues.uom.name }
|
||||||
: null,
|
: undefined,
|
||||||
uom_id: initialValues?.uom?.id ?? 0,
|
uom_id: initialValues?.uom?.id ?? 0,
|
||||||
product_category: initialValues?.product_category
|
product_category: initialValues?.product_category
|
||||||
? {
|
? {
|
||||||
value: initialValues.product_category.id,
|
value: initialValues.product_category.id,
|
||||||
label: initialValues.product_category.name,
|
label: initialValues.product_category.name,
|
||||||
}
|
}
|
||||||
: null,
|
: undefined,
|
||||||
product_category_id: initialValues?.product_category?.id ?? 0,
|
product_category_id: initialValues?.product_category?.id ?? 0,
|
||||||
product_price: initialValues?.product_price ?? 0,
|
product_price: initialValues?.product_price ?? '',
|
||||||
selling_price: initialValues?.selling_price ?? 0,
|
selling_price: initialValues?.selling_price ?? '',
|
||||||
tax: initialValues?.tax ?? 0,
|
tax: initialValues?.tax ?? '',
|
||||||
expiry_period: initialValues?.expiry_period ?? 0,
|
expiry_period: initialValues?.expiry_period ?? '',
|
||||||
supplier: null, // not used for payload, just for UI
|
|
||||||
supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
|
supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
|
||||||
flags: initialValues?.flags ?? [],
|
flags: initialValues?.flags ?? [],
|
||||||
}),
|
}),
|
||||||
@@ -111,14 +114,14 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
sku: values.sku,
|
sku: values.sku,
|
||||||
uom_id: values.uom_id,
|
uom_id: values.uom_id,
|
||||||
product_category_id: values.product_category_id,
|
product_category_id: values.product_category_id,
|
||||||
product_price: values.product_price,
|
product_price: parseInt(values.product_price.toString()) || 0,
|
||||||
selling_price: values.selling_price,
|
selling_price: parseInt(values.selling_price.toString()) || 0,
|
||||||
tax: values.tax,
|
tax: parseInt(values.tax.toString()) || 0,
|
||||||
expiry_period: values.expiry_period,
|
expiry_period: parseInt(values.expiry_period.toString()) || 0,
|
||||||
supplier_ids: (values.supplier_ids ?? []).filter(
|
supplier_ids: values.supplier_ids.filter(
|
||||||
(id): id is number => typeof id === 'number'
|
(id): id is number => typeof id === 'number'
|
||||||
),
|
),
|
||||||
flags: (values.flags ?? []).filter(
|
flags: values.flags.filter(
|
||||||
(f): f is string => typeof f === 'string'
|
(f): f is string => typeof f === 'string'
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -136,15 +139,11 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
const { setValues: formikSetValues } = formik;
|
const { setValues: formikSetValues } = formik;
|
||||||
|
|
||||||
// UOM
|
// UOM
|
||||||
const [uomSelectInputValue, setUomSelectInputValue] = useState('');
|
const {
|
||||||
const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({ search: uomSelectInputValue ?? '' }).toString()}`;
|
setInputValue: setUomSelectInputValue,
|
||||||
const { data: uoms, isLoading: isLoadingUoms } = useSWR(
|
options: uomOptions,
|
||||||
uomsUrl,
|
isLoadingOptions: isLoadingUoms,
|
||||||
UomApi.getAllFetcher
|
} = useSelect(UomApi.basePath, 'id', 'name');
|
||||||
);
|
|
||||||
const uomOptions = isResponseSuccess(uoms)
|
|
||||||
? uoms?.data.map((uom) => ({ value: uom.id, label: uom.name }))
|
|
||||||
: [];
|
|
||||||
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('uom', true);
|
formik.setFieldTouched('uom', true);
|
||||||
formik.setFieldValue('uom', val);
|
formik.setFieldValue('uom', val);
|
||||||
@@ -153,15 +152,11 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Product Category
|
// Product Category
|
||||||
const [categorySelectInputValue, setCategorySelectInputValue] = useState('');
|
const {
|
||||||
const categoriesUrl = `${ProductCategoryApi.basePath}?${new URLSearchParams({ search: categorySelectInputValue ?? '' }).toString()}`;
|
setInputValue: setCategorySelectInputValue,
|
||||||
const { data: categories, isLoading: isLoadingCategories } = useSWR(
|
options: categoryOptions,
|
||||||
categoriesUrl,
|
isLoadingOptions: isLoadingCategories,
|
||||||
ProductCategoryApi.getAllFetcher
|
} = useSelect(ProductCategoryApi.basePath, 'id', 'name');
|
||||||
);
|
|
||||||
const categoryOptions = isResponseSuccess(categories)
|
|
||||||
? categories?.data.map((cat) => ({ value: cat.id, label: cat.name }))
|
|
||||||
: [];
|
|
||||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldTouched('product_category', true);
|
formik.setFieldTouched('product_category', true);
|
||||||
formik.setFieldValue('product_category', val);
|
formik.setFieldValue('product_category', val);
|
||||||
@@ -169,7 +164,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
formik.setFieldValue('product_category_id', (val as OptionType)?.value);
|
formik.setFieldValue('product_category_id', (val as OptionType)?.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Supplier (multi select)
|
// Supplier (multi select) - using SWR to filter by category
|
||||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
||||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
|
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
|
||||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
||||||
@@ -209,7 +204,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full max-w-xl'>
|
<section className='w-full max-w-2xl'>
|
||||||
<header className='flex flex-col gap-4'>
|
<header className='flex flex-col gap-4'>
|
||||||
<Button
|
<Button
|
||||||
href='/master-data/product'
|
href='/master-data/product'
|
||||||
@@ -235,7 +230,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
required
|
required
|
||||||
label='Nama'
|
label='Nama'
|
||||||
name='name'
|
name='name'
|
||||||
placeholder='Masukkan nama produk'
|
placeholder='Masukkan nama...'
|
||||||
value={formik.values.name}
|
value={formik.values.name}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -247,7 +242,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
required
|
required
|
||||||
label='Merek'
|
label='Merek'
|
||||||
name='brand'
|
name='brand'
|
||||||
placeholder='Masukkan merek produk'
|
placeholder='Masukkan merek...'
|
||||||
value={formik.values.brand}
|
value={formik.values.brand}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -259,7 +254,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
required
|
required
|
||||||
label='SKU'
|
label='SKU'
|
||||||
name='sku'
|
name='sku'
|
||||||
placeholder='Masukkan SKU produk'
|
placeholder='Masukkan SKU...'
|
||||||
value={formik.values.sku}
|
value={formik.values.sku}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
@@ -270,6 +265,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Satuan'
|
label='Satuan'
|
||||||
|
placeholder='Pilih satuan...'
|
||||||
value={formik.values.uom ?? undefined}
|
value={formik.values.uom ?? undefined}
|
||||||
onChange={uomChangeHandler}
|
onChange={uomChangeHandler}
|
||||||
options={uomOptions}
|
options={uomOptions}
|
||||||
@@ -283,6 +279,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Kategori Produk'
|
label='Kategori Produk'
|
||||||
|
placeholder='Pilih kategori produk...'
|
||||||
value={formik.values.product_category ?? undefined}
|
value={formik.values.product_category ?? undefined}
|
||||||
onChange={categoryChangeHandler}
|
onChange={categoryChangeHandler}
|
||||||
options={categoryOptions}
|
options={categoryOptions}
|
||||||
@@ -296,15 +293,19 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
isClearable
|
isClearable
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Harga Produk'
|
label='Harga Produk'
|
||||||
name='product_price'
|
name='product_price'
|
||||||
type='number'
|
placeholder='Masukkan harga produk...'
|
||||||
placeholder='Masukkan harga produk'
|
|
||||||
value={formik.values.product_price}
|
value={formik.values.product_price}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={2}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
inputPrefix='Rp '
|
||||||
isError={
|
isError={
|
||||||
formik.touched.product_price &&
|
formik.touched.product_price &&
|
||||||
Boolean(formik.errors.product_price)
|
Boolean(formik.errors.product_price)
|
||||||
@@ -312,15 +313,19 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
errorMessage={formik.errors.product_price as string}
|
errorMessage={formik.errors.product_price as string}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Harga Jual'
|
label='Harga Jual'
|
||||||
name='selling_price'
|
name='selling_price'
|
||||||
type='number'
|
placeholder='Masukkan harga jual...'
|
||||||
placeholder='Masukkan harga jual'
|
|
||||||
value={formik.values.selling_price}
|
value={formik.values.selling_price}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={2}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
inputPrefix='Rp '
|
||||||
isError={
|
isError={
|
||||||
formik.touched.selling_price &&
|
formik.touched.selling_price &&
|
||||||
Boolean(formik.errors.selling_price)
|
Boolean(formik.errors.selling_price)
|
||||||
@@ -328,28 +333,36 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
errorMessage={formik.errors.selling_price as string}
|
errorMessage={formik.errors.selling_price as string}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Pajak (%)'
|
label='Pajak (%)'
|
||||||
name='tax'
|
name='tax'
|
||||||
type='number'
|
placeholder='Masukkan pajak...'
|
||||||
placeholder='Masukkan pajak'
|
|
||||||
value={formik.values.tax}
|
value={formik.values.tax}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={2}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
inputSuffix='%'
|
||||||
isError={formik.touched.tax && Boolean(formik.errors.tax)}
|
isError={formik.touched.tax && Boolean(formik.errors.tax)}
|
||||||
errorMessage={formik.errors.tax as string}
|
errorMessage={formik.errors.tax as string}
|
||||||
readOnly={type === 'detail'}
|
readOnly={type === 'detail'}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Periode Kadaluarsa (hari)'
|
label='Periode Kadaluarsa (hari)'
|
||||||
name='expiry_period'
|
name='expiry_period'
|
||||||
type='number'
|
placeholder='Masukkan periode kadaluarsa...'
|
||||||
placeholder='Masukkan periode kadaluarsa'
|
|
||||||
value={formik.values.expiry_period}
|
value={formik.values.expiry_period}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
decimalScale={0}
|
||||||
|
allowNegative={false}
|
||||||
|
thousandSeparator=','
|
||||||
|
decimalSeparator='.'
|
||||||
|
inputSuffix='hari'
|
||||||
isError={
|
isError={
|
||||||
formik.touched.expiry_period &&
|
formik.touched.expiry_period &&
|
||||||
Boolean(formik.errors.expiry_period)
|
Boolean(formik.errors.expiry_period)
|
||||||
@@ -360,9 +373,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Supplier'
|
label='Supplier'
|
||||||
|
placeholder='Pilih supplier...'
|
||||||
isMulti
|
isMulti
|
||||||
value={supplierOptions.filter((opt) =>
|
value={supplierOptions.filter((opt) =>
|
||||||
formik.values.supplier_ids.includes(opt.value)
|
(formik.values.supplier_ids || []).includes(opt.value)
|
||||||
)}
|
)}
|
||||||
onChange={supplierChangeHandler}
|
onChange={supplierChangeHandler}
|
||||||
options={supplierOptions}
|
options={supplierOptions}
|
||||||
@@ -379,9 +393,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Flags'
|
label='Flags'
|
||||||
|
placeholder='Pilih flags...'
|
||||||
isMulti
|
isMulti
|
||||||
value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
|
value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
|
||||||
formik.values.flags.includes(opt.value)
|
(formik.values.flags || []).includes(opt.value)
|
||||||
)}
|
)}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
CreateMovementPayload,
|
CreateMovementPayload,
|
||||||
Movement,
|
Movement,
|
||||||
UpdateMovementPayload,
|
|
||||||
} from '@/types/api/inventory/movement';
|
} from '@/types/api/inventory/movement';
|
||||||
import {
|
import {
|
||||||
CreateInventoryAdjustmentPayload,
|
CreateInventoryAdjustmentPayload,
|
||||||
@@ -23,7 +22,7 @@ export const ProductWarehouseApi = new BaseApiService<
|
|||||||
export const MovementApi = new BaseApiService<
|
export const MovementApi = new BaseApiService<
|
||||||
Movement,
|
Movement,
|
||||||
CreateMovementPayload,
|
CreateMovementPayload,
|
||||||
UpdateMovementPayload
|
unknown
|
||||||
>('/inventory/transfers');
|
>('/inventory/transfers');
|
||||||
|
|
||||||
export const inventoryAdjustmentApi = new BaseApiService<
|
export const inventoryAdjustmentApi = new BaseApiService<
|
||||||
|
|||||||
-2
@@ -71,5 +71,3 @@ export type CreateMovementPayload = {
|
|||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateMovementPayload = CreateMovementPayload;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user