mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/transfer-to-laying' into 'development'
[FIX/FE] Transfer to Laying See merge request mbugroup/lti-web-client!219
This commit is contained in:
Generated
+4
-4
@@ -52,7 +52,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"daisyui": "^5.5.8",
|
||||
"daisyui": "^5.5.14",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "^15.5.7",
|
||||
"husky": "^9.1.7",
|
||||
@@ -5855,9 +5855,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "5.5.8",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.8.tgz",
|
||||
"integrity": "sha512-6psL9jIEOFOw68V10j/BKCWcRgx8dh81mmNxShr+g7HDM6UHNoPharlp9zq/PQkHNuGU1ZQsajR3HgpvavbRKQ==",
|
||||
"version": "5.5.14",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.14.tgz",
|
||||
"integrity": "sha512-L47rvw7I7hK68TA97VB8Ee0woHew+/ohR6Lx6Ah/krfISOqcG4My7poNpX5Mo5/ytMxiR40fEaz6njzDi7cuSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"daisyui": "^5.5.8",
|
||||
"daisyui": "^5.5.14",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "^15.5.7",
|
||||
"husky": "^9.1.7",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
|
||||
@theme {
|
||||
--font-inter: var(--font-inter);
|
||||
--font-roboto: var(--font-roboto);
|
||||
|
||||
--container-sm: 40rem;
|
||||
--container-md: 48rem;
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { Inter, Roboto } from 'next/font/google';
|
||||
import '@/app/globals.css';
|
||||
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
@@ -12,6 +12,12 @@ const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const roboto = Roboto({
|
||||
variable: '--font-roboto',
|
||||
subsets: ['latin'],
|
||||
weight: ['200', '300', '400', '500', '600', '700', '900'],
|
||||
});
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#1f74bf',
|
||||
colorScheme: 'light',
|
||||
@@ -30,7 +36,9 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang='en' data-theme='lti'>
|
||||
<body className={`${inter.variable} antialiased font-inter`}>
|
||||
<body
|
||||
className={`${inter.variable} ${roboto.variable} antialiased font-inter`}
|
||||
>
|
||||
<RequireAuth>
|
||||
<MainDrawer>{children}</MainDrawer>
|
||||
</RequireAuth>
|
||||
|
||||
@@ -162,7 +162,7 @@ const Drawer = ({
|
||||
<div
|
||||
className={cn(
|
||||
varianClassName?.drawerSidebarContent,
|
||||
className?.drawerContent,
|
||||
className?.drawerSidebarContent,
|
||||
'overflow-y-auto'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -26,29 +26,34 @@ const MainDrawerContent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-full p-4 flex flex-col gap-4'>
|
||||
<div className='flex flex-row items-center gap-4'>
|
||||
<Image
|
||||
src='/assets/img/lti-logo.png'
|
||||
alt='MBU Logo'
|
||||
width={256}
|
||||
height={256}
|
||||
className='w-full max-w-16 h-auto'
|
||||
/>
|
||||
<div className='w-full flex flex-col'>
|
||||
<div className='p-3 flex flex-row items-center gap-4 border-b border-base-content/10'>
|
||||
<div className='flex flex-row items-center gap-2'>
|
||||
<Image
|
||||
src='/assets/img/lti-logo.png'
|
||||
alt='LTI Logo'
|
||||
width={40}
|
||||
height={40}
|
||||
className='w-full max-w-10 h-auto'
|
||||
/>
|
||||
|
||||
<h1 className='text-xl font-bold'>LTI ERP</h1>
|
||||
<div className='font-roboto'>
|
||||
<h1 className='text-sm font-semibold'>LTI ERP</h1>
|
||||
<p className='text-sm text-black/50'>Lumbung Telur Indonesia</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grow flex flex-row justify-end sm:hidden'>
|
||||
<Button
|
||||
variant='soft'
|
||||
color='error'
|
||||
onClick={closeMainDrawerHandler}
|
||||
className='rounded-full'
|
||||
className='p-1 rounded-full'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:close-rounded'
|
||||
width={24}
|
||||
height={24}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -121,6 +126,10 @@ const MainDrawer = ({
|
||||
setOpen={setMainDrawerOpen}
|
||||
openOnLarge
|
||||
sidebarContent={<MainDrawerContent />}
|
||||
className={{
|
||||
drawerSide: 'border-r border-base-content/10',
|
||||
drawerSidebarContent: 'min-w-[244px] lg:w-[244px]',
|
||||
}}
|
||||
>
|
||||
<main className='w-full h-full flex flex-col'>
|
||||
<Navbar title={pageTitle as string} toggleSidebar={toggleSidebar} />
|
||||
|
||||
+64
-11
@@ -86,7 +86,7 @@ export const TABLE_DEFAULT_STYLING = {
|
||||
tableHeaderClassName: '',
|
||||
headerRowClassName: '',
|
||||
headerColumnClassName:
|
||||
'px-4 py-3 border-base-content/10 text-base-content/50',
|
||||
'px-4 py-3 border-base-content/10 text-base-content/50 text-sm font-medium',
|
||||
tableBodyClassName: '',
|
||||
bodyRowClassName: 'border-t border-base-content/10',
|
||||
bodyColumnClassName: 'px-4 py-3 text-base-content',
|
||||
@@ -222,14 +222,37 @@ const Table = <TData extends object>({
|
||||
}, [pageSize, setPageSize]);
|
||||
|
||||
return (
|
||||
<div className={tableClassNames.containerClassName}>
|
||||
<div className={tableClassNames.tableWrapperClassName}>
|
||||
<table className={tableClassNames.tableClassName}>
|
||||
<thead className={tableClassNames.tableHeaderClassName}>
|
||||
<div
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.containerClassName,
|
||||
tableClassNames.containerClassName
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.tableWrapperClassName,
|
||||
tableClassNames.tableWrapperClassName
|
||||
)}
|
||||
>
|
||||
<table
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.tableClassName,
|
||||
tableClassNames.tableClassName
|
||||
)}
|
||||
>
|
||||
<thead
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.tableHeaderClassName,
|
||||
tableClassNames.tableHeaderClassName
|
||||
)}
|
||||
>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr
|
||||
key={headerGroup.id}
|
||||
className={tableClassNames.headerRowClassName}
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.headerRowClassName,
|
||||
tableClassNames.headerRowClassName
|
||||
)}
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const columnRelativeDepth =
|
||||
@@ -262,6 +285,7 @@ const Table = <TData extends object>({
|
||||
{
|
||||
'border-b': header.colSpan > 1,
|
||||
},
|
||||
TABLE_DEFAULT_STYLING.headerColumnClassName,
|
||||
tableClassNames.headerColumnClassName
|
||||
)}
|
||||
>
|
||||
@@ -311,7 +335,12 @@ const Table = <TData extends object>({
|
||||
))}
|
||||
</thead>
|
||||
|
||||
<tbody className={tableClassNames.tableBodyClassName}>
|
||||
<tbody
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.tableBodyClassName,
|
||||
tableClassNames.tableBodyClassName
|
||||
)}
|
||||
>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
const customRowContent = renderCustomRow?.(row);
|
||||
|
||||
@@ -320,12 +349,19 @@ const Table = <TData extends object>({
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
||||
<tr
|
||||
key={row.id}
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.bodyRowClassName,
|
||||
tableClassNames.bodyRowClassName
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={cn(
|
||||
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||
TABLE_DEFAULT_STYLING.bodyColumnClassName,
|
||||
tableClassNames.bodyColumnClassName
|
||||
)}
|
||||
>
|
||||
@@ -342,14 +378,25 @@ const Table = <TData extends object>({
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot className={cn(tableClassNames.tableFooterClassName)}>
|
||||
<tfoot
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.tableFooterClassName,
|
||||
tableClassNames.tableFooterClassName
|
||||
)}
|
||||
>
|
||||
{renderFooter && (
|
||||
<tr className={cn(tableClassNames.footerRowClassName)}>
|
||||
<tr
|
||||
className={cn(
|
||||
TABLE_DEFAULT_STYLING.footerRowClassName,
|
||||
tableClassNames.footerRowClassName
|
||||
)}
|
||||
>
|
||||
{table.getAllLeafColumns().map((column) => (
|
||||
<td
|
||||
key={column.id}
|
||||
className={cn(
|
||||
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||
TABLE_DEFAULT_STYLING.footerColumnClassName,
|
||||
tableClassNames.footerColumnClassName
|
||||
)}
|
||||
>
|
||||
@@ -372,7 +419,13 @@ const Table = <TData extends object>({
|
||||
emptyContent}
|
||||
|
||||
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && (
|
||||
<div className={cn('mt-5', tableClassNames.paginationClassName)}>
|
||||
<div
|
||||
className={cn(
|
||||
'mt-5',
|
||||
TABLE_DEFAULT_STYLING.paginationClassName,
|
||||
tableClassNames.paginationClassName
|
||||
)}
|
||||
>
|
||||
<Pagination
|
||||
totalItems={isServerSideTable ? totalItems : table.getRowCount()}
|
||||
itemsPerPage={table.getState().pagination.pageSize}
|
||||
|
||||
@@ -39,16 +39,15 @@ const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
||||
<li>
|
||||
<Link
|
||||
href={item.link}
|
||||
className={cn(
|
||||
{
|
||||
'menu-active border-2 border-solid border-base-300': isItemActive,
|
||||
},
|
||||
'px-3 py-1.5'
|
||||
)}
|
||||
className={cn('px-3 py-1.5', {
|
||||
'text-base-content/60': !isItemActive,
|
||||
'menu-active border-[1.5px] border-solid border-base-300':
|
||||
isItemActive,
|
||||
})}
|
||||
>
|
||||
{item.icon && <Icon icon={item.icon} width={20} height={20} />}
|
||||
|
||||
<span className='text-base'>{item.text}</span>
|
||||
<span className='text-sm'>{item.text}</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
@@ -62,12 +61,13 @@ const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
||||
<details open={isItemActive}>
|
||||
<summary
|
||||
className={cn({
|
||||
'text-base-content/60': !isItemActive,
|
||||
'text-primary': isItemActive,
|
||||
})}
|
||||
>
|
||||
{item.icon && <Icon icon={item.icon} width={20} height={20} />}
|
||||
|
||||
<span className='text-base'>{item.text}</span>
|
||||
<span className='text-sm'>{item.text}</span>
|
||||
</summary>
|
||||
|
||||
<ul>
|
||||
@@ -88,7 +88,7 @@ const SidebarMenuItem = ({ item, activeLink }: SidebarMenuItemProps) => {
|
||||
|
||||
const SidebarMenu = ({ menu, activeLink }: SidebarMenuProps) => {
|
||||
return (
|
||||
<Menu>
|
||||
<Menu className='p-3'>
|
||||
{menu.map((menuItem, menuIdx) => {
|
||||
return (
|
||||
<SidebarMenuItem
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ChangeEventHandler, useState } from 'react';
|
||||
import { ChangeEventHandler, useEffect, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import {
|
||||
CellContext,
|
||||
@@ -20,33 +20,32 @@ import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
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 ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import PopoverButton from '@/components/popover/PopoverButton';
|
||||
|
||||
import { TransferToLaying } from '@/types/api/production/transfer-to-laying';
|
||||
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
|
||||
import { cn, formatDate } from '@/lib/helper';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { Flock } from '@/types/api/master-data/flock';
|
||||
import { FlockApi } from '@/services/api/master-data';
|
||||
import PillBadge from '@/components/PillBadge';
|
||||
import { ProjectFlockApi } from '@/services/api/production';
|
||||
import Badge from '@/components/Badge';
|
||||
import { Color } from '@/types/theme';
|
||||
import PopoverContent from '@/components/popover/PopoverContent';
|
||||
|
||||
const RowOptionsMenu = ({
|
||||
type = 'dropdown',
|
||||
props,
|
||||
popoverPosition = 'bottom',
|
||||
approveClickHandler,
|
||||
rejectClickHandler,
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
type: 'dropdown' | 'collapse';
|
||||
props: CellContext<TransferToLaying, unknown>;
|
||||
popoverPosition: 'bottom' | 'top';
|
||||
approveClickHandler: () => void;
|
||||
rejectClickHandler: () => void;
|
||||
deleteClickHandler: () => void;
|
||||
@@ -60,80 +59,99 @@ const RowOptionsMenu = ({
|
||||
const showApproveButton = showEditButton;
|
||||
const showRejectButton = showEditButton;
|
||||
|
||||
const popoverId = `transferToLaying#${props.row.original.id}`;
|
||||
const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`;
|
||||
|
||||
return (
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.detail'>
|
||||
<Button
|
||||
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='primary'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||
Detail
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<div className='relative'>
|
||||
<PopoverButton
|
||||
tabIndex={0}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
popoverTarget={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
>
|
||||
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||
</PopoverButton>
|
||||
|
||||
{showEditButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.update'>
|
||||
<Button
|
||||
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||
Edit
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
<PopoverContent
|
||||
id={popoverId}
|
||||
anchorName={popoverAnchorName}
|
||||
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
|
||||
className='rounded-xl border border-base-content/5 shadow-sm'
|
||||
>
|
||||
<div className='flex flex-col bg-base-100 rounded-xl'>
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.detail'>
|
||||
<Button
|
||||
href={`/production/transfer-to-laying/detail/?transferToLayingId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:eye' width={20} height={20} />
|
||||
View Details
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
|
||||
{/* TODO: apply RBAC */}
|
||||
{showApproveButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='success'
|
||||
onClick={approveClickHandler}
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||
Approve
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{showRejectButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='error'
|
||||
onClick={rejectClickHandler}
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||
Reject
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{showDeleteButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.delete'>
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={16}
|
||||
height={16}
|
||||
className='justify-start text-sm'
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</RowOptionsMenuWrapper>
|
||||
{showEditButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.update'>
|
||||
<Button
|
||||
href={`/production/transfer-to-laying/detail/edit/?transferToLayingId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='none'
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:pencil-square' width={20} height={20} />
|
||||
Edit
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
|
||||
{showApproveButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='success'
|
||||
onClick={approveClickHandler}
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:check' width={20} height={20} />
|
||||
Approve
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
|
||||
{showRejectButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='error'
|
||||
onClick={rejectClickHandler}
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
Reject
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
|
||||
{showDeleteButton && (
|
||||
<RequirePermission permissions='lti.production.transfer_to_laying.delete'>
|
||||
<hr className='mx-3 border-base-content/10 h-px' />
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='p-3 justify-start text-sm font-semibold w-full'
|
||||
>
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
Delete
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -150,6 +168,8 @@ const TransferToLayingsTable = () => {
|
||||
transferDate: '',
|
||||
flockSource: '',
|
||||
flockDestination: '',
|
||||
filter_by: '',
|
||||
sort_by: '',
|
||||
},
|
||||
paramMap: {
|
||||
page: 'page',
|
||||
@@ -157,6 +177,8 @@ const TransferToLayingsTable = () => {
|
||||
transferDate: 'transfer_date',
|
||||
flockSource: 'flock_source',
|
||||
flockDestination: 'flock_destination',
|
||||
filter_by: 'filter_by',
|
||||
sort_by: 'sort_by',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -181,7 +203,7 @@ const TransferToLayingsTable = () => {
|
||||
isLoadingOptions: isLoadingFlockSourceOptions,
|
||||
loadMore: loadMoreFlockSource,
|
||||
hasMore: hasMoreFlockSource,
|
||||
} = useSelect<Flock>(FlockApi.basePath, 'id', 'name');
|
||||
} = useSelect<Flock>(ProjectFlockApi.basePath, 'id', 'flock_name');
|
||||
|
||||
const {
|
||||
setInputValue: setFlockDestinationInputValue,
|
||||
@@ -189,7 +211,7 @@ const TransferToLayingsTable = () => {
|
||||
isLoadingOptions: isLoadingFlockDestinationOptions,
|
||||
loadMore: loadMoreFlockDestination,
|
||||
hasMore: hasMoreFlockDestination,
|
||||
} = useSelect<Flock>(FlockApi.basePath, 'id', 'name');
|
||||
} = useSelect<Flock>(ProjectFlockApi.basePath, 'id', 'flock_name');
|
||||
|
||||
// Flocks value
|
||||
const [selectedFlockSource, setSelectedFlockSource] =
|
||||
@@ -244,13 +266,6 @@ const TransferToLayingsTable = () => {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: '#',
|
||||
cell: (props) =>
|
||||
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
||||
props.row.index +
|
||||
1,
|
||||
},
|
||||
{
|
||||
accessorKey: 'transfer_date',
|
||||
header: 'Tanggal Transfer',
|
||||
@@ -274,6 +289,7 @@ const TransferToLayingsTable = () => {
|
||||
{
|
||||
accessorKey: 'notes',
|
||||
header: 'Alasan Transfer',
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
@@ -282,34 +298,39 @@ const TransferToLayingsTable = () => {
|
||||
props.row.original.approval.action === 'REJECTED';
|
||||
let latestApprovalStepName = props.row.original.approval.step_name;
|
||||
|
||||
let pillBadgeColor: 'yellow' | 'green' | 'gray' | 'red' = 'gray';
|
||||
let badgeColor: Color = 'neutral';
|
||||
|
||||
switch (latestApprovalStepName.toLowerCase()) {
|
||||
case 'pengajuan':
|
||||
pillBadgeColor = 'yellow';
|
||||
badgeColor = 'neutral';
|
||||
break;
|
||||
|
||||
case 'disetujui':
|
||||
pillBadgeColor = 'green';
|
||||
badgeColor = 'success';
|
||||
break;
|
||||
}
|
||||
|
||||
if (isLatestApprovalRejected) {
|
||||
pillBadgeColor = 'red';
|
||||
badgeColor = 'error';
|
||||
latestApprovalStepName = 'Ditolak';
|
||||
}
|
||||
|
||||
return (
|
||||
<PillBadge
|
||||
content={latestApprovalStepName}
|
||||
color={pillBadgeColor}
|
||||
className='text-sm'
|
||||
/>
|
||||
<Badge
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2 w-full flex flex-row justify-start',
|
||||
}}
|
||||
color={badgeColor}
|
||||
>
|
||||
<Icon icon='mdi:circle' width={12} height={12} color={badgeColor} />
|
||||
{latestApprovalStepName}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
id: 'actions',
|
||||
cell: (props) => {
|
||||
const currentPageSize = props.table.getPaginationRowModel().rows.length;
|
||||
const currentPageRows = props.table.getPaginationRowModel().flatRows;
|
||||
@@ -346,31 +367,13 @@ const TransferToLayingsTable = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentPageSize > 3 && (
|
||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
props={props}
|
||||
approveClickHandler={approveClickHandler}
|
||||
rejectClickHandler={rejectClickHandler}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowDropdownOptions>
|
||||
)}
|
||||
|
||||
{currentPageSize <= 3 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='collapse'
|
||||
props={props}
|
||||
approveClickHandler={approveClickHandler}
|
||||
rejectClickHandler={rejectClickHandler}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowCollapseOptions>
|
||||
)}
|
||||
</>
|
||||
<RowOptionsMenu
|
||||
props={props}
|
||||
approveClickHandler={approveClickHandler}
|
||||
rejectClickHandler={rejectClickHandler}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -397,17 +400,21 @@ const TransferToLayingsTable = () => {
|
||||
const confirmationModalDeleteClickHandler = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
try {
|
||||
await TransferToLayingApi.delete(selectedTransferToLaying?.id as number);
|
||||
const deleteResponse = await TransferToLayingApi.delete(
|
||||
selectedTransferToLaying?.id as number
|
||||
);
|
||||
|
||||
toast.success('Berhasil menghapus data transfer ke laying!');
|
||||
refreshTransferToLayings();
|
||||
} catch (error) {
|
||||
toast.success('Gagal menghapus data transfer ke laying!');
|
||||
} finally {
|
||||
deleteModal.closeModal();
|
||||
if (isResponseError(deleteResponse)) {
|
||||
toast.error(deleteResponse.message);
|
||||
setIsDeleteLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
refreshTransferToLayings();
|
||||
|
||||
deleteModal.closeModal();
|
||||
toast.success('Berhasil menghapus data transfer ke laying!');
|
||||
setIsDeleteLoading(false);
|
||||
};
|
||||
|
||||
const confirmationModalApproveClickHandler = async (notes: string) => {
|
||||
@@ -499,20 +506,19 @@ const TransferToLayingsTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// track sorting
|
||||
// useEffect(() => {
|
||||
// const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
||||
|
||||
// if (!isNameSorted) {
|
||||
// updateFilter('nameSort', '');
|
||||
// } else {
|
||||
// updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
|
||||
// }
|
||||
// }, [sorting, updateFilter]);
|
||||
useEffect(() => {
|
||||
if (sorting.length === 1) {
|
||||
updateFilter('filter_by', sorting[0].id);
|
||||
updateFilter('sort_by', sorting[0].desc ? 'desc' : 'asc');
|
||||
} else {
|
||||
updateFilter('filter_by', '');
|
||||
updateFilter('sort_by', '');
|
||||
}
|
||||
}, [sorting]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<div className='w-full p-0'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||
@@ -579,12 +585,10 @@ const TransferToLayingsTable = () => {
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-12 justify-end gap-4'>
|
||||
<TextInput
|
||||
required
|
||||
type='date'
|
||||
label='Tanggal Transfer'
|
||||
<DateInput
|
||||
name='transfer_date'
|
||||
placeholder='Masukkan tanggal transfer'
|
||||
label='Tanggal Transfer'
|
||||
placeholder='Tanggal Transfer'
|
||||
value={tableFilterState.transferDate}
|
||||
onChange={transferDateChangeHandler}
|
||||
className={{
|
||||
@@ -619,20 +623,6 @@ const TransferToLayingsTable = () => {
|
||||
wrapper: 'col-span-12 sm:col-span-3',
|
||||
}}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
label='Baris'
|
||||
options={ROWS_OPTIONS}
|
||||
value={{
|
||||
label: String(tableFilterState.pageSize),
|
||||
value: tableFilterState.pageSize,
|
||||
}}
|
||||
onChange={pageSizeChangeHandler}
|
||||
className={{
|
||||
wrapper:
|
||||
'col-span-6 sm:col-span-3 max-w-28 sm:justify-self-end',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -653,26 +643,21 @@ const TransferToLayingsTable = () => {
|
||||
: 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
isLoading={isLoading}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
enableRowSelection={tableEnableRowSelectionHandler}
|
||||
withCheckbox
|
||||
className={{
|
||||
containerClassName: cn({
|
||||
'mb-20':
|
||||
'w-full mb-20':
|
||||
isResponseSuccess(transferToLayings) &&
|
||||
transferToLayings?.data?.length === 0,
|
||||
}),
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||
headerColumnClassName: 'text-nowrap',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -80,7 +80,7 @@ export const TransferToLayingFormSchema: Yup.ObjectSchema<TransferToLayingFormSc
|
||||
)
|
||||
.required('Kuantitas wajib diisi!'),
|
||||
|
||||
maxQuantity: Yup.number().min(1).required(), // internal helper field
|
||||
maxQuantity: Yup.number().min(0).required(), // internal helper field
|
||||
})
|
||||
)
|
||||
.min(1, 'Minimal 1 kandang terisi!')
|
||||
@@ -102,7 +102,7 @@ export const TransferToLayingFormSchema: Yup.ObjectSchema<TransferToLayingFormSc
|
||||
)
|
||||
.required('Kuantitas wajib diisi!'),
|
||||
|
||||
maxQuantity: Yup.number().min(1).required(), // internal helper field
|
||||
maxQuantity: Yup.number().min(0).required(), // internal helper field
|
||||
})
|
||||
)
|
||||
.min(1, 'Minimal 1 kandang terisi!')
|
||||
|
||||
@@ -123,6 +123,13 @@ const DailyMarketingsTable = ({
|
||||
accessorKey: 'average_weight',
|
||||
header: 'Bobot Rata-Rata (Kg)',
|
||||
cell: (props) => formatNumber(props.row.original.average_weight_kg),
|
||||
footer: () => {
|
||||
const totalAverageWeightKg = isResponseSuccess(dailyMarketings)
|
||||
? dailyMarketings?.total?.total_average_weight
|
||||
: 0;
|
||||
|
||||
return totalAverageWeightKg ? formatNumber(totalAverageWeightKg) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'total_weight',
|
||||
@@ -140,6 +147,13 @@ const DailyMarketingsTable = ({
|
||||
accessorKey: 'sales_price',
|
||||
header: 'Harga Jual (Rp)',
|
||||
cell: (props) => formatCurrency(props.row.original.sales_price_per_kg),
|
||||
footer: () => {
|
||||
const totalSalesPrice = isResponseSuccess(dailyMarketings)
|
||||
? dailyMarketings?.total?.total_sales_price
|
||||
: 0;
|
||||
|
||||
return totalSalesPrice ? formatNumber(totalSalesPrice) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'hpp_price',
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import Button, { ButtonProps } from '@/components/Button';
|
||||
|
||||
export interface PopoverButtonProps extends ButtonProps {
|
||||
popoverTarget: string;
|
||||
anchorName: string;
|
||||
}
|
||||
|
||||
const PopoverButton = ({
|
||||
children,
|
||||
popoverTarget,
|
||||
anchorName,
|
||||
...props
|
||||
}: PopoverButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
popoverTarget={popoverTarget}
|
||||
style={
|
||||
{
|
||||
anchorName: anchorName,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopoverButton;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
export interface PopoverContentProps {
|
||||
children: React.ReactNode;
|
||||
id: string;
|
||||
anchorName: string; // Must include `--` like "--menu-anchor"
|
||||
popover?: 'auto' | 'hint' | 'manual';
|
||||
position?:
|
||||
| 'top'
|
||||
| 'bottom'
|
||||
| 'left'
|
||||
| 'right'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
| 'bottom-start'
|
||||
| 'bottom-end'
|
||||
| 'left-start'
|
||||
| 'left-end'
|
||||
| 'right-start'
|
||||
| 'right-end';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const positionAreaMap: Record<
|
||||
NonNullable<PopoverContentProps['position']>,
|
||||
string
|
||||
> = {
|
||||
top: 'top center',
|
||||
bottom: 'bottom center',
|
||||
left: 'left center',
|
||||
right: 'right center',
|
||||
|
||||
'top-start': 'top left',
|
||||
'top-end': 'top right',
|
||||
'bottom-start': 'bottom left',
|
||||
'bottom-end': 'bottom right',
|
||||
|
||||
'left-start': 'left top',
|
||||
'left-end': 'left bottom',
|
||||
|
||||
'right-start': 'right top',
|
||||
'right-end': 'right bottom',
|
||||
};
|
||||
|
||||
const PopoverContent = ({
|
||||
children,
|
||||
id,
|
||||
anchorName,
|
||||
popover = 'auto',
|
||||
position = 'bottom-start',
|
||||
className,
|
||||
}: PopoverContentProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(className)}
|
||||
id={id}
|
||||
popover={popover}
|
||||
style={
|
||||
{
|
||||
inset: 'unset',
|
||||
positionAnchor: anchorName,
|
||||
positionArea: positionAreaMap[position],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopoverContent;
|
||||
@@ -12,7 +12,7 @@ const RowCollapseOptions = ({ children }: RowCollapseOptionsProps) => {
|
||||
return (
|
||||
<Collapse
|
||||
title={
|
||||
<Button>
|
||||
<Button variant='ghost' color='none'>
|
||||
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const RowDropdownOptions = ({
|
||||
'dropdown-end': isLast2Rows,
|
||||
})}
|
||||
>
|
||||
<Button tabIndex={0}>
|
||||
<Button tabIndex={0} variant='ghost' color='none'>
|
||||
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
||||
permission: ['lti.production.recording.list'],
|
||||
},
|
||||
{
|
||||
text: 'Transfer to Laying',
|
||||
text: 'Transfer ke Laying',
|
||||
link: '/production/transfer-to-laying',
|
||||
},
|
||||
{
|
||||
|
||||
Vendored
+2
@@ -41,7 +41,9 @@ export type DailyMarketingRow = BaseMetadata & BaseDailyMarketingRow;
|
||||
|
||||
export interface SalesSummary {
|
||||
total_qty: number;
|
||||
total_average_weight: number;
|
||||
total_weight_kg: number;
|
||||
total_sales_price: number;
|
||||
total_sales_amount: number;
|
||||
total_hpp_amount: number;
|
||||
total_hpp_price_per_kg: number;
|
||||
|
||||
Reference in New Issue
Block a user