mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 05:45:46 +00:00
refactor(FE-238-106): change dateinput and create chickin page and pull development
This commit is contained in:
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Area } from '@/types/api/master-data/area';
|
||||
import { AreaApi } from '@/services/api/master-data';
|
||||
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/area/detail/?areaId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,7 +142,7 @@ const AreasTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -199,10 +191,15 @@ const AreasTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/area/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/area/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Area
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Bank } from '@/types/api/master-data/bank';
|
||||
import { BankApi } from '@/services/api/master-data';
|
||||
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/bank/detail/?bankId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -163,7 +155,7 @@ const BanksTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -212,10 +204,15 @@ const BanksTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/bank/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/bank/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Bank
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import Table from '@/components/Table';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { cn } from '@/lib/helper';
|
||||
@@ -15,10 +16,7 @@ import { CustomerApi } from '@/services/api/master-data';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { Customer } from '@/types/api/master-data/customer';
|
||||
import { Icon } from '@iconify/react';
|
||||
import {
|
||||
CellContext,
|
||||
ColumnDef,
|
||||
} from '@tanstack/react-table';
|
||||
import { CellContext, ColumnDef } from '@tanstack/react-table';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR from 'swr';
|
||||
@@ -33,16 +31,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type == 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/customer/detail/?customerId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -53,10 +42,10 @@ const RowOptionsMenu = ({
|
||||
Detail
|
||||
</Button>
|
||||
<Button
|
||||
className='justify-start text-sm'
|
||||
href={`/master-data/customer/detail/edit/?customerId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||
Edit
|
||||
@@ -65,7 +54,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -75,7 +64,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -174,7 +163,7 @@ const CustomersTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -210,10 +199,15 @@ const CustomersTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/customer/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/customer/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Customer
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -285,4 +279,4 @@ const CustomersTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomersTable;
|
||||
export default CustomersTable;
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { CustomerFormSchema, CustomerFormValues, UpdateCustomerFormSchema } from '@/components/pages/master-data/customer/form/CustomerForm.schema';
|
||||
import {
|
||||
CustomerFormSchema,
|
||||
CustomerFormValues,
|
||||
UpdateCustomerFormSchema,
|
||||
} from '@/components/pages/master-data/customer/form/CustomerForm.schema';
|
||||
import { useFormik } from 'formik';
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -150,7 +154,8 @@ const CustomerForm = ({
|
||||
const formik = useFormik<CustomerFormValues>({
|
||||
initialValues: formikInitialValues,
|
||||
enableReinitialize: true,
|
||||
validationSchema: formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema,
|
||||
validationSchema:
|
||||
formType === 'edit' ? UpdateCustomerFormSchema : CustomerFormSchema,
|
||||
onSubmit: async (values) => {
|
||||
// reset error message
|
||||
setCustomerFormErrorMessage('');
|
||||
|
||||
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Fcr } from '@/types/api/master-data/fcr';
|
||||
import { FcrApi } from '@/services/api/master-data';
|
||||
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/fcr/detail/?fcrId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,7 +142,7 @@ const FcrsTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -199,10 +191,15 @@ const FcrsTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/fcr/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/fcr/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah FCR
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { FlockApi } from '@/services/api/master-data';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
@@ -30,16 +31,7 @@ const RowsOptions = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type == 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/flock/detail/edit/?flockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -54,7 +46,7 @@ const RowsOptions = ({
|
||||
/>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
href={`/master-data/flock/detail/?flockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='primary'
|
||||
@@ -72,7 +64,7 @@ const RowsOptions = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -82,7 +74,7 @@ const RowsOptions = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -203,9 +195,15 @@ const FlockTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/flock/add' color='primary'>
|
||||
Tambah Flock
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/flock/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -275,4 +273,4 @@ const FlockTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default FlockTable;
|
||||
export default FlockTable;
|
||||
|
||||
@@ -3,10 +3,7 @@ import * as Yup from 'yup';
|
||||
export const FlockFormSchema = Yup.object({
|
||||
name: Yup.string()
|
||||
.required('Nama wajib diisi!')
|
||||
.matches(
|
||||
/^[\p{L}\p{N}\s]+$/u,
|
||||
'Nama tidak boleh mengandung simbol'
|
||||
),
|
||||
.matches(/^[\p{L}\p{N}\s]+$/u, 'Nama tidak boleh mengandung simbol'),
|
||||
});
|
||||
|
||||
export const UpdateFlockFormSchema = FlockFormSchema;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import { useModal } from '@/components/Modal';
|
||||
import { FlockApi } from '@/services/api/master-data';
|
||||
import { Flock } from '@/types/api/master-data/flock';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FlockFormSchema, FlockFormValues, UpdateFlockFormSchema } from '@/components/pages/master-data/flock/form/FlockForm.schema';
|
||||
import {
|
||||
FlockFormSchema,
|
||||
FlockFormValues,
|
||||
UpdateFlockFormSchema,
|
||||
} from '@/components/pages/master-data/flock/form/FlockForm.schema';
|
||||
import { useFormik } from 'formik';
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -48,7 +52,8 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
|
||||
const formik = useFormik<FlockFormValues>({
|
||||
initialValues: formikInitialValue,
|
||||
enableReinitialize: true,
|
||||
validationSchema: formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
|
||||
validationSchema:
|
||||
formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
|
||||
onSubmit: async (values) => {
|
||||
// reset error message
|
||||
setFlockFormErrorMessage('');
|
||||
|
||||
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { KandangApi } from '@/services/api/master-data';
|
||||
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/kandang/detail/?kandangId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -185,7 +177,7 @@ const KandangsTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -250,10 +242,15 @@ const KandangsTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/kandang/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/kandang/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Kandang
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Location } from '@/types/api/master-data/location';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/location/detail/?locationId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -172,7 +164,7 @@ const LocationsTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -237,10 +229,15 @@ const LocationsTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/location/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/location/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Location
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||
import { NonstockApi } from '@/services/api/master-data';
|
||||
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/nonstock/detail/?nonstockId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -71,7 +63,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -184,7 +176,7 @@ const NonstocksTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -249,10 +241,15 @@ const NonstocksTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/nonstock/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/nonstock/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Nonstock
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||
import { ProductCategoryApi } from '@/services/api/master-data';
|
||||
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/product-category/detail/?productCategoryId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -64,7 +56,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='mdi:delete-outline'
|
||||
@@ -74,7 +66,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -154,7 +146,7 @@ const ProductCategoryTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -200,10 +192,15 @@ const ProductCategoryTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/product-category/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/product-category/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Product Category
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
<DebouncedTextInput
|
||||
|
||||
+6
-2
@@ -1,10 +1,14 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const ProductCategoryFormSchema = Yup.object({
|
||||
code: Yup.string().required('Kode wajib diisi!').max(3, 'Kode kategori produk melebihi 3 karakter!'),
|
||||
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 type ProductCategoryFormValues = Yup.InferType<typeof ProductCategoryFormSchema>;
|
||||
export type ProductCategoryFormValues = Yup.InferType<
|
||||
typeof ProductCategoryFormSchema
|
||||
>;
|
||||
|
||||
@@ -30,7 +30,10 @@ interface ProductCategoryFormProps {
|
||||
initialValues?: ProductCategory;
|
||||
}
|
||||
|
||||
const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFormProps) => {
|
||||
const ProductCategoryForm = ({
|
||||
type = 'add',
|
||||
initialValues,
|
||||
}: ProductCategoryFormProps) => {
|
||||
const router = useRouter();
|
||||
const deleteModal = useModal();
|
||||
|
||||
@@ -77,7 +80,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
|
||||
|
||||
const formik = useFormik<ProductCategoryFormValues>({
|
||||
initialValues: formikInitialValues,
|
||||
validationSchema: type === 'edit' ? UpdateProductCategoryFormSchema : ProductCategoryFormSchema,
|
||||
validationSchema:
|
||||
type === 'edit'
|
||||
? UpdateProductCategoryFormSchema
|
||||
: ProductCategoryFormSchema,
|
||||
onSubmit: async (values) => {
|
||||
setFormErrorMessage('');
|
||||
|
||||
@@ -91,7 +97,10 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
|
||||
await createProductCategoryHandler(payload);
|
||||
break;
|
||||
case 'edit':
|
||||
await updateProductCategoryHandler(initialValues?.id as number, payload);
|
||||
await updateProductCategoryHandler(
|
||||
initialValues?.id as number,
|
||||
payload
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -263,4 +272,4 @@ const ProductCategoryForm = ({ type = 'add', initialValues }: ProductCategoryFor
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductCategoryForm;
|
||||
export default ProductCategoryForm;
|
||||
|
||||
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Product } from '@/types/api/master-data/product';
|
||||
import { ProductApi } from '@/services/api/master-data';
|
||||
@@ -36,16 +37,7 @@ const RowOptionsMenu = ({
|
||||
props: CellContext<Product, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/product/detail/?productId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -68,7 +60,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -78,7 +70,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
|
||||
const ProductsTable = () => {
|
||||
@@ -217,7 +209,7 @@ const ProductsTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -280,10 +272,15 @@ const ProductsTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/product/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/product/add'
|
||||
variant='outline'
|
||||
className='w-full sm:w-fit'
|
||||
color='primary'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Produk
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
<DebouncedTextInput
|
||||
|
||||
@@ -5,49 +5,50 @@ export const ProductFormSchema = Yup.object({
|
||||
brand: Yup.string().required('Merek wajib diisi!'),
|
||||
sku: Yup.string().required('SKU wajib diisi!'),
|
||||
uom: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
uom_id: Yup.number().required('Satuan wajib diisi!').typeError('Satuan wajib diisi!'),
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
uom_id: Yup.number()
|
||||
.required('Satuan wajib diisi!')
|
||||
.typeError('Satuan wajib diisi!'),
|
||||
product_category: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
product_category_id: Yup.number()
|
||||
.required('Kategori produk wajib diisi!')
|
||||
.typeError('Kategori produk wajib diisi!'),
|
||||
.required('Kategori produk wajib diisi!')
|
||||
.typeError('Kategori produk 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!'),
|
||||
.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!'),
|
||||
.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%!'),
|
||||
.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!'),
|
||||
.required('Periode kadaluarsa wajib diisi!')
|
||||
.typeError('Periode kadaluarsa wajib diisi!')
|
||||
.min(0, 'Periode kadaluarsa tidak boleh kurang dari 0!'),
|
||||
supplier: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
}).nullable(),
|
||||
supplier_ids: Yup.array()
|
||||
.of(Yup.number().typeError('Supplier tidak valid!'))
|
||||
.min(1, 'Minimal harus ada 1 supplier!')
|
||||
.required('Supplier wajib diisi!'),
|
||||
.of(Yup.number().typeError('Supplier tidak valid!'))
|
||||
.min(1, 'Minimal harus ada 1 supplier!')
|
||||
.required('Supplier wajib diisi!'),
|
||||
flags: Yup.array()
|
||||
.of(Yup.string())
|
||||
.min(1, 'Minimal harus ada 1 flag!')
|
||||
.required('Flag wajib diisi!'),
|
||||
.of(Yup.string())
|
||||
.min(1, 'Minimal harus ada 1 flag!')
|
||||
.required('Flag wajib diisi!'),
|
||||
});
|
||||
|
||||
export const UpdateProductFormSchema = ProductFormSchema;
|
||||
|
||||
export type ProductFormValues = Yup.InferType<typeof ProductFormSchema>;
|
||||
|
||||
|
||||
@@ -24,7 +24,12 @@ import {
|
||||
CreateProductPayload,
|
||||
UpdateProductPayload,
|
||||
} from '@/types/api/master-data/product';
|
||||
import { UomApi, ProductCategoryApi, SupplierApi, ProductApi } from '@/services/api/master-data';
|
||||
import {
|
||||
UomApi,
|
||||
ProductCategoryApi,
|
||||
SupplierApi,
|
||||
ProductApi,
|
||||
} from '@/services/api/master-data';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
|
||||
|
||||
@@ -67,30 +72,37 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
[router]
|
||||
);
|
||||
|
||||
const formikInitialValues = useMemo<ProductFormValues>(() => ({
|
||||
name: initialValues?.name ?? '',
|
||||
brand: initialValues?.brand ?? '',
|
||||
sku: initialValues?.sku ?? '',
|
||||
uom: initialValues?.uom
|
||||
? { value: initialValues.uom.id, label: initialValues.uom.name }
|
||||
: null,
|
||||
uom_id: initialValues?.uom?.id ?? 0,
|
||||
product_category: initialValues?.product_category
|
||||
? { value: initialValues.product_category.id, label: initialValues.product_category.name }
|
||||
: null,
|
||||
product_category_id: initialValues?.product_category?.id ?? 0,
|
||||
product_price: initialValues?.product_price ?? 0,
|
||||
selling_price: initialValues?.selling_price ?? 0,
|
||||
tax: initialValues?.tax ?? 0,
|
||||
expiry_period: initialValues?.expiry_period ?? 0,
|
||||
supplier: null, // not used for payload, just for UI
|
||||
supplier_ids: initialValues?.suppliers?.map(s => s.id) ?? [],
|
||||
flags: initialValues?.flags ?? [],
|
||||
}), [initialValues]);
|
||||
const formikInitialValues = useMemo<ProductFormValues>(
|
||||
() => ({
|
||||
name: initialValues?.name ?? '',
|
||||
brand: initialValues?.brand ?? '',
|
||||
sku: initialValues?.sku ?? '',
|
||||
uom: initialValues?.uom
|
||||
? { value: initialValues.uom.id, label: initialValues.uom.name }
|
||||
: null,
|
||||
uom_id: initialValues?.uom?.id ?? 0,
|
||||
product_category: initialValues?.product_category
|
||||
? {
|
||||
value: initialValues.product_category.id,
|
||||
label: initialValues.product_category.name,
|
||||
}
|
||||
: null,
|
||||
product_category_id: initialValues?.product_category?.id ?? 0,
|
||||
product_price: initialValues?.product_price ?? 0,
|
||||
selling_price: initialValues?.selling_price ?? 0,
|
||||
tax: initialValues?.tax ?? 0,
|
||||
expiry_period: initialValues?.expiry_period ?? 0,
|
||||
supplier: null, // not used for payload, just for UI
|
||||
supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
|
||||
flags: initialValues?.flags ?? [],
|
||||
}),
|
||||
[initialValues]
|
||||
);
|
||||
|
||||
const formik = useFormik<ProductFormValues>({
|
||||
initialValues: formikInitialValues,
|
||||
validationSchema: type === 'edit' ? UpdateProductFormSchema : ProductFormSchema,
|
||||
validationSchema:
|
||||
type === 'edit' ? UpdateProductFormSchema : ProductFormSchema,
|
||||
onSubmit: async (values) => {
|
||||
setProductFormErrorMessage('');
|
||||
const payload: CreateProductPayload = {
|
||||
@@ -103,8 +115,12 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
selling_price: values.selling_price,
|
||||
tax: values.tax,
|
||||
expiry_period: values.expiry_period,
|
||||
supplier_ids: (values.supplier_ids ?? []).filter((id): id is number => typeof id === 'number'),
|
||||
flags: (values.flags ?? []).filter((f): f is string => typeof f === 'string'),
|
||||
supplier_ids: (values.supplier_ids ?? []).filter(
|
||||
(id): id is number => typeof id === 'number'
|
||||
),
|
||||
flags: (values.flags ?? []).filter(
|
||||
(f): f is string => typeof f === 'string'
|
||||
),
|
||||
};
|
||||
switch (type) {
|
||||
case 'add':
|
||||
@@ -122,7 +138,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
// UOM
|
||||
const [uomSelectInputValue, setUomSelectInputValue] = useState('');
|
||||
const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({ search: uomSelectInputValue ?? '' }).toString()}`;
|
||||
const { data: uoms, isLoading: isLoadingUoms } = useSWR(uomsUrl, UomApi.getAllFetcher);
|
||||
const { data: uoms, isLoading: isLoadingUoms } = useSWR(
|
||||
uomsUrl,
|
||||
UomApi.getAllFetcher
|
||||
);
|
||||
const uomOptions = isResponseSuccess(uoms)
|
||||
? uoms?.data.map((uom) => ({ value: uom.id, label: uom.name }))
|
||||
: [];
|
||||
@@ -136,7 +155,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
// Product Category
|
||||
const [categorySelectInputValue, setCategorySelectInputValue] = useState('');
|
||||
const categoriesUrl = `${ProductCategoryApi.basePath}?${new URLSearchParams({ search: categorySelectInputValue ?? '' }).toString()}`;
|
||||
const { data: categories, isLoading: isLoadingCategories } = useSWR(categoriesUrl, ProductCategoryApi.getAllFetcher);
|
||||
const { data: categories, isLoading: isLoadingCategories } = useSWR(
|
||||
categoriesUrl,
|
||||
ProductCategoryApi.getAllFetcher
|
||||
);
|
||||
const categoryOptions = isResponseSuccess(categories)
|
||||
? categories?.data.map((cat) => ({ value: cat.id, label: cat.name }))
|
||||
: [];
|
||||
@@ -150,16 +172,22 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
// Supplier (multi select)
|
||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
|
||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(suppliersUrl, SupplierApi.getAllFetcher);
|
||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
||||
suppliersUrl,
|
||||
SupplierApi.getAllFetcher
|
||||
);
|
||||
const supplierOptions = isResponseSuccess(suppliers)
|
||||
? suppliers?.data
|
||||
.filter((sup) => sup.category === 'SAPRONAK')
|
||||
.map((sup) => ({ value: sup.id, label: sup.name }))
|
||||
: [];
|
||||
? suppliers?.data
|
||||
.filter((sup) => sup.category === 'SAPRONAK')
|
||||
.map((sup) => ({ value: sup.id, label: sup.name }))
|
||||
: [];
|
||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||
formik.setFieldTouched('supplier_ids', true);
|
||||
formik.setFieldValue('supplier_ids', arr.map((v) => (v as OptionType).value));
|
||||
formik.setFieldValue(
|
||||
'supplier_ids',
|
||||
arr.map((v) => (v as OptionType).value)
|
||||
);
|
||||
};
|
||||
|
||||
const deleteProductClickHandler = () => {
|
||||
@@ -260,7 +288,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
options={categoryOptions}
|
||||
onInputChange={setCategorySelectInputValue}
|
||||
isLoading={isLoadingCategories}
|
||||
isError={formik.touched.product_category_id && Boolean(formik.errors.product_category_id)}
|
||||
isError={
|
||||
formik.touched.product_category_id &&
|
||||
Boolean(formik.errors.product_category_id)
|
||||
}
|
||||
errorMessage={formik.errors.product_category_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
@@ -274,7 +305,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
value={formik.values.product_price}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={formik.touched.product_price && Boolean(formik.errors.product_price)}
|
||||
isError={
|
||||
formik.touched.product_price &&
|
||||
Boolean(formik.errors.product_price)
|
||||
}
|
||||
errorMessage={formik.errors.product_price as string}
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
@@ -287,7 +321,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
value={formik.values.selling_price}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={formik.touched.selling_price && Boolean(formik.errors.selling_price)}
|
||||
isError={
|
||||
formik.touched.selling_price &&
|
||||
Boolean(formik.errors.selling_price)
|
||||
}
|
||||
errorMessage={formik.errors.selling_price as string}
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
@@ -313,7 +350,10 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
value={formik.values.expiry_period}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={formik.touched.expiry_period && Boolean(formik.errors.expiry_period)}
|
||||
isError={
|
||||
formik.touched.expiry_period &&
|
||||
Boolean(formik.errors.expiry_period)
|
||||
}
|
||||
errorMessage={formik.errors.expiry_period as string}
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
@@ -321,12 +361,17 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
required
|
||||
label='Supplier'
|
||||
isMulti
|
||||
value={supplierOptions.filter(opt => formik.values.supplier_ids.includes(opt.value))}
|
||||
value={supplierOptions.filter((opt) =>
|
||||
formik.values.supplier_ids.includes(opt.value)
|
||||
)}
|
||||
onChange={supplierChangeHandler}
|
||||
options={supplierOptions}
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
isLoading={isLoadingSuppliers}
|
||||
isError={formik.touched.supplier_ids && Boolean(formik.errors.supplier_ids)}
|
||||
isError={
|
||||
formik.touched.supplier_ids &&
|
||||
Boolean(formik.errors.supplier_ids)
|
||||
}
|
||||
errorMessage={formik.errors.supplier_ids as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
@@ -335,10 +380,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
required
|
||||
label='Flags'
|
||||
isMulti
|
||||
value={PRODUCT_FLAG_OPTIONS.filter(opt => formik.values.flags.includes(opt.value))}
|
||||
onChange={val => {
|
||||
value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
|
||||
formik.values.flags.includes(opt.value)
|
||||
)}
|
||||
onChange={(val) => {
|
||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||
formik.setFieldValue('flags', arr.map((v) => (v as OptionType).value));
|
||||
formik.setFieldValue(
|
||||
'flags',
|
||||
arr.map((v) => (v as OptionType).value)
|
||||
);
|
||||
}}
|
||||
options={PRODUCT_FLAG_OPTIONS}
|
||||
isError={formik.touched.flags && Boolean(formik.errors.flags)}
|
||||
@@ -435,4 +485,4 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductForm;
|
||||
export default ProductForm;
|
||||
|
||||
@@ -8,6 +8,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import Table from '@/components/Table';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { cn } from '@/lib/helper';
|
||||
@@ -30,16 +31,7 @@ const RowOptions = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type == 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/supplier/detail/?supplierId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -72,7 +64,7 @@ const RowOptions = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -82,7 +74,7 @@ const RowOptions = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -226,10 +218,15 @@ const SuppliersTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/supplier/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/supplier/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Supplier
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const SupplierFormSchema = Yup.object({
|
||||
name: Yup.string().required('Nama wajib diisi!'),
|
||||
alias: Yup.string()
|
||||
.matches(/^[A-Za-z0-9]+$/, 'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!')
|
||||
name: Yup.string().required('Nama wajib diisi!'),
|
||||
alias: Yup.string()
|
||||
.matches(
|
||||
/^[A-Za-z0-9]+$/,
|
||||
'Alias hanya boleh berisi huruf dan angka tanpa spasi atau simbol!'
|
||||
)
|
||||
.max(5, 'Alias maksimal 5 karakter!')
|
||||
.required('Alias wajib diisi!'),
|
||||
pic: Yup.string().required('PIC wajib diisi!'),
|
||||
type: Yup.object({
|
||||
value: Yup.string().required(),
|
||||
label: Yup.string().required(),
|
||||
})
|
||||
.required('Tipe wajib diisi!'),
|
||||
category: Yup.object({
|
||||
value: Yup.string().required(),
|
||||
label: Yup.string().required(),
|
||||
})
|
||||
.required('Tipe wajib diisi!'),
|
||||
hatchery: Yup.string().required('Hatchery wajib diisi!'),
|
||||
phone: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
|
||||
.min(10, 'Nomor telepon minimal 10 digit!')
|
||||
.max(12, 'Nomor telepon maksimal 12 digit!')
|
||||
.required('Nomor telepon wajib diisi!'),
|
||||
email: Yup.string()
|
||||
.email('Format email tidak valid!')
|
||||
.required('Email wajib diisi!'),
|
||||
address: Yup.string().required('Alamat wajib diisi!'),
|
||||
npwp: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor NPWP hanya boleh berisi angka!')
|
||||
.required('Nomor NPWP wajib diisi!'),
|
||||
account_number: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!')
|
||||
.required('Nomor rekening wajib diisi!'),
|
||||
due_date: Yup.number().min(1, 'Tanggal jatuh tempo wajib diisi!').required('Tanggal jatuh tempo wajib diisi!'),
|
||||
pic: Yup.string().required('PIC wajib diisi!'),
|
||||
type: Yup.object({
|
||||
value: Yup.string().required(),
|
||||
label: Yup.string().required(),
|
||||
}).required('Tipe wajib diisi!'),
|
||||
category: Yup.object({
|
||||
value: Yup.string().required(),
|
||||
label: Yup.string().required(),
|
||||
}).required('Tipe wajib diisi!'),
|
||||
hatchery: Yup.string().required('Hatchery wajib diisi!'),
|
||||
phone: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
|
||||
.min(10, 'Nomor telepon minimal 10 digit!')
|
||||
.max(12, 'Nomor telepon maksimal 12 digit!')
|
||||
.required('Nomor telepon wajib diisi!'),
|
||||
email: Yup.string()
|
||||
.email('Format email tidak valid!')
|
||||
.required('Email wajib diisi!'),
|
||||
address: Yup.string().required('Alamat wajib diisi!'),
|
||||
npwp: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor NPWP hanya boleh berisi angka!')
|
||||
.required('Nomor NPWP wajib diisi!'),
|
||||
account_number: Yup.string()
|
||||
.matches(/^[0-9]+$/, 'Nomor rekening hanya boleh berisi angka!')
|
||||
.required('Nomor rekening wajib diisi!'),
|
||||
due_date: Yup.number()
|
||||
.min(1, 'Tanggal jatuh tempo wajib diisi!')
|
||||
.required('Tanggal jatuh tempo wajib diisi!'),
|
||||
});
|
||||
|
||||
export const UpdateSupplierFormSchema = SupplierFormSchema;
|
||||
|
||||
export type SupplierFormValues = Yup.InferType<typeof SupplierFormSchema>;
|
||||
export type SupplierFormValues = Yup.InferType<typeof SupplierFormSchema>;
|
||||
|
||||
@@ -41,7 +41,9 @@ const SupplierForm = ({
|
||||
// Setup State
|
||||
const [supplierFormErrorMessage, setSupplierFormErrorMessage] = useState('');
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<OptionType[]>([]);
|
||||
const [hatcheryOptionsValues, setHatcheryOptionValues] = useState<
|
||||
OptionType[]
|
||||
>([]);
|
||||
|
||||
// -- Options data mapping
|
||||
const typeOptions = TYPE_OPTIONS;
|
||||
@@ -167,7 +169,7 @@ const SupplierForm = ({
|
||||
// Initialize Formik
|
||||
useEffect(() => {
|
||||
formikSetValues(formikInitialValues);
|
||||
if(formType != 'add'){
|
||||
if (formType != 'add') {
|
||||
const hatcheryArrays = formikInitialValues.hatchery.split(',');
|
||||
const hatcheryCreatedOptions = hatcheryArrays.map((item) => ({
|
||||
value: item,
|
||||
@@ -177,11 +179,13 @@ const SupplierForm = ({
|
||||
}
|
||||
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
|
||||
useEffect(() => {
|
||||
const commaSeparatedValues = hatcheryOptionsValues.map((item) => item.value).join(',');
|
||||
const commaSeparatedValues = hatcheryOptionsValues
|
||||
.map((item) => item.value)
|
||||
.join(',');
|
||||
formikSetValues({
|
||||
...formik.values,
|
||||
hatchery: commaSeparatedValues,
|
||||
})
|
||||
});
|
||||
}, [hatcheryOptionsValues, formikSetValues]);
|
||||
|
||||
// Option Handler
|
||||
@@ -305,7 +309,9 @@ const SupplierForm = ({
|
||||
console.log(val); // pastikan val = array of { value, label }
|
||||
setHatcheryOptionValues(val as OptionType[]);
|
||||
}}
|
||||
isError={formik.touched.hatchery && Boolean(formik.errors.hatchery)}
|
||||
isError={
|
||||
formik.touched.hatchery && Boolean(formik.errors.hatchery)
|
||||
}
|
||||
errorMessage={formik.errors.hatchery as string}
|
||||
isDisabled={formType === 'detail'}
|
||||
isClearable
|
||||
|
||||
@@ -14,6 +14,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Uom } from '@/types/api/master-data/uom';
|
||||
import { UomApi } from '@/services/api/master-data';
|
||||
@@ -32,16 +33,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/uom/detail/?uomId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -66,7 +58,7 @@ const RowOptionsMenu = ({
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
@@ -76,7 +68,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,7 +142,7 @@ const UomsTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -199,10 +191,15 @@ const UomsTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/uom/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/uom/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah UOM
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
import { WarehouseApi } from '@/services/api/master-data';
|
||||
@@ -37,16 +38,7 @@ const RowOptionsMenu = ({
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
tabIndex={type === 'dropdown' ? 0 : undefined}
|
||||
className={cn(
|
||||
{
|
||||
'dropdown-content': type === 'dropdown',
|
||||
'mt-2': type === 'collapse',
|
||||
},
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/warehouse/detail/?warehouseId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
@@ -81,7 +73,7 @@ const RowOptionsMenu = ({
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -206,7 +198,7 @@ const WarehousesTable = () => {
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
@@ -277,10 +269,15 @@ const WarehousesTable = () => {
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
||||
<div className='flex flex-row'>
|
||||
<Button href='/master-data/warehouse/add' color='primary'>
|
||||
<div className='w-full flex flex-row'>
|
||||
<Button
|
||||
href='/master-data/warehouse/add'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||
Tambah Warehouse
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user