feat(FE): slicing ui dashboard, API integration with dummy data and form validation

This commit is contained in:
randy-ar
2026-01-10 08:09:29 +07:00
parent 777b06c690
commit 126346dc52
7 changed files with 574 additions and 387 deletions
@@ -20,13 +20,19 @@ import Alert from '@/components/Alert';
import {
DashboardFilterSchema,
DashboardFilterType,
getDashboardFilterSchema,
} from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
import DashboardLineChart from '@/components/pages/dashboard/chart/DashboardLineChart';
import DashboardLineChartSkeleton from '@/components/pages/dashboard/skeleton/DashboardLineChartSkeleton';
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
import { DashboardFilter } from '@/types/api/dashboard/dashboard';
import {
DashboardFilter,
DashboardMeta,
} from '@/types/api/dashboard/dashboard';
import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats';
import { isResponseSuccess } from '@/lib/api-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
// Helper function to normalize values to array
const normalizeToArray = (
@@ -46,6 +52,7 @@ const DashboardProduction = () => {
);
const [endpointUrl, setEndpointUrl] = useState('/dashboard');
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]);
const [formErrorList, setFormErrorList] = useState<string[]>([]);
// ===== FETCH DATA =====
const {
@@ -78,7 +85,7 @@ const DashboardProduction = () => {
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
});
const comparedByOptions = [
{ value: 'LOCATION', label: 'Location' },
{ value: 'LOCATION', label: 'Farm' },
{ value: 'FLOCK', label: 'Flock' },
{ value: 'KANDANG', label: 'Kandang' },
];
@@ -97,7 +104,7 @@ const DashboardProduction = () => {
flockIds: [],
kandangIds: [],
} as DashboardFilterType,
validationSchema: DashboardFilterSchema,
validationSchema: getDashboardFilterSchema(analysisMode),
onSubmit: (values) => {
console.log(values);
@@ -117,6 +124,7 @@ const DashboardProduction = () => {
setAnalysisMode('OVERVIEW');
setEndpointUrl('/dashboard');
};
const handleApplyFilter = (values: DashboardFilter) => {
console.log(values);
@@ -140,6 +148,23 @@ const DashboardProduction = () => {
formik.resetForm();
};
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
// Parse and display errors
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return; // Stop submission
}
};
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleValidateForm();
formik.handleSubmit();
};
if (isLoadingDashboardProductionData) {
return (
<div className='w-full min-h-screen flex items-center justify-center'>
@@ -155,11 +180,53 @@ const DashboardProduction = () => {
<div className='flex flex-row justify-end gap-2'>
<Button
variant='outline'
className='min-w-28 rounded-lg'
className={`min-w-28 rounded-lg ${
isResponseSuccess(dashboardProductionResponse) &&
(dashboardProductionResponse.meta as unknown as DashboardMeta)
.filters
? 'bg-gradient-to-r from-blue-50 to-blue-100 border-blue-500 text-blue-600 hover:from-blue-100 hover:to-blue-200'
: ''
}`}
onClick={() => filterModal.openModal()}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
<Icon
icon='heroicons:funnel'
width={20}
height={20}
className={
isResponseSuccess(dashboardProductionResponse) &&
(dashboardProductionResponse.meta as unknown as DashboardMeta)
.filters
? 'text-blue-600'
: ''
}
/>
Filter
{isResponseSuccess(dashboardProductionResponse) &&
dashboardProductionResponse.meta &&
(dashboardProductionResponse.meta as unknown as DashboardMeta)
.filters && (
<span className='w-6 h-6 text-white bg-red-500 rounded-lg flex items-center justify-center text-xs'>
{(() => {
const meta =
dashboardProductionResponse.meta as unknown as DashboardMeta;
if (!meta.filters) return 0;
const count =
(meta.filters.location_ids.length > 1
? meta.filters.location_ids.length
: 0) +
(meta.filters.flock_ids.length > 1
? meta.filters.flock_ids.length
: 0) +
(meta.filters.kandang_ids.length > 1
? meta.filters.kandang_ids.length
: 0);
return meta.filters.analysis_mode === 'OVERVIEW'
? 1
: count;
})()}
</span>
)}
</Button>
<Button
variant='outline'
@@ -183,7 +250,15 @@ const DashboardProduction = () => {
dashboardProductionData.charts &&
Object.keys(dashboardProductionData.charts).length > 0 ? (
<DashboardLineChart
analysisMode={analysisMode}
analysisMode={
isResponseSuccess(dashboardProductionResponse)
? dashboardProductionResponse.meta
? (
dashboardProductionResponse.meta as unknown as DashboardMeta
).filters?.analysis_mode
: analysisMode
: analysisMode
}
data={dashboardProductionData}
/>
) : (
@@ -214,7 +289,11 @@ const DashboardProduction = () => {
</Button>
</div>
<form className='space-y-4' onSubmit={formik.handleSubmit}>
<form
className='space-y-4'
onSubmit={handleFormSubmit}
onReset={handleResetFilter}
>
{/* Rentang Waktu */}
<div className='px-4'>
<label className='flex items-center gap-2 mb-3'>Tanggal</label>
@@ -259,6 +338,7 @@ const DashboardProduction = () => {
value={formik.values.analysisMode}
onChange={(e) => {
formik.handleChange(e);
setAnalysisMode(e.target.value as 'OVERVIEW' | 'COMPARISON');
// Reset all dependent fields when analysis mode changes
formik.setFieldValue('location', []);
formik.setFieldValue('flock', []);
@@ -395,6 +475,14 @@ const DashboardProduction = () => {
</div>
)}
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
{/* Action Buttons */}
<div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'>
<Button