mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 07:15:44 +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 = () => {
|
const ProductionResultReportPage = () => {
|
||||||
return (
|
return (
|
||||||
<section className='w-full max-w-full'>
|
<section className='w-full max-w-full'>
|
||||||
<ProductionResultContent />
|
<ProductionResultTabs />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+50
-87
@@ -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,93 +266,60 @@ 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
|
<Table<ProductionResult>
|
||||||
open={open}
|
data={
|
||||||
onOpenChange={setOpen}
|
isResponseSuccess(productionResults) ? productionResults?.data : []
|
||||||
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!'
|
columns={productionResultColumns}
|
||||||
titleClassName='w-full p-0!'
|
pageSize={tableFilterState.pageSize}
|
||||||
>
|
onPageSizeChange={setPageSize}
|
||||||
<div className='w-full p-0'>
|
rowOptions={[10, 20, 50, 100]}
|
||||||
{/* <div className='flex flex-col gap-2 mb-4'>
|
page={
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-start items-end sm:items-center gap-4'>
|
isResponseSuccess(productionResults)
|
||||||
<DebouncedTextInput
|
? productionResults?.meta?.page
|
||||||
name='search'
|
: 0
|
||||||
placeholder='Cari Record'
|
}
|
||||||
value={tableFilterState.search}
|
totalItems={
|
||||||
onChange={searchChangeHandler}
|
isResponseSuccess(productionResults)
|
||||||
className={{ wrapper: 'sm:max-w-3xs' }}
|
? productionResults?.meta?.total_results
|
||||||
/>
|
: 0
|
||||||
</div>
|
}
|
||||||
</div> */}
|
onPageChange={setPage}
|
||||||
|
isLoading={isLoadingProductionResults}
|
||||||
<Table<ProductionResult>
|
sorting={sorting}
|
||||||
data={
|
setSorting={setSorting}
|
||||||
isResponseSuccess(productionResults)
|
renderFooter={false}
|
||||||
? productionResults?.data
|
className={{
|
||||||
: []
|
containerClassName: 'w-full mb-0!',
|
||||||
}
|
tableWrapperClassName:
|
||||||
columns={productionResultColumns}
|
'overflow-x-auto rounded-tr-none rounded-tl-none',
|
||||||
pageSize={tableFilterState.pageSize}
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
onPageSizeChange={setPageSize}
|
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||||
rowOptions={[10, 20, 50, 100]}
|
headerColumnClassName:
|
||||||
page={
|
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
|
||||||
isResponseSuccess(productionResults)
|
bodyRowClassName:
|
||||||
? productionResults?.meta?.page
|
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||||
: 0
|
bodyColumnClassName:
|
||||||
}
|
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||||
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>
|
|
||||||
</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<
|
||||||
|
|||||||
+14
-1
@@ -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
Reference in New Issue
Block a user