fix(FE): fixing floating action button project flock and change page size

This commit is contained in:
randy-ar
2026-01-13 11:11:10 +07:00
parent 00eed01cea
commit 0ac14fe342
3 changed files with 152 additions and 239 deletions
+3 -3
View File
@@ -39,8 +39,8 @@ const FloatingActionsButton = ({
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB // Jika tidak ada baris yang dipilih, jangan tampilkan FAB
const positionStyles = const positionStyles =
selectedRowIds.length > 0 selectedRowIds.length > 0
? 'bottom-[10%] opacity-100' ? 'bottom-[5%] opacity-100'
: 'bottom-[-10%] opacity-0'; : 'bottom-[-5%] opacity-0';
// Helper untuk menentukan gaya warna tombol approval // Helper untuk menentukan gaya warna tombol approval
const getApprovalColor = (action: 'APPROVED' | 'REJECTED') => { const getApprovalColor = (action: 'APPROVED' | 'REJECTED') => {
@@ -60,7 +60,7 @@ const FloatingActionsButton = ({
// Container utama FAB // Container utama FAB
<div <div
className={cn( className={cn(
`absolute ${positionStyles} inset-x-1/2 -translate-x-1/2 z-50`, `fixed ${positionStyles} inset-x-1/2 -translate-x-1/2 z-50`,
'mx-auto w-full max-w-sm sm:mx-0 bg-base-300 p-4 rounded-xl shadow-md transition-all duration-300 transform', 'mx-auto w-full max-w-sm sm:mx-0 bg-base-300 p-4 rounded-xl shadow-md transition-all duration-300 transform',
'bg-slate-950 backdrop-blur-md' 'bg-slate-950 backdrop-blur-md'
)} )}
@@ -19,7 +19,7 @@ import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Kandang } from '@/types/api/master-data/kandang'; import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { CellContext, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -27,84 +27,6 @@ import useSWR from 'swr';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
const RowOptionsMenu = ({
type = 'dropdown',
props,
deleteClickHandler,
}: {
type: 'dropdown' | 'collapse';
props: CellContext<ProjectFlock, unknown>;
deleteClickHandler: () => void;
}) => {
return (
<div
tabIndex={type == 'dropdown' ? 0 : undefined}
className={cn(
{
'dropdown-content': type === 'dropdown',
'mt-2': type === 'collapse',
},
'p-2.5 mr-2 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
<div className='flex flex-col gap-1'>
<RequirePermission permissions='lti.production.project_flocks.detail'>
<Button
href={`/production/project-flock/detail?projectFlockId=${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>
{props.row.original.approval.step_name === 'Aktif' && (
<RequirePermission permissions='lti.production.chickins.create'>
<Button
href={`/production/project-flock/chickin/add?projectFlockId=${props.row.original.id}`}
variant='ghost'
color='success'
className='justify-start text-sm'
>
<Icon icon='mdi:home-import-outline' width={16} height={16} />
Chickin
</Button>
</RequirePermission>
)}
{props.row.original.approval.step_name === 'Pengajuan' && (
<RequirePermission permissions='lti.production.project_flocks.update'>
<Button
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
variant='ghost'
color='warning'
className='justify-start text-sm'
>
<Icon icon='mdi:pencil-outline' width={16} height={16} />
Edit
</Button>
</RequirePermission>
)}
<RequirePermission permissions='lti.production.project_flocks.delete'>
<Button
onClick={deleteClickHandler}
variant='ghost'
color='error'
className='text-error hover:text-inherit justify-start text-sm'
>
<Icon
icon='material-symbols:delete-outline-rounded'
width={16}
height={16}
/>
Delete
</Button>
</RequirePermission>
</div>
</div>
);
};
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const { const {
state: tableFilterState, state: tableFilterState,
@@ -149,8 +71,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
const [periodInputValue, setPeriodInputValue] = useState<number | null>(null); const [periodInputValue, setPeriodInputValue] = useState<number | null>(null);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<ProjectFlock>();
const deleteModal = useModal(); const deleteModal = useModal();
const confirmModal = useModal(); const confirmModal = useModal();
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
@@ -221,10 +141,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
: []; : [];
// ====== HANDLER ====== // ====== HANDLER ======
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType;
setPageSize(newVal.value as number);
};
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
@@ -292,12 +208,146 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const canApprove = useMemo(() => { const canApprove = useMemo(() => {
if (!selectedSingleRow || isApproveLoading) return false; if (!selectedSingleRow || isApproveLoading) return false;
const isPengajuan = selectedSingleRow.approval.step_number == 1; const isPengajuan = selectedSingleRow.approval?.step_number == 1;
const isNotRejected = selectedSingleRow.approval.action != 'REJECTED'; const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED';
return isPengajuan && isNotRejected; return isPengajuan && isNotRejected;
}, [selectedSingleRow, isApproveLoading]); }, [selectedSingleRow, isApproveLoading]);
// ====== COLUMNS ======
const columns = useMemo<ColumnDef<ProjectFlock>[]>(
() => [
{
id: 'select',
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows;
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0;
const someSelected =
selectableRows.some((row) => row.getIsSelected()) && !allSelected;
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
selectableRows.forEach((row) => row.toggleSelected(shouldSelect));
};
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
/>
</div>
);
},
cell: ({ row }) => {
return (
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
);
},
},
{
accessorKey: 'flock_name',
header: 'Flock',
},
{
accessorKey: 'area.name',
header: 'Area',
},
{
accessorKey: 'location.name',
header: 'Lokasi',
},
{
accessorKey: 'fcr.name',
header: 'FCR',
},
{
accessorKey: 'category',
header: 'Kategori',
},
{
accessorKey: 'approval.step_name',
header: 'Status',
cell: (props) => {
const approval = props.row.original.approval;
return (
<Badge
variant='soft'
className={{
badge: 'rounded-lg px-2 w-full flex flex-row justify-start',
}}
color={
approval?.step_number == 1
? 'neutral'
: approval?.step_number == 2
? 'success'
: 'error'
}
>
<Icon
icon='mdi:circle'
width={12}
height={12}
color={
approval?.step_number == 1
? 'neutral'
: approval?.step_number == 2
? 'success'
: 'error'
}
/>
{approval?.step_name}
</Badge>
);
},
},
{
header: 'Kandang',
cell: (props) => {
const kandang = props.row.original.kandangs;
if (kandang) {
const kandangNames = kandang.map((k: Kandang) => k.name);
return (
<div>
{kandangNames.length > 0
? kandangNames.join(', ')
: 'Tidak ada'}
</div>
);
} else {
return '-';
}
},
},
{
accessorKey: 'period',
header: 'Periode',
},
{
accessorKey: 'created_at',
header: 'Dibuat pada',
cell: (props) =>
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
},
],
[]
);
return ( return (
<> <>
<div className='min-h-screen w-full p-4'> <div className='min-h-screen w-full p-4'>
@@ -320,7 +370,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<div className='ms-auto w-full sm:w-auto'> <div className='ms-auto w-full sm:w-auto'>
<DebouncedTextInput <DebouncedTextInput
name='search' name='search'
placeholder='Cari Area' placeholder='Cari Project Flock'
value={tableFilterState.search} value={tableFilterState.search}
onChange={searchChangeHandler} onChange={searchChangeHandler}
className={{ className={{
@@ -382,160 +432,18 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
type='number' type='number'
label='Periode' label='Periode'
placeholder='Masukan periode' placeholder='Masukan periode'
value={periodInputValue ?? ''} value={periodInputValue?.toString() ?? ''}
onChange={(e) => { onChange={(e) => {
setPeriodInputValue(parseInt(e.target.value)); setPeriodInputValue(parseInt(e.target.value));
updateFilter('periodFilter', e.target.value); updateFilter('periodFilter', e.target.value);
}} }}
/> />
<SelectInput
label='Baris'
options={ROWS_OPTIONS}
value={{
label: String(tableFilterState.pageSize),
value: tableFilterState.pageSize,
}}
onChange={pageSizeChangeHandler}
className={{ wrapper: 'max-w-28' }}
/>
</div> </div>
</div> </div>
<Table<ProjectFlock> <Table<ProjectFlock>
data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []} data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []}
columns={[ columns={columns}
{
id: 'select',
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows;
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0;
const someSelected =
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected;
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
selectableRows.forEach((row) =>
row.toggleSelected(shouldSelect)
);
};
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
/>
</div>
);
},
cell: ({ row }) => {
return (
<CheckboxInput
name='row'
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
);
},
},
{
accessorKey: 'flock_name',
header: 'Flock',
},
{
accessorKey: 'area.name',
header: 'Area',
},
{
accessorKey: 'location.name',
header: 'Lokasi',
},
{
accessorKey: 'fcr.name',
header: 'FCR',
},
{
accessorKey: 'category',
header: 'Kategori',
},
{
accessorKey: 'approval.step_name',
header: 'Status',
cell: (props) => {
const approval = props.row.original.approval;
return (
<Badge
variant='soft'
className={{
badge:
'rounded-lg px-2 w-full flex flex-row justify-start',
}}
color={
approval.step_number == 1
? 'neutral'
: approval.step_number == 2
? 'success'
: 'error'
}
>
<Icon
icon='mdi:circle'
width={12}
height={12}
color={
approval.step_number == 1
? 'neutral'
: approval.step_number == 2
? 'success'
: 'error'
}
/>
{approval.step_name}
</Badge>
);
},
},
{
header: 'Kandang',
cell: (props) => {
const kandang = props.row.original.kandangs;
if (kandang) {
const kandangNames = kandang.map((k: Kandang) => k.name);
return (
<div>
{kandangNames.length > 0
? kandangNames.join(', ')
: 'Tidak ada'}
</div>
);
} else {
return '-';
}
},
},
{
accessorKey: 'period',
header: 'Periode',
},
{
accessorKey: 'created_at',
header: 'Dibuat pada',
cell: (props) =>
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
},
]}
pageSize={tableFilterState.pageSize} pageSize={tableFilterState.pageSize}
page={ page={
isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0 isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0
@@ -545,7 +453,12 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
? projectFlocks?.meta?.total_results ? projectFlocks?.meta?.total_results
: 0 : 0
} }
onPageChange={setPage} onPageChange={(page) => {
setPage(page);
}}
onPageSizeChange={(pageSize) => {
setPageSize(pageSize);
}}
isLoading={isLoading} isLoading={isLoading}
sorting={sorting} sorting={sorting}
setSorting={setSorting} setSorting={setSorting}
@@ -553,9 +466,9 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
setRowSelection={setRowSelection} setRowSelection={setRowSelection}
className={{ className={{
containerClassName: cn({ containerClassName: cn({
'mb-20': 'mb-40':
isResponseSuccess(projectFlocks) && isResponseSuccess(projectFlocks) &&
projectFlocks?.data?.length === 0, projectFlocks?.data?.length > 0,
}), }),
tableWrapperClassName: 'overflow-x-auto min-h-full!', tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!', tableClassName: 'font-inter w-full table-auto min-h-full!',
+1 -1
View File
@@ -289,4 +289,4 @@
"debt_price": -181845000 "debt_price": -181845000
} }
} }
] ]