mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu
This commit is contained in:
+5
-1
@@ -25,5 +25,9 @@ export default function Home() {
|
||||
);
|
||||
}
|
||||
|
||||
return <>Loading...</>;
|
||||
return (
|
||||
<main className='w-full h-full min-h-screen flex flex-row justify-center items-center'>
|
||||
<span className='loading loading-spinner loading-lg'></span>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
+116
-55
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import { Fragment, ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getExpandedRowModel,
|
||||
getSortedRowModel,
|
||||
TableOptions,
|
||||
useReactTable,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
OnChangeFn,
|
||||
Row,
|
||||
HeaderContext,
|
||||
ExpandedState,
|
||||
} from '@tanstack/react-table';
|
||||
import { rankItem } from '@tanstack/match-sorter-utils';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -33,6 +35,9 @@ interface TableClassNames {
|
||||
bodyRowClassName?: string;
|
||||
selectedBodyRowClassName?: string;
|
||||
bodyColumnClassName?: string;
|
||||
bodySubRowClassName?: (depth: number) => string;
|
||||
selectedBodySubRowClassName?: (depth: number) => string;
|
||||
bodySubRowColumnClassName?: (depth: number) => string;
|
||||
tableFooterClassName?: string;
|
||||
footerRowClassName?: string;
|
||||
footerColumnClassName?: string;
|
||||
@@ -60,6 +65,7 @@ export interface TableProps<TData extends object> {
|
||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||
renderFooter?: boolean;
|
||||
withCheckbox?: boolean;
|
||||
withPagination?: boolean;
|
||||
rowOptions?: number[];
|
||||
/**
|
||||
* Custom row renderer. Should return a complete <tr> element or null.
|
||||
@@ -67,6 +73,10 @@ export interface TableProps<TData extends object> {
|
||||
* Return null to render the default row.
|
||||
*/
|
||||
renderCustomRow?: (row: Row<TData>) => ReactNode | null;
|
||||
getRowCanExpand?: (row: Row<TData>) => boolean;
|
||||
renderSubComponent?: (props: { row: Row<TData> }) => React.ReactElement;
|
||||
expanded?: ExpandedState;
|
||||
getSubRows?: (originalRow: TData, index: number) => TData[] | undefined;
|
||||
}
|
||||
|
||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||
@@ -92,7 +102,12 @@ export const TABLE_DEFAULT_STYLING = {
|
||||
bodyRowClassName:
|
||||
'transition-all duration-200 border-t border-base-content/10 bg-transparent',
|
||||
selectedBodyRowClassName: 'bg-primary/5',
|
||||
bodyColumnClassName: 'px-4 py-3 text-base-content',
|
||||
bodyColumnClassName: 'px-4 py-3 text-base-content font-medium',
|
||||
bodySubRowClassName: (depth: number) =>
|
||||
'transition-all duration-200 border-t border-base-content/10 bg-transparent',
|
||||
selectedBodySubRowClassName: (depth: number) => 'bg-primary/5',
|
||||
bodySubRowColumnClassName: (depth: number) =>
|
||||
'px-4 py-3 text-base-content font-medium',
|
||||
paginationClassName: 'px-3',
|
||||
tableFooterClassName: 'font-semibold border-base-content/10',
|
||||
footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10',
|
||||
@@ -120,8 +135,13 @@ const Table = <TData extends object>({
|
||||
enableRowSelection,
|
||||
renderFooter = false,
|
||||
withCheckbox = false,
|
||||
withPagination = true,
|
||||
rowOptions = [10, 20, 50, 100],
|
||||
renderCustomRow,
|
||||
getRowCanExpand,
|
||||
renderSubComponent,
|
||||
expanded = {},
|
||||
getSubRows,
|
||||
}: TableProps<TData>) => {
|
||||
const isServerSideTable =
|
||||
totalItems !== undefined &&
|
||||
@@ -154,10 +174,14 @@ const Table = <TData extends object>({
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
getExpandedRowModel: getExpandedRowModel(),
|
||||
getRowCanExpand: getRowCanExpand ?? (getSubRows ? undefined : () => false),
|
||||
getSubRows,
|
||||
manualSorting,
|
||||
state: {
|
||||
pagination,
|
||||
globalFilter: fuzzySearchValue,
|
||||
expanded,
|
||||
},
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter,
|
||||
@@ -228,7 +252,10 @@ const Table = <TData extends object>({
|
||||
<div
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.containerClassName,
|
||||
tableClassNames.containerClassName
|
||||
tableClassNames.containerClassName,
|
||||
{
|
||||
'mb-0': !withPagination,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@@ -352,36 +379,67 @@ const Table = <TData extends object>({
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.bodyRowClassName,
|
||||
tableClassNames.bodyRowClassName,
|
||||
{
|
||||
[tableClassNames.selectedBodyRowClassName]:
|
||||
row.getIsSelected(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={cn(
|
||||
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||
TABLE_DEFAULT_STYLING.bodyColumnClassName,
|
||||
tableClassNames.bodyColumnClassName
|
||||
)}
|
||||
>
|
||||
{!isLoading &&
|
||||
flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
<Fragment key={row.id}>
|
||||
<tr
|
||||
data-depth={row.depth}
|
||||
className={cn(
|
||||
row.depth > 0
|
||||
? tableClassNames.bodySubRowClassName(row.depth)
|
||||
: tableClassNames.bodyRowClassName,
|
||||
{
|
||||
[tableClassNames.selectedBodyRowClassName!]:
|
||||
row.getIsSelected() && row.depth === 0,
|
||||
[tableClassNames.selectedBodySubRowClassName(
|
||||
row.depth
|
||||
)!]: row.getIsSelected() && row.depth > 0,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={cn(
|
||||
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||
TABLE_DEFAULT_STYLING.bodyColumnClassName,
|
||||
row.depth > 0
|
||||
? tableClassNames.bodySubRowColumnClassName(
|
||||
row.depth
|
||||
)
|
||||
: tableClassNames.bodyColumnClassName
|
||||
)}
|
||||
>
|
||||
{!isLoading &&
|
||||
flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
|
||||
{isLoading && <div className='skeleton w-full h-4' />}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
{isLoading && <div className='skeleton w-full h-4' />}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
{row.getIsExpanded() && (
|
||||
<>
|
||||
{renderSubComponent && (
|
||||
<tr
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.bodySubRowClassName(1),
|
||||
tableClassNames.bodySubRowClassName(1),
|
||||
{
|
||||
[tableClassNames.selectedBodySubRowClassName(1)]:
|
||||
row.getIsSelected(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<td colSpan={row.getVisibleCells().length}>
|
||||
{renderSubComponent({ row })}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
@@ -425,30 +483,33 @@ const Table = <TData extends object>({
|
||||
!isLoading &&
|
||||
emptyContent}
|
||||
|
||||
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
|
||||
<div
|
||||
className={cn(
|
||||
'mt-5',
|
||||
TABLE_DEFAULT_STYLING.paginationClassName,
|
||||
tableClassNames.paginationClassName
|
||||
)}
|
||||
>
|
||||
<Pagination
|
||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||
itemsPerPage={table.getState().pagination.pageSize}
|
||||
currentPage={
|
||||
isServerSideTable
|
||||
? page
|
||||
: table.getState().pagination.pageIndex + 1
|
||||
}
|
||||
onPrevPage={prevPageClickHandler}
|
||||
onNextPage={nextPageClickHandler}
|
||||
onPageChange={pageChangeHandler}
|
||||
rowOptions={rowOptions}
|
||||
onRowChange={onPageSizeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{data.length > 0 &&
|
||||
table.getRowModel().rows.length > 0 &&
|
||||
!isLoading &&
|
||||
withPagination && (
|
||||
<div
|
||||
className={cn(
|
||||
'mt-5',
|
||||
TABLE_DEFAULT_STYLING.paginationClassName,
|
||||
tableClassNames.paginationClassName
|
||||
)}
|
||||
>
|
||||
<Pagination
|
||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||
itemsPerPage={table.getState().pagination.pageSize}
|
||||
currentPage={
|
||||
isServerSideTable
|
||||
? page
|
||||
: table.getState().pagination.pageIndex + 1
|
||||
}
|
||||
onPrevPage={prevPageClickHandler}
|
||||
onNextPage={nextPageClickHandler}
|
||||
onPageChange={pageChangeHandler}
|
||||
rowOptions={rowOptions}
|
||||
onRowChange={onPageSizeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ const StatusBadge = ({
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className={cn({
|
||||
'text-base-content/10': color === 'neutral',
|
||||
'text-success': color === 'success',
|
||||
'text-[#008000]': color === 'success',
|
||||
'text-error': color === 'error',
|
||||
'text-primary': color === 'info',
|
||||
})}
|
||||
|
||||
@@ -167,49 +167,67 @@ const ConfirmationModal = ({
|
||||
|
||||
{children && <div className='w-full'>{children}</div>}
|
||||
|
||||
<div className='w-full grid grid-cols-2 gap-3'>
|
||||
{secondaryButton && secondaryButton.text && (
|
||||
<Button
|
||||
{...secondaryButton}
|
||||
variant='outline'
|
||||
color={secondaryButton?.color}
|
||||
isLoading={secondaryButton?.isLoading}
|
||||
disabled={
|
||||
secondaryButton?.isLoading !== undefined
|
||||
? secondaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
onClick={closeModalHandler}
|
||||
className={cn(
|
||||
'p-2 rounded-xl text-sm',
|
||||
secondaryButton?.className
|
||||
)}
|
||||
>
|
||||
{secondaryButton?.text ?? 'Tidak'}
|
||||
</Button>
|
||||
)}
|
||||
{(secondaryButton || primaryButton) && (
|
||||
<div
|
||||
className={cn('w-full grid gap-3', {
|
||||
'grid-cols-2': secondaryButton && primaryButton,
|
||||
'grid-cols-1':
|
||||
(secondaryButton && !primaryButton) ||
|
||||
(!secondaryButton && primaryButton),
|
||||
})}
|
||||
>
|
||||
{secondaryButton && secondaryButton.text && (
|
||||
<Button
|
||||
{...secondaryButton}
|
||||
variant='outline'
|
||||
color={secondaryButton?.color}
|
||||
isLoading={secondaryButton?.isLoading}
|
||||
disabled={
|
||||
secondaryButton?.isLoading !== undefined
|
||||
? secondaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
onClick={(e) => {
|
||||
if (secondaryButton?.onClick) {
|
||||
secondaryButton.onClick(e);
|
||||
} else {
|
||||
closeModalHandler();
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'p-2 rounded-xl text-sm',
|
||||
secondaryButton?.className
|
||||
)}
|
||||
>
|
||||
{secondaryButton?.text ?? 'Tidak'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{primaryButton && primaryButton.text && (
|
||||
<Button
|
||||
{...primaryButton}
|
||||
color={primaryButton?.color ?? 'info'}
|
||||
onClick={primaryButtonClickHandler}
|
||||
isLoading={
|
||||
primaryButton?.isLoading !== undefined
|
||||
? primaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
disabled={
|
||||
primaryButton?.isLoading !== undefined
|
||||
? primaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
className={cn('p-2 rounded-xl text-sm', primaryButton?.className)}
|
||||
>
|
||||
{primaryButton?.text ?? 'Ya'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{primaryButton && primaryButton.text && (
|
||||
<Button
|
||||
{...primaryButton}
|
||||
color={primaryButton?.color ?? 'info'}
|
||||
onClick={primaryButtonClickHandler}
|
||||
isLoading={
|
||||
primaryButton?.isLoading !== undefined
|
||||
? primaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
disabled={
|
||||
primaryButton?.isLoading !== undefined
|
||||
? primaryButton?.isLoading
|
||||
: isPrimaryButtonLoading
|
||||
}
|
||||
className={cn(
|
||||
'p-2 rounded-xl text-sm',
|
||||
primaryButton?.className
|
||||
)}
|
||||
>
|
||||
{primaryButton?.text ?? 'Ya'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
'use client';
|
||||
|
||||
import { ChangeEventHandler, RefObject, useId, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import ConfirmationModal, {
|
||||
ConfirmationModalProps,
|
||||
} from '@/components/modal/ConfirmationModal';
|
||||
import Table from '@/components/Table';
|
||||
import TextArea from '@/components/input/TextArea';
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { cn, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { TransferToLayingFormValues } from '@/components/pages/production/transfer-to-laying/form/TransferToLayingForm.schema';
|
||||
import { Color } from '@/types/theme';
|
||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
|
||||
interface TransferToLayingConfirmationModalProps
|
||||
extends Omit<ConfirmationModalProps, 'children' | 'primaryButton'> {
|
||||
ref: RefObject<HTMLDialogElement | null>;
|
||||
type?: 'info' | 'success' | 'error';
|
||||
transferToLayingIds?: number[];
|
||||
transferToLayingForm?: TransferToLayingFormValues;
|
||||
onClose?: () => void;
|
||||
|
||||
withNote?: boolean;
|
||||
noteLabel?: string;
|
||||
rows?: number;
|
||||
placeholder?: string;
|
||||
|
||||
primaryButton?: {
|
||||
text?: string;
|
||||
color?: Color;
|
||||
isLoading?: boolean;
|
||||
onClick?: (notes: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
interface TransferToLayingConfirmationTableDataType {
|
||||
label: string;
|
||||
value: string;
|
||||
subRows?: TransferToLayingConfirmationTableDataType[];
|
||||
}
|
||||
|
||||
const TransferToLayingConfirmationModalTable = ({
|
||||
transferToLayingForm,
|
||||
transferToLayingId,
|
||||
}: {
|
||||
transferToLayingForm?: TransferToLayingFormValues;
|
||||
transferToLayingId?: number;
|
||||
}) => {
|
||||
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
|
||||
useSWR(
|
||||
transferToLayingId
|
||||
? ['detail-transfer-to-laying', String(transferToLayingId)]
|
||||
: undefined,
|
||||
([_, id]) => TransferToLayingApi.getSingle(Number(id))
|
||||
);
|
||||
|
||||
const confirmationTableColumns: ColumnDef<TransferToLayingConfirmationTableDataType>[] =
|
||||
[
|
||||
{
|
||||
header: 'Label',
|
||||
accessorKey: 'label',
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const isSubRow = row.depth > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSubRow && row.original.label}
|
||||
|
||||
{isSubRow && (
|
||||
<div
|
||||
className={cn('w-full min-h-full flex items-stretch gap-0')}
|
||||
>
|
||||
<div className='w-px mx-4 bg-base-content/10' />
|
||||
<span className='p-3'>{row.original.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Value',
|
||||
accessorKey: 'value',
|
||||
enableSorting: false,
|
||||
},
|
||||
];
|
||||
|
||||
const confirmationTableData: TransferToLayingConfirmationTableDataType[] = [
|
||||
{
|
||||
label: 'Tanggal',
|
||||
value: formatDate(
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? transferToLaying.data.transfer_date
|
||||
: transferToLayingForm?.transfer_date,
|
||||
'DD MMMM YYYY'
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Flock Asal',
|
||||
value:
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? transferToLaying.data.from_project_flock.flock_name
|
||||
: (transferToLayingForm?.flockSource?.label ?? '-'),
|
||||
subRows:
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? (transferToLaying.data.sources?.map(
|
||||
(sourceProjectFlockKandang) => ({
|
||||
label:
|
||||
sourceProjectFlockKandang.source_project_flock_kandang.kandang
|
||||
.name,
|
||||
value: formatNumber(
|
||||
Number(sourceProjectFlockKandang.qty),
|
||||
'en-US'
|
||||
),
|
||||
})
|
||||
) ?? [])
|
||||
: (transferToLayingForm?.flockSourceKandangs?.map((kandang) => ({
|
||||
label: kandang.kandang.label,
|
||||
value: formatNumber(Number(kandang.quantity), 'en-US'),
|
||||
})) ?? []),
|
||||
},
|
||||
{
|
||||
label: 'Flock Tujuan',
|
||||
value:
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? transferToLaying.data.to_project_flock.flock_name
|
||||
: (transferToLayingForm?.flockDestination?.label ?? '-'),
|
||||
subRows:
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? (transferToLaying.data.targets?.map(
|
||||
(targetProjectFlockKandang) => ({
|
||||
label:
|
||||
targetProjectFlockKandang.target_project_flock_kandang.kandang
|
||||
.name,
|
||||
value: formatNumber(
|
||||
Number(targetProjectFlockKandang.qty),
|
||||
'en-US'
|
||||
),
|
||||
})
|
||||
) ?? [])
|
||||
: (transferToLayingForm?.flockDestinationKandangs?.map((kandang) => ({
|
||||
label: kandang.kandang.label,
|
||||
value: formatNumber(Number(kandang.quantity), 'en-US'),
|
||||
})) ?? []),
|
||||
},
|
||||
{
|
||||
label: 'Jumlah Transfer',
|
||||
value: formatNumber(
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? Number(
|
||||
transferToLaying.data.sources.reduce(
|
||||
(total, source) => total + Number(source.qty),
|
||||
0
|
||||
)
|
||||
)
|
||||
: Number(transferToLayingForm?.totalQuantity),
|
||||
'en-US'
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Notes',
|
||||
value:
|
||||
transferToLayingId && isResponseSuccess(transferToLaying)
|
||||
? (transferToLaying.data.notes ?? '-')
|
||||
: (transferToLayingForm?.reason ?? '-'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table<TransferToLayingConfirmationTableDataType>
|
||||
columns={confirmationTableColumns}
|
||||
data={confirmationTableData}
|
||||
withPagination={false}
|
||||
pageSize={10000}
|
||||
expanded={true}
|
||||
getSubRows={(row) => row.subRows}
|
||||
className={{
|
||||
headerRowClassName: 'border-b border-base-content/10',
|
||||
bodyRowClassName: 'border-none',
|
||||
bodySubRowClassName: () => 'border-none',
|
||||
bodySubRowColumnClassName: () => 'first:p-0',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TransferToLayingConfirmationModal = ({
|
||||
ref,
|
||||
type = 'success',
|
||||
transferToLayingForm,
|
||||
transferToLayingIds,
|
||||
onClose,
|
||||
withNote,
|
||||
rows = 4,
|
||||
noteLabel,
|
||||
placeholder = 'Alasan Transfer',
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
...props
|
||||
}: TransferToLayingConfirmationModalProps) => {
|
||||
const randomId = useId();
|
||||
|
||||
const [notes, setNotes] = useState('');
|
||||
|
||||
const notesChangeHandler: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||
setNotes(e.target.value);
|
||||
};
|
||||
|
||||
const closeModalHandler = () => {
|
||||
onClose?.();
|
||||
ref.current?.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
ref={ref}
|
||||
iconPosition='left'
|
||||
type={type}
|
||||
primaryButton={{
|
||||
...primaryButton,
|
||||
text: primaryButton?.text ?? 'Oke',
|
||||
color: primaryButton?.color ?? 'primary',
|
||||
className: 'rounded-lg',
|
||||
onClick: (e) => {
|
||||
if (withNote) {
|
||||
primaryButton?.onClick?.(notes);
|
||||
} else if (primaryButton && primaryButton?.onClick) {
|
||||
primaryButton?.onClick?.('');
|
||||
} else {
|
||||
closeModalHandler();
|
||||
}
|
||||
|
||||
setNotes('');
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
secondaryButton
|
||||
? {
|
||||
text: secondaryButton?.text ?? 'Cancel',
|
||||
color: secondaryButton?.color ?? 'none',
|
||||
onClick: (e) => {
|
||||
if (secondaryButton && secondaryButton?.onClick) {
|
||||
secondaryButton.onClick?.(e);
|
||||
} else {
|
||||
closeModalHandler();
|
||||
}
|
||||
|
||||
setNotes('');
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={{
|
||||
modalBox: 'max-h-full',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<div className='flex flex-col gap-4'>
|
||||
{!transferToLayingIds && transferToLayingForm && (
|
||||
<TransferToLayingConfirmationModalTable
|
||||
transferToLayingForm={transferToLayingForm}
|
||||
/>
|
||||
)}
|
||||
|
||||
{transferToLayingIds &&
|
||||
!transferToLayingForm &&
|
||||
transferToLayingIds.map((transferToLayingId, idx) => (
|
||||
<TransferToLayingConfirmationModalTable
|
||||
key={idx}
|
||||
transferToLayingId={transferToLayingId}
|
||||
/>
|
||||
))}
|
||||
|
||||
{withNote && (
|
||||
<TextArea
|
||||
name={randomId}
|
||||
label={noteLabel}
|
||||
placeholder={placeholder}
|
||||
value={notes}
|
||||
onChange={notesChangeHandler}
|
||||
rows={rows}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransferToLayingConfirmationModal;
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -19,8 +20,8 @@ import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import TextArea from '@/components/input/TextArea';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal';
|
||||
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { ProjectFlockApi } from '@/services/api/production';
|
||||
import { getIn, useFormik } from 'formik';
|
||||
import {
|
||||
@@ -58,8 +59,11 @@ const TransferToLayingFormModal = () => {
|
||||
};
|
||||
|
||||
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
|
||||
useSWR(transferToLayingId ? transferToLayingId : undefined, (id: number) =>
|
||||
TransferToLayingApi.getSingle(id)
|
||||
useSWR(
|
||||
transferToLayingId
|
||||
? ['detail-transfer-to-laying', transferToLayingId]
|
||||
: undefined,
|
||||
([, id]) => TransferToLayingApi.getSingle(Number(id))
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -71,7 +75,11 @@ const TransferToLayingFormModal = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
const formModal = useModal();
|
||||
const successModal = useModal();
|
||||
|
||||
const [formikLastValues, setFormikLastValues] = useState<
|
||||
TransferToLayingFormValues | undefined
|
||||
>(undefined);
|
||||
const [formErrorMessage, setFormErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Flock Source
|
||||
@@ -133,6 +141,7 @@ const TransferToLayingFormModal = () => {
|
||||
toast.success(createTransferToLayingRes?.message as string);
|
||||
router.push('/production/transfer-to-laying');
|
||||
closeModalHandler(false);
|
||||
successModal.openModal();
|
||||
},
|
||||
[router]
|
||||
);
|
||||
@@ -156,6 +165,7 @@ const TransferToLayingFormModal = () => {
|
||||
toast.success(updateKandangRes?.message as string);
|
||||
router.push('/production/transfer-to-laying');
|
||||
closeModalHandler(false);
|
||||
successModal.openModal();
|
||||
},
|
||||
[router]
|
||||
);
|
||||
@@ -187,6 +197,8 @@ const TransferToLayingFormModal = () => {
|
||||
reason: values.reason as string,
|
||||
};
|
||||
|
||||
setFormikLastValues(values);
|
||||
|
||||
switch (modalAction) {
|
||||
case 'add':
|
||||
await createTransferToLayingHandler(transferToLayingPayload);
|
||||
@@ -244,7 +256,7 @@ const TransferToLayingFormModal = () => {
|
||||
? selectedFlockSourceRawData.kandangs.map((kandang) => {
|
||||
const availability =
|
||||
flockSourceKandangsAvailability[kandang.project_flock_kandang_id]
|
||||
.available_qty;
|
||||
?.available_qty;
|
||||
|
||||
return {
|
||||
kandang_name: kandang.name,
|
||||
@@ -375,6 +387,12 @@ const TransferToLayingFormModal = () => {
|
||||
formik.setValues(filledInitialValues);
|
||||
setStep(3);
|
||||
}
|
||||
|
||||
if (isResponseError(transferToLaying)) {
|
||||
router.push('/production/transfer-to-laying');
|
||||
closeModalHandler();
|
||||
toast.error(transferToLaying.message);
|
||||
}
|
||||
};
|
||||
|
||||
const getFlockSourceData = async () => {
|
||||
@@ -437,7 +455,7 @@ const TransferToLayingFormModal = () => {
|
||||
<div className='w-px border-none bg-base-content/10' />
|
||||
|
||||
<h4 className='text-sm font-medium text-base-content/50'>
|
||||
Add Transfer to Laying
|
||||
{modalAction === 'add' ? 'Add' : 'Edit'} Transfer to Laying
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@@ -771,7 +789,12 @@ const TransferToLayingFormModal = () => {
|
||||
<div className='flex flex-col gap-3'>
|
||||
{formik.values.flockSourceKandangs.map((item, index) => {
|
||||
const isInvalid =
|
||||
item.quantity === ''
|
||||
!Boolean(
|
||||
getIn(
|
||||
formik.touched,
|
||||
`flockSourceKandangs[${index}].quantity`
|
||||
)
|
||||
) && item.quantity === ''
|
||||
? false
|
||||
: Boolean(
|
||||
getIn(
|
||||
@@ -833,7 +856,8 @@ const TransferToLayingFormModal = () => {
|
||||
: 'neutral'
|
||||
}
|
||||
text={`Sisa transfer: ${formatNumber(
|
||||
totalAvailableChickenForTransfer
|
||||
totalAvailableChickenForTransfer,
|
||||
'en-US'
|
||||
)} ekor`}
|
||||
className={{
|
||||
badge: 'text-nowrap',
|
||||
@@ -852,7 +876,12 @@ const TransferToLayingFormModal = () => {
|
||||
{formik.values.flockDestinationKandangs.map(
|
||||
(item, index) => {
|
||||
const isInvalid =
|
||||
item.quantity === ''
|
||||
!Boolean(
|
||||
getIn(
|
||||
formik.touched,
|
||||
`flockDestinationKandangs.${index}.quantity`
|
||||
)
|
||||
) && item.quantity === ''
|
||||
? false
|
||||
: Boolean(
|
||||
getIn(
|
||||
@@ -968,6 +997,16 @@ const TransferToLayingFormModal = () => {
|
||||
)}
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<TransferToLayingConfirmationModal
|
||||
ref={successModal.ref}
|
||||
type='success'
|
||||
text='Data Berhasil Ditambahkan'
|
||||
subtitleText='Data transfer to laying telah berhasil disimpan.'
|
||||
transferToLayingForm={formikLastValues}
|
||||
onClose={() => setFormikLastValues(undefined)}
|
||||
secondaryButton={undefined}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,9 +14,7 @@ import { Icon } from '@iconify/react';
|
||||
import Table from '@/components/Table';
|
||||
import Button from '@/components/Button';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
import Badge from '@/components/Badge';
|
||||
@@ -24,13 +22,14 @@ import PopoverContent from '@/components/popover/PopoverContent';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import TransferToLayingFilterModal from '@/components/pages/production/transfer-to-laying/TransferToLayingFilterModal';
|
||||
import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal';
|
||||
|
||||
import {
|
||||
TransferToLaying,
|
||||
TransferToLayingFilter,
|
||||
} from '@/types/api/production/transfer-to-laying';
|
||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||
import { cn, formatDate } from '@/lib/helper';
|
||||
import { cn, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { Color } from '@/types/theme';
|
||||
@@ -38,14 +37,11 @@ import { Color } from '@/types/theme';
|
||||
const RowOptionsMenu = ({
|
||||
props,
|
||||
popoverPosition = 'bottom',
|
||||
approveClickHandler,
|
||||
rejectClickHandler,
|
||||
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
props: CellContext<TransferToLaying, unknown>;
|
||||
popoverPosition: 'bottom' | 'top';
|
||||
approveClickHandler: () => void;
|
||||
rejectClickHandler: () => void;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
const showEditButton =
|
||||
@@ -54,9 +50,6 @@ const RowOptionsMenu = ({
|
||||
|
||||
const showDeleteButton = showEditButton;
|
||||
|
||||
// const showApproveButton = showEditButton;
|
||||
// const showRejectButton = showEditButton;
|
||||
|
||||
const popoverId = `transferToLaying#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`;
|
||||
|
||||
@@ -260,7 +253,14 @@ const TransferToLayingsTable = () => {
|
||||
{
|
||||
accessorKey: 'usage_qty',
|
||||
header: 'Kuantitas',
|
||||
cell: (props) => props.getValue() ?? props.row.original.pending_usage_qty,
|
||||
cell: (props) => {
|
||||
const totalQuantity = props.row.original.targets.reduce(
|
||||
(total, target) => total + target.qty,
|
||||
0
|
||||
);
|
||||
|
||||
return formatNumber(totalQuantity, 'en-US');
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'notes',
|
||||
@@ -304,38 +304,20 @@ const TransferToLayingsTable = () => {
|
||||
|
||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||
|
||||
const approveClickHandler = () => {
|
||||
setSelectedTransferToLaying(props.row.original);
|
||||
|
||||
// Set row selection
|
||||
setRowSelection({
|
||||
[String(props.row.original.id)]: true,
|
||||
});
|
||||
|
||||
approveModal.openModal();
|
||||
};
|
||||
|
||||
const rejectClickHandler = () => {
|
||||
setSelectedTransferToLaying(props.row.original);
|
||||
|
||||
// Set row selection
|
||||
setRowSelection({
|
||||
[String(props.row.original.id)]: true,
|
||||
});
|
||||
|
||||
rejectModal.openModal();
|
||||
};
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
setSelectedTransferToLaying(props.row.original);
|
||||
|
||||
// Set row selection
|
||||
setRowSelection({
|
||||
[String(props.row.original.id)]: true,
|
||||
});
|
||||
|
||||
deleteModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
approveClickHandler={approveClickHandler}
|
||||
rejectClickHandler={rejectClickHandler}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
@@ -377,6 +359,8 @@ const TransferToLayingsTable = () => {
|
||||
|
||||
refreshTransferToLayings();
|
||||
|
||||
setRowSelection({});
|
||||
setSelectedTransferToLaying(undefined);
|
||||
deleteModal.closeModal();
|
||||
toast.success('Berhasil menghapus data transfer ke laying!');
|
||||
setIsDeleteLoading(false);
|
||||
@@ -646,53 +630,70 @@ const TransferToLayingsTable = () => {
|
||||
onReset={filterResetHandler}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
<TransferToLayingConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
iconPosition='left'
|
||||
type='error'
|
||||
text='Delete This Data?'
|
||||
subtitleText='Are you sure you want to delete this data? '
|
||||
transferToLayingIds={selectedRowIds}
|
||||
primaryButton={{
|
||||
isLoading: isDeleteLoading,
|
||||
color: 'error',
|
||||
onClick: confirmationModalDeleteClickHandler,
|
||||
}}
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Delete',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: confirmationModalDeleteClickHandler,
|
||||
color: 'none',
|
||||
onClick: () => {
|
||||
setRowSelection({});
|
||||
deleteModal.closeModal();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConfirmationModalWithNotes
|
||||
{/* Approve Modal */}
|
||||
<TransferToLayingConfirmationModal
|
||||
ref={approveModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text='Approve This Submission?'
|
||||
subtitleText='Are you sure you want to approve this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
type='success'
|
||||
transferToLayingIds={selectedRowIds}
|
||||
withNote
|
||||
noteLabel='Notes Approval'
|
||||
primaryButton={{
|
||||
text: 'Approve',
|
||||
color: 'success',
|
||||
isLoading: isApproveLoading,
|
||||
onClick: confirmationModalApproveClickHandler,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConfirmationModalWithNotes
|
||||
ref={rejectModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text='Reject This Submission?'
|
||||
subtitleText='Are you sure you want to reject this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
color: 'none',
|
||||
onClick: () => {
|
||||
setRowSelection({});
|
||||
approveModal.closeModal();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Reject Modal */}
|
||||
<TransferToLayingConfirmationModal
|
||||
ref={rejectModal.ref}
|
||||
type='error'
|
||||
text='Reject This Submission?'
|
||||
subtitleText='Are you sure you want to reject this submission?'
|
||||
transferToLayingIds={selectedRowIds}
|
||||
withNote
|
||||
noteLabel='Notes Reject'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
color: 'none',
|
||||
onClick: () => {
|
||||
setRowSelection({});
|
||||
rejectModal.closeModal();
|
||||
},
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Reject',
|
||||
color: 'error',
|
||||
isLoading: isRejectLoading,
|
||||
color: 'error',
|
||||
onClick: confirmationModalRejectClickHandler,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -312,6 +312,7 @@ export function DailyChecklistContent() {
|
||||
try {
|
||||
const activitiesRes = await PhaseActivityApi.getAll({
|
||||
phase_ids: selectedPhaseIds.join(','),
|
||||
limit: '100',
|
||||
});
|
||||
|
||||
if (isResponseError(activitiesRes)) {
|
||||
|
||||
Reference in New Issue
Block a user