feat(FE): API integration dashboard

This commit is contained in:
randy-ar
2026-01-11 19:15:22 +07:00
parent c752cad057
commit 0da9f9d651
13 changed files with 95 additions and 4423 deletions
@@ -1,7 +1,6 @@
'use client';
import Button from '@/components/Button';
import Card from '@/components/Card';
import { Icon } from '@iconify/react';
import Modal, { useModal } from '@/components/Modal';
import DateInput from '@/components/input/DateInput';
@@ -50,7 +49,7 @@ const DashboardProduction = () => {
const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>(
'OVERVIEW'
);
const [endpointUrl, setEndpointUrl] = useState('/dashboard');
const [endpointUrl, setEndpointUrl] = useState('/dashboards');
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]);
const [formErrorList, setFormErrorList] = useState<string[]>([]);
@@ -84,8 +83,8 @@ const DashboardProduction = () => {
limit: 'limit',
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
});
const comparedByOptions = [
{ value: 'LOCATION', label: 'Farm' },
const comparisonTypeOptions = [
{ value: 'FARM', label: 'Farm' },
{ value: 'FLOCK', label: 'Flock' },
{ value: 'KANDANG', label: 'Kandang' },
];
@@ -99,7 +98,7 @@ const DashboardProduction = () => {
location: [] as OptionType[],
kandang: [] as OptionType[],
analysisMode: analysisMode,
comparedBy: '',
comparisonType: '',
lokasiIds: [],
flockIds: [],
kandangIds: [],
@@ -115,6 +114,7 @@ const DashboardProduction = () => {
location_ids: normalizeToArray(values.location),
flock_ids: normalizeToArray(values.flock),
kandang_ids: normalizeToArray(values.kandang),
comparison_type: values.comparisonType,
});
},
});
@@ -122,7 +122,7 @@ const DashboardProduction = () => {
const handleResetFilter = () => {
formik.resetForm();
setAnalysisMode('OVERVIEW');
setEndpointUrl('/dashboard');
setEndpointUrl('/dashboards');
};
const handleApplyFilter = (values: DashboardFilter) => {
@@ -140,8 +140,9 @@ const DashboardProduction = () => {
params.flock_ids = values.flock_ids.toString();
if (values.kandang_ids.length > 0)
params.kandang_ids = values.kandang_ids.toString();
if (values.comparison_type) params.comparison_type = values.comparison_type;
setEndpointUrl(`/dashboard?${new URLSearchParams(params).toString()}`);
setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`);
console.log(endpointUrl);
filterModal.closeModal();
refreshDashboardProductionData();
@@ -262,7 +263,13 @@ const DashboardProduction = () => {
data={dashboardProductionData}
/>
) : (
<DashboardLineChartSkeleton />
<DashboardLineChartSkeleton
meta={
isResponseSuccess(dashboardProductionResponse)
? (dashboardProductionResponse.meta as unknown as DashboardMeta)
: undefined
}
/>
)}
</section>
@@ -343,7 +350,7 @@ const DashboardProduction = () => {
formik.setFieldValue('location', []);
formik.setFieldValue('flock', []);
formik.setFieldValue('kandang', []);
formik.setFieldValue('comparedBy', '');
formik.setFieldValue('comparisonType', '');
setSelectedLocationIds([]);
}}
color='primary'
@@ -368,21 +375,21 @@ const DashboardProduction = () => {
<div className='px-4'>
<SelectInput
label='Compared By'
value={comparedByOptions.find(
(option) => option.value === formik.values.comparedBy
value={comparisonTypeOptions.find(
(option) => option.value === formik.values.comparisonType
)}
onChange={(selected) =>
formik.setFieldValue(
'comparedBy',
'comparisonType',
selected ? (selected as OptionType).value : ''
)
}
errorMessage={formik.errors.comparedBy as string}
options={comparedByOptions}
errorMessage={formik.errors.comparisonType as string}
options={comparisonTypeOptions}
isLoading={isLoadingLocationOptions}
isError={
Boolean(formik.errors.comparedBy) &&
Boolean(formik.touched.comparedBy)
Boolean(formik.errors.comparisonType) &&
Boolean(formik.touched.comparisonType)
}
/>
</div>
@@ -405,9 +412,9 @@ const DashboardProduction = () => {
options={locationOptions}
isLoading={isLoadingLocationOptions}
isMulti={
comparedByOptions.find(
(option) => option.value === formik.values.comparedBy
)?.value === 'LOCATION'
comparisonTypeOptions.find(
(option) => option.value === formik.values.comparisonType
)?.value === 'FARM'
}
isError={
Boolean(formik.errors.location) &&
@@ -420,8 +427,8 @@ const DashboardProduction = () => {
{!(
formik.values.analysisMode === 'COMPARISON' &&
!(
formik.values.comparedBy === 'FLOCK' ||
formik.values.comparedBy === 'KANDANG'
formik.values.comparisonType === 'FLOCK' ||
formik.values.comparisonType === 'KANDANG'
)
) && (
<div className='px-4'>
@@ -435,8 +442,8 @@ const DashboardProduction = () => {
options={flockOptions}
isLoading={isLoadingFlockOptions}
isMulti={
comparedByOptions.find(
(option) => option.value === formik.values.comparedBy
comparisonTypeOptions.find(
(option) => option.value === formik.values.comparisonType
)?.value === 'FLOCK'
}
isError={
@@ -450,7 +457,7 @@ const DashboardProduction = () => {
{/* Kandang */}
{!(
formik.values.analysisMode === 'COMPARISON' &&
!(formik.values.comparedBy === 'KANDANG')
!(formik.values.comparisonType === 'KANDANG')
) && (
<div className='px-4'>
<SelectInput
@@ -463,8 +470,8 @@ const DashboardProduction = () => {
options={kandangOptions}
isLoading={isLoadingKandangOptions}
isMulti={
comparedByOptions.find(
(option) => option.value === formik.values.comparedBy
comparisonTypeOptions.find(
(option) => option.value === formik.values.comparisonType
)?.value === 'KANDANG'
}
isError={
@@ -5,7 +5,7 @@ export type DashboardFilterType = {
startDate: string;
endDate: string;
analysisMode: string;
comparedBy: string | undefined;
comparisonType: string | undefined;
location: OptionType | OptionType[];
lokasiIds: number[] | undefined;
flock: OptionType | OptionType[] | undefined;
@@ -20,7 +20,7 @@ export const DashboardFilterOverviewSchema: yup.ObjectSchema<DashboardFilterType
startDate: yup.string().required('Start date is required'),
endDate: yup.string().required('End date is required'),
analysisMode: yup.string().required('Analysis mode is required'),
comparedBy: yup.string().when('analysisMode', {
comparisonType: yup.string().when('analysisMode', {
is: 'COMPARISON',
then: (schema) => schema.required('Compared by is required'),
otherwise: (schema) => schema.optional(),
@@ -63,7 +63,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema<DashboardFilterTy
startDate: yup.string().required('Start date is required'),
endDate: yup.string().required('End date is required'),
analysisMode: yup.string().required('Analysis mode is required'),
comparedBy: yup.string().when('analysisMode', {
comparisonType: yup.string().when('analysisMode', {
is: 'COMPARISON',
then: (schema) => schema.required('Compared by is required'),
otherwise: (schema) => schema.optional(),
@@ -80,7 +80,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema<DashboardFilterTy
}
return !!value;
}),
flock: yup.mixed<OptionType | OptionType[]>().when('comparedBy', {
flock: yup.mixed<OptionType | OptionType[]>().when('comparisonType', {
is: (value: string) => value === 'FLOCK' || value === 'KANDANG',
then: (schema) =>
schema.test('is-required', 'Flock is required', (value) => {
@@ -91,7 +91,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema<DashboardFilterTy
}),
otherwise: (schema) => schema.optional(),
}),
kandang: yup.mixed<OptionType | OptionType[]>().when('comparedBy', {
kandang: yup.mixed<OptionType | OptionType[]>().when('comparisonType', {
is: 'KANDANG',
then: (schema) =>
schema.test('is-required', 'Kandang is required', (value) => {
@@ -1,6 +1,7 @@
import { Icon } from '@iconify/react';
import { DashboardMeta } from '@/types/api/dashboard/dashboard';
const DashboardLineChartSkeleton = () => {
const DashboardLineChartSkeleton = ({ meta }: { meta?: DashboardMeta }) => {
return (
<div className='w-full bg-white rounded-lg shadow-sm border border-gray-200 p-6 relative'>
{/* Header with title skeleton */}
@@ -32,24 +33,49 @@ const DashboardLineChartSkeleton = () => {
<div className='flex-1 relative'>
{/* Empty state centered in chart area */}
<div className='absolute inset-0 flex flex-col items-center justify-center pb-12'>
{/* Filter icon */}
<div className='w-12 h-12 bg-blue-500 rounded-xl flex items-center justify-center mb-4'>
<Icon
icon='heroicons:funnel'
className='text-white'
width={24}
height={24}
/>
</div>
{!meta?.filters && (
<>
{/* Filter icon */}
<div className='w-12 h-12 bg-blue-500 rounded-xl flex items-center justify-center mb-4'>
<Icon
icon='heroicons:funnel'
className='text-white'
width={24}
height={24}
/>
</div>
{/* Empty state text */}
<h3 className='text-gray-900 font-semibold text-base mb-2'>
No Filters Selected
</h3>
<p className='text-gray-500 text-sm text-center max-w-xs'>
Please choose filters to narrow down your results and make your
search easier.
</p>
{/* Empty state text */}
<h3 className='text-gray-900 font-semibold text-base mb-2'>
No Filters Selected
</h3>
<p className='text-gray-500 text-sm text-center max-w-xs'>
Please choose filters to narrow down your results and make
your search easier.
</p>
</>
)}
{meta?.filters && (
<>
{/* Filter icon */}
<div className='w-12 h-12 bg-blue-500 rounded-xl flex items-center justify-center mb-4'>
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={24}
height={24}
/>
</div>
{/* Empty state text */}
<h3 className='text-gray-900 font-semibold text-base mb-2'>
Data Not Yet Available
</h3>
<p className='text-gray-500 text-sm text-center max-w-xs'>
Please change your filters to get the data.
</p>
</>
)}
</div>
{/* Placeholder for chart height */}
@@ -40,6 +40,7 @@ const DebtSupplierTab = () => {
const [filterSupplier, setFilterSupplier] = useState<OptionType[]>([]);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
const [filterDataType, setFilterDataType] = useState<OptionType>();
const [filterErrors, setFilterErrors] = useState<Record<string, string>>({});
const filterModal = useModal();
@@ -142,6 +143,7 @@ const DebtSupplierTab = () => {
filter_by: 'do_date' as const,
start_date: filterStartDate || undefined,
end_date: filterEndDate || undefined,
date_type: filterDataType ? filterDataType.value : undefined,
limit: 100,
page: 1,
};
@@ -556,6 +558,9 @@ const DebtSupplierTab = () => {
placeholder='Pilih Filter Berdasarkan'
options={dataTypeOptions}
value={dataTypeOptions[0]}
onChange={(val) => {
setFilterDataType(val ? (val as OptionType) : undefined);
}}
isDisabled={true}
className={{ wrapper: 'w-full' }}
/>