refactor(FE-238-106): change dateinput and create chickin page and pull development

This commit is contained in:
randy-ar
2025-11-03 10:12:15 +07:00
90 changed files with 1603 additions and 1484 deletions
+13 -15
View File
@@ -1,13 +1,11 @@
'use client';
import {
HTMLAttributes,
ReactNode,
} from 'react';
import { HTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/helper';
export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
export interface CardProps
extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {
title?: string;
subtitle?: string;
image?: string;
@@ -44,17 +42,17 @@ const Card = ({
const baseClasses = 'card bg-base-100';
const variantClasses = {
'default': '',
'compact': 'card-compact',
'bordered': 'border border-base-300',
'shadow': 'shadow-xl',
default: '',
compact: 'card-compact',
bordered: 'border border-base-300',
shadow: 'shadow-xl',
'image-full': 'card-side card-compact shadow-xl',
};
const sizeClasses = {
'sm': 'w-64',
'md': 'w-96',
'lg': 'w-[28rem]',
sm: 'w-64',
md: 'w-96',
lg: 'w-[28rem]',
};
return cn(
@@ -84,9 +82,9 @@ const Card = ({
const getTitleClasses = () => {
const sizeClasses = {
'sm': 'text-lg',
'md': 'text-xl',
'lg': 'text-2xl',
sm: 'text-lg',
md: 'text-xl',
lg: 'text-2xl',
};
return cn('card-title font-bold', sizeClasses[size], className?.title);
+10 -10
View File
@@ -185,17 +185,17 @@ const Pagination = ({
currentPage <= 2
? currentPage + 2
: currentPage === totalPages - 2
? 3
: currentPage >= totalPages - 1
? 4
: 1
? 3
: currentPage >= totalPages - 1
? 4
: 1
}
endPage={
currentPage <= 2 || currentPage >= totalPages - 1
? totalPages - 3
: currentPage === totalPages - 2
? totalPages - 4
: 2
? totalPages - 4
: 2
}
onPageItemClick={pageChangeHandler}
/>
@@ -242,15 +242,15 @@ const Pagination = ({
currentPage <= 3
? currentPage + 2
: currentPage >= 4
? currentPage + 2
: 1
? currentPage + 2
: 1
}
endPage={
currentPage <= 3
? totalPages - 2
: currentPage >= 4
? totalPages - 1
: 0
? totalPages - 1
: 0
}
onPageItemClick={pageChangeHandler}
/>
-1
View File
@@ -3,7 +3,6 @@
import {
ChangeEventHandler,
FocusEventHandler,
ReactNode,
useEffect,
useState,
} from 'react';
+1 -4
View File
@@ -69,10 +69,7 @@ const FileInput = ({
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
className={cn(
'grow file-input w-full h-12 rounded',
className?.input
)}
className={cn('grow file-input w-full h-12 rounded', className?.input)}
readOnly={readOnly}
/>
+8 -8
View File
@@ -1,10 +1,10 @@
"use client";
'use client';
import { ChangeEvent, ReactNode } from "react";
import { NumericFormat, OnValueChange } from "react-number-format";
import TextInput, { TextInputProps } from "@/components/input/TextInput";
import { ChangeEvent, ReactNode } from 'react';
import { NumericFormat, OnValueChange } from 'react-number-format';
import TextInput, { TextInputProps } from '@/components/input/TextInput';
interface NumberInputProps extends Omit<TextInputProps, "type"> {
interface NumberInputProps extends Omit<TextInputProps, 'type'> {
thousandSeparator?: string;
decimalSeparator?: string;
decimalScale?: number;
@@ -17,8 +17,8 @@ interface NumberInputProps extends Omit<TextInputProps, "type"> {
}
const NumberInput = ({
thousandSeparator = ",",
decimalSeparator = ".",
thousandSeparator = ',',
decimalSeparator = '.',
decimalScale = 5,
allowNegative = true,
onChange,
@@ -28,7 +28,7 @@ const NumberInput = ({
}: NumberInputProps) => {
const valueChangeHandler: OnValueChange = (
numberFormatValues,
sourceInfo,
sourceInfo
) => {
const newChangeEvent = sourceInfo.event as
| ChangeEvent<HTMLInputElement>
+6 -2
View File
@@ -49,14 +49,18 @@ const MenuItem = ({
);
return (
<li onClick={onClick}>
<li>
{href && (
<Link href={href} className={menuItemBaseClassName}>
{menuItemContent}
</Link>
)}
{!href && <a className={menuItemBaseClassName}>{menuItemContent}</a>}
{!href && (
<button className={menuItemBaseClassName} onClick={onClick}>
{menuItemContent}
</button>
)}
</li>
);
};
+10 -10
View File
@@ -31,21 +31,21 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
approval.status === 'APPROVED'
? 'success'
: approval.status === 'REJECTED'
? 'error'
: approval.status === 'WAITING'
? 'warning'
: undefined;
? 'error'
: approval.status === 'WAITING'
? 'warning'
: undefined;
const stepItemIcon =
approval.status === 'APPROVED'
? 'material-symbols:check-rounded'
: approval.status === 'REJECTED'
? 'material-symbols:close-rounded'
: approval.status === 'WAITING'
? 'pajamas:dash-circle'
: approval.logs && approval.logs.length > 0
? 'material-symbols:info-outline-rounded'
: 'bxs:hourglass';
? 'material-symbols:close-rounded'
: approval.status === 'WAITING'
? 'pajamas:dash-circle'
: approval.logs && approval.logs.length > 0
? 'material-symbols:info-outline-rounded'
: 'bxs:hourglass';
return (
<StepItem
@@ -10,11 +10,7 @@ import { inventoryAdjustmentApi } from '@/services/api/inventory';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { InventoryAdjustment } from '@/types/api/inventory/adjustment';
import { Icon } from '@iconify/react';
import {
ColumnDef,
ColumnSort,
SortingState,
} from '@tanstack/react-table';
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
import { useCallback, useEffect, useState } from 'react';
import useSWR from 'swr';
@@ -44,10 +40,7 @@ const InventoryAdjustmentTable = () => {
});
// Fetch Data
const {
data: inventoryAdjustments,
isLoading,
} = useSWR(
const { data: inventoryAdjustments, isLoading } = useSWR(
`${inventoryAdjustmentApi.basePath}${getTableFilterQueryString()}`,
inventoryAdjustmentApi.getAllFetcher
);
@@ -113,8 +106,8 @@ const InventoryAdjustmentTable = () => {
type === 'INCREASE'
? 'Peningkatan'
: type === 'DECREASE'
? 'Penurunan'
: '-';
? 'Penurunan'
: '-';
return (
<div
@@ -187,8 +180,13 @@ const InventoryAdjustmentTable = () => {
<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='/inventory/adjustment/add' color='primary'>
<div className='w-full flex flex-row'>
<Button
href='/inventory/adjustment/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah
</Button>
@@ -211,7 +209,7 @@ const InventoryAdjustmentTable = () => {
value: tableFilterState.pageSize,
}}
onChange={pageSizeChangeHandler}
className={{ wrapper: 'max-w-28' }}
className={{ wrapper: 'min-w-28' }}
/>
</div>
</div>
@@ -4,24 +4,21 @@ export const InventoryAdjustmentFormSchema = Yup.object({
product_category: Yup.object({
value: Yup.number().required('ID Kategori Produk wajib diisi!'),
label: Yup.string().required('Nama Kategori Produk wajib diisi!'),
})
.nullable(),
}).nullable(),
product_category_id: Yup.number().nullable(),
product: Yup.object({
value: Yup.number().required('ID Produk wajib diisi!'),
label: Yup.string().required('Nama Produk wajib diisi!'),
})
.nullable(),
}).nullable(),
product_id: Yup.number().nullable(),
warehouse: Yup.object({
value: Yup.number().required('ID Gudang wajib diisi!'),
label: Yup.string().required('Nama Gudang wajib diisi!'),
})
.nullable(),
}).nullable(),
warehouse_id: Yup.number().nullable(),
@@ -51,9 +51,8 @@ const InventoryAdjustmentForm = ({
// Submit Handler
const createInventoryAdjustmentHandler = useCallback(
async (payload: CreateInventoryAdjustmentPayload) => {
const createInventoryAdjustmentRes = await inventoryAdjustmentApi.create(
payload
);
const createInventoryAdjustmentRes =
await inventoryAdjustmentApi.create(payload);
if (isResponseError(createInventoryAdjustmentRes)) {
setInventoryAdjustmentFormErrorMessage(
@@ -68,7 +67,9 @@ const InventoryAdjustmentForm = ({
[router]
);
const formikInitialValues = useMemo<Partial<InventoryAdjustmentFormValues>>(() => {
const formikInitialValues = useMemo<
Partial<InventoryAdjustmentFormValues>
>(() => {
return {
product_category_id: initialValues?.product_category?.id ?? 0,
product_id: initialValues?.product?.id ?? 0,
@@ -185,7 +186,6 @@ const InventoryAdjustmentForm = ({
warehouseChangeHandler(null);
};
const { setValues: formikSetValues } = formik;
// Effect
@@ -225,7 +225,13 @@ const InventoryAdjustmentForm = ({
const type = initialValues.transaction_type.toLowerCase();
setQuantityLabel(type === 'increase' ? 'Tambah Stok' : 'Kurangi Stok');
}
}, [formik, initialValues, setQuantityLabel, setDisabledProduct, setSelectedProductCategories]);
}, [
formik,
initialValues,
setQuantityLabel,
setDisabledProduct,
setSelectedProductCategories,
]);
useEffect(() => {
formikSetValues(formikInitialValues as InventoryAdjustmentFormValues);
}, [formikSetValues, formikInitialValues]);
@@ -364,15 +370,19 @@ const InventoryAdjustmentForm = ({
errorMessage={formik.errors.transaction_type as string}
variant='radio-primary'
required
bottomLabel={formik.values.transaction_type == undefined ? 'Pilih salah satu tipe transaksi' : undefined}
bottomLabel={
formik.values.transaction_type == undefined
? 'Pilih salah satu tipe transaksi'
: undefined
}
disabled={type === 'detail'}
/>
{/* Number Input Stock */}
<TextInput
className={{
wrapper: `${formik.values.transaction_type != undefined ? '' : 'hidden'}`,
}}
className={{
wrapper: `${formik.values.transaction_type != undefined ? '' : 'hidden'}`,
}}
required
label={quantityLabel}
name='quantity'
@@ -395,8 +405,6 @@ const InventoryAdjustmentForm = ({
readOnly={type === 'detail'}
/>
{/* Text Area Input Reason */}
<TextArea
required
@@ -413,14 +421,23 @@ const InventoryAdjustmentForm = ({
<div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'detail' && (
<div className='flex flex-row justify-end gap-2'>
<Button type='button' color='warning' className='px-4' onClick={resetHandler}>
<Button
type='button'
color='warning'
className='px-4'
onClick={resetHandler}
>
Reset
</Button>
<Button
type='submit'
color='primary'
isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting || formik.values.product == undefined}
disabled={
!formik.isValid ||
formik.isSubmitting ||
formik.values.product == undefined
}
className='px-4'
>
Submit
@@ -77,7 +77,7 @@ const MovementTable = () => {
<TableToolbar
addButton={{
href: '/inventory/movement/add',
label: 'Tambah Movement',
label: 'Tambah',
}}
search={{
value: tableFilterState.search,
@@ -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
@@ -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>
@@ -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 { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper';
@@ -87,7 +88,9 @@ const ChickinTable = () => {
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
<Button
href='/production/chickin/add?projectFlockId=1'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='uil:plus' width={24} height={24} />
Tambah
@@ -130,20 +133,20 @@ const ChickinTable = () => {
} else {
return '-';
}
}
},
},
{
accessorFn: (row) => row.chick_in_date,
header: 'Tanggal Chickin',
cell: (props) => {
if (props.row.original.chick_in_date) {
return new Date(props.row.original.chick_in_date).toLocaleDateString(
'id-ID'
);
return new Date(
props.row.original.chick_in_date
).toLocaleDateString('id-ID');
} else {
return '-';
}
}
},
},
{
accessorFn: (row) => row.note,
@@ -166,7 +169,7 @@ const ChickinTable = () => {
deleteModal.openModal();
};
const editClickHandler = () => {
const editClickHandler = () => {
setSelectedChickin(props.row.original);
chickinModal.openModal();
};
@@ -240,7 +243,9 @@ const ChickinTable = () => {
<Modal ref={chickinModal.ref}>
<div className='flex flex-row justify-between items-center'>
<h1 className='text-xl font-semibold text-center mb-6'>
Chickin Kandang - { selectedChickin?.project_flock_kandang && selectedChickin?.project_flock_kandang.kandang?.name}
Chickin Kandang -{' '}
{selectedChickin?.project_flock_kandang &&
selectedChickin?.project_flock_kandang.kandang?.name}
</h1>
<Button
color='error'
@@ -255,10 +260,14 @@ const ChickinTable = () => {
/>
</Button>
</div>
<ChickinForm initialValues={selectedChickin} formType='edit' afterSubmit={() => {
refreshChickins()
chickinModal.closeModal()
}}/>
<ChickinForm
initialValues={selectedChickin}
formType='edit'
afterSubmit={() => {
refreshChickins();
chickinModal.closeModal();
}}
/>
</Modal>
</>
);
@@ -276,16 +285,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={`/production/chickin/detail?chickinId=${props.row.original.id}`}
variant='ghost'
@@ -308,7 +308,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'
@@ -318,7 +318,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -3,9 +3,11 @@ import * as Yup from 'yup';
export const ChickinFormSchema = Yup.object({
chick_in_date: Yup.string().required('Tanggal masuk wajib diisi!'),
note: Yup.string().required('Catatan wajib diisi!'),
quantity: Yup.number().min(1, 'Jumlah wajib diisi!').required('Jumlah wajib diisi!'),
})
quantity: Yup.number()
.min(1, 'Jumlah wajib diisi!')
.required('Jumlah wajib diisi!'),
});
export type ChickinFormValues = Yup.InferType<typeof ChickinFormSchema>;
export const UpdateChickinFormSchema = ChickinFormSchema;
export const UpdateChickinFormSchema = ChickinFormSchema;
@@ -9,6 +9,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 { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn } from '@/lib/helper';
@@ -261,6 +262,7 @@ const ProjectFlockTable = () => {
<div className='flex flex-col sm:flex-row gap-3 w-full'>
<Button
href='/production/project-flock/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
@@ -24,7 +24,8 @@ export const ProjectFlockFormSchema = Yup.object({
value: Yup.string().required('Nilai Kategori wajib diisi!'),
label: Yup.string().required('Label Kategori wajib diisi!'),
}).nullable(),
category: Yup.string().oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
category: Yup.string()
.oneOf(['GROWING', 'LAYING'], 'Kategori wajib diisi!')
.required('Kategori wajib diisi!'),
// FCR
@@ -79,9 +79,8 @@ const ProjectFlockForm = ({
const [isApprovedDisabled, setIsApprovedDisabled] = useState(
initialValues?.approval.step_name == 'Pengajuan' ? false : true
);
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
!isApprovedDisabled
);
const [isRejectedDisabled, setIsRejectedDisabled] =
useState(!isApprovedDisabled);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
@@ -143,10 +142,11 @@ const ProjectFlockForm = ({
search: '',
location_id: selectedLocation == '' ? '0' : selectedLocation,
}).toString()}`;
const { data: kandang, isLoading: isLoadingKandang, mutate: refreshKandang} = useSWR(
kandangUrl,
KandangApi.getAllFetcher
);
const {
data: kandang,
isLoading: isLoadingKandang,
mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`;
@@ -207,10 +207,7 @@ const ProjectFlockForm = ({
setOpenSelectKandangs(true);
const newRowSelection = Object.fromEntries(
initialValues.kandangs.map((k: Kandang) => [
k.id.toString(),
true,
])
initialValues.kandangs.map((k: Kandang) => [k.id.toString(), true])
);
setRowSelection(newRowSelection);
}
@@ -38,10 +38,13 @@ const ProjectFlockKandangTable = ({
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0 && formType != 'detail';
selectableRows.length != 0 &&
formType != 'detail';
const someSelected =
selectableRows.some((row) => row.getIsSelected()) && !allSelected && formType != 'detail';
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected &&
formType != 'detail';
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
@@ -14,6 +14,7 @@ import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import Table from '@/components/Table';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { type CellContext } from '@tanstack/react-table';
import { type Recording } from '@/types/api/production/recording';
@@ -127,16 +128,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={`recording/detail/?recordingId=${props.row.original.id}`}
variant='ghost'
@@ -159,7 +151,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'
@@ -169,7 +161,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -256,7 +248,7 @@ const RecordingTable = () => {
<TableToolbar
addButton={{
href: 'recording/add',
label: 'Tambah Recording',
label: 'Tambah',
}}
search={{
value: search,
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import TextInput from '@/components/input/TextInput';
import CheckboxInput from '@/components/input/CheckboxInput';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
@@ -43,16 +44,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={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
variant='ghost'
@@ -97,7 +89,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'
@@ -107,7 +99,7 @@ const RowOptionsMenu = ({
/>
Delete
</Button>
</div>
</RowOptionsMenuWrapper>
);
};
@@ -291,7 +283,7 @@ const TransferToLayingsTable = () => {
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
type='collapse'
props={props}
approveClickHandler={approveClickHandler}
rejectClickHandler={rejectClickHandler}
@@ -328,9 +320,8 @@ const TransferToLayingsTable = () => {
const confirmationModalApproveClickHandler = async () => {
setIsApproveLoading(true);
const bulkApproveResponse = await TransferToLayingApi.bulkApprove(
selectedRowIds
);
const bulkApproveResponse =
await TransferToLayingApi.bulkApprove(selectedRowIds);
if (isResponseSuccess(bulkApproveResponse)) {
refreshTransferToLayings();
@@ -358,9 +349,8 @@ const TransferToLayingsTable = () => {
const confirmationModalRejectClickHandler = async () => {
setIsRejectLoading(true);
const bulkRejectResponse = await TransferToLayingApi.bulkReject(
selectedRowIds
);
const bulkRejectResponse =
await TransferToLayingApi.bulkReject(selectedRowIds);
if (isResponseSuccess(bulkRejectResponse)) {
refreshTransferToLayings();
@@ -437,11 +427,12 @@ const TransferToLayingsTable = () => {
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
<Button
href='/production/transfer-to-laying/add'
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Transfer ke Laying
Tambah
</Button>
{selectedRowIds.length > 0 && (
@@ -484,7 +475,9 @@ const TransferToLayingsTable = () => {
placeholder='Cari TransferToLaying'
value={tableFilterState.search}
onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }}
className={{
wrapper: 'sm:max-w-3xs',
}}
/>
</div>
@@ -497,7 +490,9 @@ const TransferToLayingsTable = () => {
placeholder='Masukkan tanggal transfer'
value={tableFilterState.transferDate}
onChange={transferDateChangeHandler}
className={{ wrapper: 'col-span-12 sm:col-span-3' }}
className={{
wrapper: 'col-span-12 sm:col-span-3',
}}
/>
<SelectInput
+1 -1
View File
@@ -16,7 +16,7 @@ const RowCollapseOptions = ({ children }: RowCollapseOptionsProps) => {
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</Button>
}
className='w-fit'
className='w-fit min-w-36'
titleClassName='p-0! justify-self-end'
>
{children}
@@ -0,0 +1,29 @@
import { ReactNode } from 'react';
import { cn } from '@/lib/helper';
interface RowOptionsMenuWrapperProps {
children?: ReactNode;
type: 'dropdown' | 'collapse';
}
const RowOptionsMenuWrapper = ({
children,
type,
}: RowOptionsMenuWrapperProps) => {
return (
<div
tabIndex={type === 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content mr-2': type === 'dropdown',
'w-fit ml-auto mt-2': type === 'collapse',
},
'p-2.5 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<div className='flex flex-col gap-1'>{children}</div>
</div>
);
};
export default RowOptionsMenuWrapper;
+4 -13
View File
@@ -1,6 +1,6 @@
import { Icon } from '@iconify/react';
import Button from '../Button';
import { cn } from '@/lib/helper';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
interface TableRowOptionsProps {
type?: 'dropdown' | 'collapse';
@@ -21,16 +21,7 @@ export const TableRowOptions = ({
showEdit = true,
showDelete = true,
}: TableRowOptionsProps) => (
<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={`${basePath}/detail/?${queryParam}=${recordId}`}
variant='ghost'
@@ -56,7 +47,7 @@ export const TableRowOptions = ({
onClick={onDelete}
variant='ghost'
color='error'
className='text-error hover:text-inherit justify-start text-sm'
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
>
<Icon
icon='mdi:delete-outline'
@@ -67,5 +58,5 @@ export const TableRowOptions = ({
Delete
</Button>
)}
</div>
</RowOptionsMenuWrapper>
);
+7 -2
View File
@@ -18,8 +18,13 @@ export const TableToolbar = ({ addButton, search }: TableToolbarProps) => {
return (
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
{addButton && (
<div className='flex flex-row'>
<Button href={addButton.href} color='primary'>
<div className='w-full flex flex-row'>
<Button
href={addButton.href}
variant='outline'
color='primary'
className='w-full sm:w-fit'
>
<Icon icon='ic:round-plus' width={24} height={24} />
{addButton.label}
</Button>