refactor(FE): Refactor production result components and improve UI

This commit is contained in:
rstubryan
2026-02-13 09:24:42 +07:00
parent 211622c7b0
commit 5c00893ea3
6 changed files with 682 additions and 473 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
import ProductionResultContent from '@/components/pages/report/production-result/tab/ProductionResultTab'; import ProductionResultTabs from '@/components/pages/report/production-result/ProductionResultTabs';
const ProductionResultReportPage = () => { const ProductionResultReportPage = () => {
return ( return (
<section className='w-full max-w-full'> <section className='w-full max-w-full'>
<ProductionResultContent /> <ProductionResultTabs />
</section> </section>
); );
}; };
@@ -4,12 +4,10 @@ import { useEffect, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table'; import { ColumnDef, SortingState } from '@tanstack/react-table';
import { Icon } from '@iconify/react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Card from '@/components/Card'; import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { ProductionResult } from '@/types/api/report/production-result'; import { ProductionResult } from '@/types/api/report/production-result';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
@@ -52,8 +50,6 @@ const ProductionResultProjectFlockKandangTable = ({
} }
); );
const [open, setOpen] = useState(false);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const productionResultColumns: ColumnDef<ProductionResult>[] = [ const productionResultColumns: ColumnDef<ProductionResult>[] = [
@@ -270,61 +266,26 @@ const ProductionResultProjectFlockKandangTable = ({
} }
}, [sorting]); }, [sorting]);
useEffect(() => { return (
if (!open) { <Card
setOpen( title={kandangName}
className={{
wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
collapsible: 'rounded-lg',
}}
variant='bordered'
collapsible={true}
defaultCollapsed={
isResponseSuccess(productionResults) isResponseSuccess(productionResults)
? productionResults.data.length > 0 ? productionResults.data.length > 0
: false : false
);
} }
}, [productionResults, isResponseSuccess]);
return (
<Card
className={{
wrapper: 'w-full',
body: 'p-4 shadow',
}}
> >
<Collapse
open={open}
onOpenChange={setOpen}
title={
<div className='card-actions p-4 justify-between items-center w-full'>
<div className='card-title'>{kandangName}</div>
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
>
<div className='w-full p-0'>
{/* <div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-start items-end sm:items-center gap-4'>
<DebouncedTextInput
name='search'
placeholder='Cari Record'
value={tableFilterState.search}
onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }}
/>
</div>
</div> */}
<Table<ProductionResult> <Table<ProductionResult>
data={ data={
isResponseSuccess(productionResults) isResponseSuccess(productionResults) ? productionResults?.data : []
? productionResults?.data
: []
} }
columns={productionResultColumns} columns={productionResultColumns}
pageSize={tableFilterState.pageSize} pageSize={tableFilterState.pageSize}
@@ -346,17 +307,19 @@ const ProductionResultProjectFlockKandangTable = ({
setSorting={setSorting} setSorting={setSorting}
renderFooter={false} renderFooter={false}
className={{ className={{
containerClassName: cn({ containerClassName: 'w-full mb-0!',
'w-full mb-20': tableWrapperClassName:
isResponseSuccess(productionResults) && 'overflow-x-auto rounded-tr-none rounded-tl-none',
productionResults?.data?.length === 0, tableClassName: 'w-full table-auto text-sm',
}), headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName: headerColumnClassName:
'px-4 py-3 border-x border-base-content/10 text-base-content/50', 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
/> />
</div>
</Collapse>
</Card> </Card>
); );
}; };
@@ -0,0 +1,39 @@
'use client';
import { useState } from 'react';
import Tabs from '@/components/Tabs';
import ProductionResultTab from '@/components/pages/report/production-result/tab/ProductionResultTab';
import { useReportTabStore } from '@/stores/report/report-tab.store';
const ProductionResultTabs = () => {
const [activeTabId, setActiveTabId] = useState<string>('1');
const tabActions = useReportTabStore((state) => state.tabActions);
const tabs = [
{
id: '1',
label: 'Hasil Produksi',
content: <ProductionResultTab tabId={'1'} />,
},
];
return (
<section className='w-full'>
<Tabs
tabs={tabs}
variant='boxed'
activeTabId={activeTabId}
onTabChange={setActiveTabId}
className={{
tabHeaderWrapper:
'justify-between items-center p-3 border-b border-base-content/10',
tab: 'w-fit',
content: 'p-0 m-0',
}}
sideContent={tabActions[activeTabId] || null}
/>
</section>
);
};
export default ProductionResultTabs;
@@ -1,48 +1,17 @@
import * as yup from 'yup'; import * as yup from 'yup';
export type ProductionResultFilterType = { export type ProductionResultFilterProps = {
area_id: string | null; area_id: string | null;
location_id: string | null; location_id: string | null;
project_flock_id: string | null; project_flock_id: string | null;
kandang_id: string | null; kandang_id: string | null;
date_start: string | null;
date_end: string | null;
sort_by: string | null;
show_unrecorded: boolean | null;
}; };
export const ProductionResultFilterSchema = yup.object({ export const ProductionResultFilterSchema = yup.object({
area_id: yup.string().nullable(), area_id: yup.string().required('Area wajib dipilih'),
location_id: yup.string().nullable(), location_id: yup.string().required('Lokasi wajib dipilih'),
project_flock_id: yup.string().nullable(), project_flock_id: yup.string().required('Project Flock wajib dipilih'),
kandang_id: yup.string().nullable(), kandang_id: yup.string().required('Kandang wajib dipilih'),
date_start: yup
.string()
.nullable()
.test(
'is-valid-date',
'Tanggal mulai tidak valid',
(value) => !value || !isNaN(Date.parse(value))
),
date_end: yup
.string()
.nullable()
.test(
'is-valid-date',
'Tanggal akhir tidak valid',
(value) => !value || !isNaN(Date.parse(value))
)
.test(
'is-after-start',
'Tanggal akhir tidak boleh lebih awal dari tanggal mulai',
function (value) {
const { date_start } = this.parent;
if (!date_start || !value) return true;
return new Date(value) >= new Date(date_start);
}
),
sort_by: yup.string().nullable(),
show_unrecorded: yup.boolean().nullable(),
}); });
export type ProductionResultFilterValues = yup.InferType< export type ProductionResultFilterValues = yup.InferType<
@@ -4,13 +4,26 @@ import Table from '@/components/Table';
import { ProductionResult } from '@/types/api/report/production-result'; import { ProductionResult } from '@/types/api/report/production-result';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
type ProductionResultColumn =
| ColumnDef<ProductionResult>
| {
header: string;
columns: Array<{
header: string;
accessorKey?: string;
cell?: (props: {
row: { original: ProductionResult };
}) => React.ReactNode;
}>;
};
const ProductionResultSkeleton = ({ const ProductionResultSkeleton = ({
columns, columns,
icon, icon,
title, title,
subtitle, subtitle,
}: { }: {
columns: ColumnDef<ProductionResult>[]; columns: ProductionResultColumn[];
icon: React.ReactNode; icon: React.ReactNode;
title: string; title: string;
subtitle: string; subtitle: string;
File diff suppressed because it is too large Load Diff