mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE): Refactor production result components and improve UI
This commit is contained in:
@@ -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 = () => {
|
||||
return (
|
||||
<section className='w-full max-w-full'>
|
||||
<ProductionResultContent />
|
||||
<ProductionResultTabs />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
+50
-87
@@ -4,12 +4,10 @@ import { useEffect, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { ColumnDef, SortingState } from '@tanstack/react-table';
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
import Table from '@/components/Table';
|
||||
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 { ProductionResult } from '@/types/api/report/production-result';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
@@ -52,8 +50,6 @@ const ProductionResultProjectFlockKandangTable = ({
|
||||
}
|
||||
);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const productionResultColumns: ColumnDef<ProductionResult>[] = [
|
||||
@@ -270,93 +266,60 @@ const ProductionResultProjectFlockKandangTable = ({
|
||||
}
|
||||
}, [sorting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setOpen(
|
||||
return (
|
||||
<Card
|
||||
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)
|
||||
? productionResults.data.length > 0
|
||||
: 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>
|
||||
<Table<ProductionResult>
|
||||
data={
|
||||
isResponseSuccess(productionResults) ? productionResults?.data : []
|
||||
}
|
||||
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>
|
||||
data={
|
||||
isResponseSuccess(productionResults)
|
||||
? productionResults?.data
|
||||
: []
|
||||
}
|
||||
columns={productionResultColumns}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
onPageSizeChange={setPageSize}
|
||||
rowOptions={[10, 20, 50, 100]}
|
||||
page={
|
||||
isResponseSuccess(productionResults)
|
||||
? productionResults?.meta?.page
|
||||
: 0
|
||||
}
|
||||
totalItems={
|
||||
isResponseSuccess(productionResults)
|
||||
? productionResults?.meta?.total_results
|
||||
: 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
isLoading={isLoadingProductionResults}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
renderFooter={false}
|
||||
className={{
|
||||
containerClassName: cn({
|
||||
'w-full mb-20':
|
||||
isResponseSuccess(productionResults) &&
|
||||
productionResults?.data?.length === 0,
|
||||
}),
|
||||
headerColumnClassName:
|
||||
'px-4 py-3 border-x border-base-content/10 text-base-content/50',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Collapse>
|
||||
columns={productionResultColumns}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
onPageSizeChange={setPageSize}
|
||||
rowOptions={[10, 20, 50, 100]}
|
||||
page={
|
||||
isResponseSuccess(productionResults)
|
||||
? productionResults?.meta?.page
|
||||
: 0
|
||||
}
|
||||
totalItems={
|
||||
isResponseSuccess(productionResults)
|
||||
? productionResults?.meta?.total_results
|
||||
: 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
isLoading={isLoadingProductionResults}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
renderFooter={false}
|
||||
className={{
|
||||
containerClassName: 'w-full mb-0!',
|
||||
tableWrapperClassName:
|
||||
'overflow-x-auto rounded-tr-none rounded-tl-none',
|
||||
tableClassName: 'w-full table-auto text-sm',
|
||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||
headerColumnClassName:
|
||||
'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',
|
||||
}}
|
||||
/>
|
||||
</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';
|
||||
|
||||
export type ProductionResultFilterType = {
|
||||
export type ProductionResultFilterProps = {
|
||||
area_id: string | null;
|
||||
location_id: string | null;
|
||||
project_flock_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({
|
||||
area_id: yup.string().nullable(),
|
||||
location_id: yup.string().nullable(),
|
||||
project_flock_id: yup.string().nullable(),
|
||||
kandang_id: yup.string().nullable(),
|
||||
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(),
|
||||
area_id: yup.string().required('Area wajib dipilih'),
|
||||
location_id: yup.string().required('Lokasi wajib dipilih'),
|
||||
project_flock_id: yup.string().required('Project Flock wajib dipilih'),
|
||||
kandang_id: yup.string().required('Kandang wajib dipilih'),
|
||||
});
|
||||
|
||||
export type ProductionResultFilterValues = yup.InferType<
|
||||
|
||||
+14
-1
@@ -4,13 +4,26 @@ import Table from '@/components/Table';
|
||||
import { ProductionResult } from '@/types/api/report/production-result';
|
||||
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 = ({
|
||||
columns,
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
}: {
|
||||
columns: ColumnDef<ProductionResult>[];
|
||||
columns: ProductionResultColumn[];
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user