mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 22:05:45 +00:00
feat(FE): API integration dashboard
This commit is contained in:
@@ -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' }}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user