mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-316): Add uniformity Excel template generator
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import { formatNumber, formatDate } from '@/lib/helper';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
||||||
|
|
||||||
|
export const generateUniformityTemplate = (
|
||||||
|
availableQuantity: number,
|
||||||
|
projectFlockKandangLookup: ProjectFlockKandangLookup
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// Calculate 2% sample from total population
|
||||||
|
const sampleSize = Math.round(availableQuantity * 0.02);
|
||||||
|
|
||||||
|
const data = Array.from({ length: sampleSize }, (_, index) => ({
|
||||||
|
NO: index + 1,
|
||||||
|
BW: '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(data, {
|
||||||
|
header: ['NO', 'BW'],
|
||||||
|
});
|
||||||
|
|
||||||
|
worksheet['!cols'] = [{ wch: 10 }, { wch: 15 }];
|
||||||
|
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
const kandangName = projectFlockKandangLookup.kandang?.name || 'Kandang';
|
||||||
|
const flockName = projectFlockKandangLookup.project_flock?.flock_name || '';
|
||||||
|
const flockPeriod = projectFlockKandangLookup.project_flock?.period || 1;
|
||||||
|
|
||||||
|
const sheetName =
|
||||||
|
kandangName.length > 31 ? kandangName.substring(0, 31) : kandangName;
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
||||||
|
|
||||||
|
const sanitizedFlockName = flockName
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
|
||||||
|
const sanitizedKandangName = kandangName
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
|
||||||
|
const filename = `${formatDate(
|
||||||
|
new Date(),
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
)}-${sanitizedFlockName}-${sanitizedKandangName}-periode-${flockPeriod}-${sampleSize}-data.xlsx`;
|
||||||
|
|
||||||
|
XLSX.writeFile(workbook, filename);
|
||||||
|
|
||||||
|
toast.success(
|
||||||
|
`Template berhasil dibuat dengan ${formatNumber(sampleSize)} baris data (2% dari ${formatNumber(availableQuantity)} populasi).`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating uniformity template:', error);
|
||||||
|
toast.error('Gagal membuat template Excel. Silakan coba lagi.');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -40,8 +40,9 @@ import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'
|
|||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import UniformityPreviewForm from '@/components/pages/uniformity/form/UniformityPreviewForm';
|
import UniformityPreviewForm from '@/components/pages/uniformity/form/UniformityPreviewForm';
|
||||||
import UniformityResultForm from '@/components/pages/uniformity/form/UniformityResultForm';
|
import UniformityResultForm from '@/components/pages/uniformity/form/UniformityResultForm';
|
||||||
|
import { generateUniformityTemplate } from '@/components/pages/uniformity/export/UniformityTemplate';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn, formatNumber } from '@/lib/helper';
|
||||||
|
|
||||||
interface UniformityFormProps {
|
interface UniformityFormProps {
|
||||||
formType?: 'add' | 'edit';
|
formType?: 'add' | 'edit';
|
||||||
@@ -352,20 +353,31 @@ const UniformityForm = ({
|
|||||||
|
|
||||||
formik.setFieldValue('file', file);
|
formik.setFieldValue('file', file);
|
||||||
},
|
},
|
||||||
[formik]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDateChange = useCallback(
|
const handleDateChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
formik.setFieldValue('date', e.target.value);
|
formik.setFieldValue('date', e.target.value);
|
||||||
},
|
},
|
||||||
[formik]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveFile = useCallback(() => {
|
const handleRemoveFile = useCallback(() => {
|
||||||
formik.setFieldValue('files', undefined);
|
formik.setFieldValue('files', undefined);
|
||||||
}, [formik]);
|
}, [formik]);
|
||||||
|
|
||||||
|
const handleDownloadTemplate = useCallback(() => {
|
||||||
|
const availableQuantity = projectFlockKandangLookup?.available_quantity;
|
||||||
|
|
||||||
|
if (!availableQuantity || !projectFlockKandangLookup) {
|
||||||
|
toast.error('Silakan pilih Project Flock dan Kandang terlebih dahulu.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateUniformityTemplate(availableQuantity, projectFlockKandangLookup);
|
||||||
|
}, [projectFlockKandangLookup]);
|
||||||
|
|
||||||
// ===== SIDE EFFECTS =====
|
// ===== SIDE EFFECTS =====
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formik.values.date) {
|
if (formik.values.date) {
|
||||||
@@ -576,36 +588,38 @@ const UniformityForm = ({
|
|||||||
</span>
|
</span>
|
||||||
<span className='text-xs font-light text-[#18181B80] text-center max-w-xs px-4'>
|
<span className='text-xs font-light text-[#18181B80] text-center max-w-xs px-4'>
|
||||||
{projectFlockKandangLookup?.available_quantity
|
{projectFlockKandangLookup?.available_quantity
|
||||||
? `Jumlah data yang dibutuhkan: ${projectFlockKandangLookup.available_quantity.toLocaleString('id-ID')} (2% dari total populasi).`
|
? `Jumlah data yang dibutuhkan: ${formatNumber(Math.round(projectFlockKandangLookup.available_quantity * 0.02))} (2% dari ${formatNumber(projectFlockKandangLookup.available_quantity)} populasi).`
|
||||||
: 'Upload data file (*.xlsx)'}
|
: 'Upload data file (*.xlsx)'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-center gap-2 py-4'>
|
{projectFlockKandangLookup?.available_quantity && (
|
||||||
<div className='h-px bg-[#18181B33] w-8'></div>
|
<>
|
||||||
<span className='text-[#18181B33] text-xs'>
|
<div className='flex items-center justify-center gap-2 py-4'>
|
||||||
Templates
|
<div className='h-px bg-[#18181B33] w-8'></div>
|
||||||
</span>
|
<span className='text-[#18181B33] text-xs'>
|
||||||
<div className='h-px bg-[#18181B33] w-8'></div>
|
Templates
|
||||||
</div>
|
</span>
|
||||||
|
<div className='h-px bg-[#18181B33] w-8'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-center mb-10'>
|
<div className='flex items-center justify-center mb-10'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
className='btn-sm rounded-2xl shadow-md border border-base-300'
|
className='btn-sm rounded-2xl shadow-md border border-base-300'
|
||||||
href='https://www.timestored.com/data/sample/chickweight.csv'
|
onClick={handleDownloadTemplate}
|
||||||
target='_blank'
|
>
|
||||||
onClick={(e) => e.stopPropagation()}
|
<Icon
|
||||||
>
|
icon='heroicons:arrow-down-tray'
|
||||||
<Icon
|
width={18}
|
||||||
icon='heroicons:arrow-down-tray'
|
height={18}
|
||||||
width={18}
|
/>
|
||||||
height={18}
|
Template XLSX
|
||||||
/>
|
</Button>
|
||||||
Template XLSX
|
</div>
|
||||||
</Button>
|
</>
|
||||||
</div>
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user