mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 06:45:46 +00:00
Merge remote-tracking branch 'origin/development' into staging
This commit is contained in:
@@ -96,11 +96,6 @@ const ClosingProductionDataTabContent = ({
|
|||||||
value={formatNumber(purchase.feed_used)}
|
value={formatNumber(purchase.feed_used)}
|
||||||
unit='Kg'
|
unit='Kg'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
|
||||||
label='Pakan Terpakai per Ekor'
|
|
||||||
value={formatNumber(purchase.feed_used_per_head)}
|
|
||||||
unit='Kg'
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -124,14 +119,12 @@ const ClosingProductionDataTabContent = ({
|
|||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Bobot Rata-Rata'
|
label='Bobot Rata-Rata'
|
||||||
value={formatNumber(sales.chicken.average_weight)}
|
value={formatNumber(sales.chicken.avg_weight)}
|
||||||
unit='Kg/Ekor'
|
unit='Kg/Ekor'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Harga Jual Rata-Rata'
|
label='Harga Jual Rata-Rata'
|
||||||
value={formatNumber(
|
value={formatNumber(sales.chicken.avg_selling_price)}
|
||||||
sales.chicken.chicken_average_selling_price
|
|
||||||
)}
|
|
||||||
unit='Rupiah'
|
unit='Rupiah'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,17 +141,17 @@ const ClosingProductionDataTabContent = ({
|
|||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Telur (Kg)'
|
label='Telur (Kg)'
|
||||||
value={formatNumber(sales.egg.egg_mass_kg)}
|
value={formatNumber(sales.egg.egg_mass)}
|
||||||
unit='Kg'
|
unit='Kg'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Berat Telur Rata-Rata'
|
label='Berat Telur Rata-Rata'
|
||||||
value={formatNumber(sales.egg.average_egg_weight_kg)}
|
value={formatNumber(sales.egg.avg_egg_weight)}
|
||||||
unit='Kg'
|
unit='Kg'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Harga Jual Telur Rata-Rata'
|
label='Harga Jual Telur Rata-Rata'
|
||||||
value={formatNumber(sales.egg.egg_average_selling_price)}
|
value={formatNumber(sales.egg.avg_selling_price)}
|
||||||
unit='Rupiah'
|
unit='Rupiah'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,17 +184,37 @@ const ClosingProductionDataTabContent = ({
|
|||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Mortalitas Std'
|
label='Mortalitas Std'
|
||||||
value={formatNumber(performance.mortality_std)}
|
value={formatNumber(performance.mor_std)}
|
||||||
unitClassName='hidden'
|
unitClassName='hidden'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='Mortalitas Act'
|
label='Mortalitas Act'
|
||||||
value={formatNumber(performance.mortality_act)}
|
value={formatNumber(performance.mor_act)}
|
||||||
unitClassName='hidden'
|
unitClassName='hidden'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='DEFF Mortalitas'
|
label='DEFF Mortalitas'
|
||||||
value={formatNumber(performance.deff_mortality)}
|
value={formatNumber(performance.mor_diff)}
|
||||||
|
unitClassName='hidden'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='AWG Std'
|
||||||
|
value={formatNumber(performance.awg_std)}
|
||||||
|
unit='Gr/Hari'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='AWG Act'
|
||||||
|
value={formatNumber(performance.awg_act)}
|
||||||
|
unit='Gr/Hari'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Feed Intake Std'
|
||||||
|
value={formatNumber(performance.feed_intake_std)}
|
||||||
|
unitClassName='hidden'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Feed Intake Act'
|
||||||
|
value={formatNumber(performance.feed_intake)}
|
||||||
unitClassName='hidden'
|
unitClassName='hidden'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
@@ -216,14 +229,70 @@ const ClosingProductionDataTabContent = ({
|
|||||||
/>
|
/>
|
||||||
<DataRow
|
<DataRow
|
||||||
label='DEFF FCR'
|
label='DEFF FCR'
|
||||||
value={formatNumber(performance.deff_fcr)}
|
value={formatNumber(performance.fcr_diff)}
|
||||||
unitClassName='hidden'
|
unitClassName='hidden'
|
||||||
/>
|
/>
|
||||||
<DataRow
|
|
||||||
label='AWG'
|
{/* Laying Specific Fields */}
|
||||||
value={formatNumber(performance.awg)}
|
{performance.hen_day_act !== undefined && (
|
||||||
unit='Gr/Hari'
|
<>
|
||||||
/>
|
<DataRow
|
||||||
|
label='Hen Day Std'
|
||||||
|
value={formatNumber(performance.hen_day_std!)}
|
||||||
|
unit='%'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Hen Day Act'
|
||||||
|
value={formatNumber(performance.hen_day_act)}
|
||||||
|
unit='%'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{performance.egg_mass !== undefined && (
|
||||||
|
<>
|
||||||
|
<DataRow
|
||||||
|
label='Egg Mass Std'
|
||||||
|
value={formatNumber(performance.egg_mass_std!)}
|
||||||
|
unit='Kg'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Egg Mass Act'
|
||||||
|
value={formatNumber(performance.egg_mass)}
|
||||||
|
unit='Kg'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{performance.egg_weight !== undefined && (
|
||||||
|
<>
|
||||||
|
<DataRow
|
||||||
|
label='Egg Weight Std'
|
||||||
|
value={formatNumber(performance.egg_weight_std!)}
|
||||||
|
unit='Gr'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Egg Weight Act'
|
||||||
|
value={formatNumber(performance.egg_weight)}
|
||||||
|
unit='Gr'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{performance.hen_housed_act !== undefined && (
|
||||||
|
<>
|
||||||
|
<DataRow
|
||||||
|
label='Hen Housed Std'
|
||||||
|
value={formatNumber(performance.hen_housed_std!)}
|
||||||
|
unit='%'
|
||||||
|
/>
|
||||||
|
<DataRow
|
||||||
|
label='Hen Housed Act'
|
||||||
|
value={formatNumber(performance.hen_housed_act)}
|
||||||
|
unit='%'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Table from '@/components/Table';
|
|||||||
import {
|
import {
|
||||||
FINANCE_INITIAL_BALANCE_STATUS,
|
FINANCE_INITIAL_BALANCE_STATUS,
|
||||||
FINANCE_TRANSACTION_STATUS,
|
FINANCE_TRANSACTION_STATUS,
|
||||||
|
FINANCE_INJECTION_STATUS,
|
||||||
} from '@/config/constant';
|
} from '@/config/constant';
|
||||||
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
import { FinanceApi } from '@/services/api/finance';
|
import { FinanceApi } from '@/services/api/finance';
|
||||||
@@ -33,7 +34,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Pihak',
|
label: 'Pihak',
|
||||||
value: finance.party.name,
|
value: finance.party.id ? finance.party.name : '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Tanggal',
|
label: 'Tanggal',
|
||||||
@@ -51,7 +52,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
|||||||
const informasiTransfer = [
|
const informasiTransfer = [
|
||||||
{
|
{
|
||||||
label: 'No. Referensi',
|
label: 'No. Referensi',
|
||||||
value: finance.reference_number,
|
value: finance.reference_number ?? '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Nomor Rekening',
|
label: 'Nomor Rekening',
|
||||||
@@ -69,7 +70,16 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
|||||||
label: 'Sisa',
|
label: 'Sisa',
|
||||||
value: formatCurrency(finance.income_amount),
|
value: formatCurrency(finance.income_amount),
|
||||||
},
|
},
|
||||||
];
|
].filter((item) => {
|
||||||
|
// Hide party account number row if transaction type is INJECTION
|
||||||
|
if (
|
||||||
|
FINANCE_INJECTION_STATUS.includes(finance.transaction_type) &&
|
||||||
|
item.label === `Rekening ${formatTitleCase(finance.party.type)}`
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
@@ -162,7 +172,19 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
<RequirePermission permissions='lti.finance.transaction.delete'>
|
{FINANCE_INJECTION_STATUS.includes(finance.transaction_type) && (
|
||||||
|
<RequirePermission permissions='lti.finance.injections.update'>
|
||||||
|
<Button
|
||||||
|
color='warning'
|
||||||
|
className='min-w-24'
|
||||||
|
href={`/finance/detail/edit/injection?financeId=${finance.id}`}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:pencil-outline' />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
<RequirePermission permissions='lti.finance.transactions.delete'>
|
||||||
<Button
|
<Button
|
||||||
color='error'
|
color='error'
|
||||||
className='min-w-24'
|
className='min-w-24'
|
||||||
|
|||||||
@@ -49,7 +49,14 @@ const RowOptionsMenu = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<RequirePermission permissions='lti.finance.transaction.detail'>
|
<RequirePermission
|
||||||
|
permissions={[
|
||||||
|
'lti.finance.transactions.detail',
|
||||||
|
'lti.finance.initial_balances.detail',
|
||||||
|
'lti.finance.injections.detail',
|
||||||
|
'lti.finance.payments.detail',
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
href={`/finance/detail?financeId=${props.row.original.id}`}
|
href={`/finance/detail?financeId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -109,7 +116,7 @@ const RowOptionsMenu = ({
|
|||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<RequirePermission permissions='lti.finance.transaction.delete'>
|
<RequirePermission permissions='lti.finance.transactions.delete'>
|
||||||
<Button
|
<Button
|
||||||
onClick={deleteClickHandler}
|
onClick={deleteClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { Movement } from '@/types/api/inventory/movement';
|
import { Movement, MovementDocument } from '@/types/api/inventory/movement';
|
||||||
|
|
||||||
type MovementFormSchemaType = {
|
type MovementFormSchemaType = {
|
||||||
transfer_reason: string;
|
transfer_reason: string;
|
||||||
@@ -29,7 +29,7 @@ type MovementFormSchemaType = {
|
|||||||
deliveries: {
|
deliveries: {
|
||||||
delivery_cost?: number | string;
|
delivery_cost?: number | string;
|
||||||
delivery_cost_per_item?: number | string;
|
delivery_cost_per_item?: number | string;
|
||||||
document?: File | string | null;
|
document?: File | MovementDocument | null;
|
||||||
document_path?: string | null;
|
document_path?: string | null;
|
||||||
driver_name: string;
|
driver_name: string;
|
||||||
vehicle_plate: string;
|
vehicle_plate: string;
|
||||||
@@ -61,7 +61,7 @@ export type ProductSchema = {
|
|||||||
export type DeliverySchema = {
|
export type DeliverySchema = {
|
||||||
delivery_cost?: number | string;
|
delivery_cost?: number | string;
|
||||||
delivery_cost_per_item?: number | string;
|
delivery_cost_per_item?: number | string;
|
||||||
document?: File | string | null;
|
document?: File | MovementDocument | null;
|
||||||
document_path?: string | null;
|
document_path?: string | null;
|
||||||
driver_name: string;
|
driver_name: string;
|
||||||
vehicle_plate: string;
|
vehicle_plate: string;
|
||||||
@@ -129,13 +129,12 @@ const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({
|
|||||||
}),
|
}),
|
||||||
document_path: Yup.string().optional(),
|
document_path: Yup.string().optional(),
|
||||||
document_index: Yup.number().optional(),
|
document_index: Yup.number().optional(),
|
||||||
document: Yup.mixed<File | string>()
|
document: Yup.mixed<File | MovementDocument>()
|
||||||
.nullable()
|
.nullable()
|
||||||
.test('fileSize', 'Ukuran dokumen maksimal 2 MB', (value) => {
|
.test('fileSize', 'Ukuran dokumen maksimal 2 MB', (value) => {
|
||||||
if (!value) return true;
|
if (!value) return true;
|
||||||
if (typeof value === 'string') return true;
|
|
||||||
if (value instanceof File) return value.size <= 2 * 1024 * 1024;
|
if (value instanceof File) return value.size <= 2 * 1024 * 1024;
|
||||||
return false;
|
return true;
|
||||||
}),
|
}),
|
||||||
driver_name: Yup.string().required('Nama sopir wajib diisi!'),
|
driver_name: Yup.string().required('Nama sopir wajib diisi!'),
|
||||||
vehicle_plate: Yup.string().required('Plat nomor wajib diisi!'),
|
vehicle_plate: Yup.string().required('Plat nomor wajib diisi!'),
|
||||||
@@ -241,7 +240,7 @@ export const getMovementFormInitialValues = (
|
|||||||
delivery_cost: d.shipping_cost_total ?? undefined,
|
delivery_cost: d.shipping_cost_total ?? undefined,
|
||||||
delivery_cost_per_item: d.shipping_cost_item ?? undefined,
|
delivery_cost_per_item: d.shipping_cost_item ?? undefined,
|
||||||
document_number: d.document_number ?? '',
|
document_number: d.document_number ?? '',
|
||||||
document: d.document_path ?? null,
|
document: d.document ?? null,
|
||||||
document_path: d.document_path ?? null,
|
document_path: d.document_path ?? null,
|
||||||
driver_name: d.driver_name ?? '',
|
driver_name: d.driver_name ?? '',
|
||||||
vehicle_plate: d.vehicle_plate ?? '',
|
vehicle_plate: d.vehicle_plate ?? '',
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import FileInput from '@/components/input/FileInput';
|
|||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
import { S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||||
|
|
||||||
interface MovementFormProps {
|
interface MovementFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -55,16 +56,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
|
|
||||||
// ===== FORM HANDLERS =====
|
// ===== FORM HANDLERS =====
|
||||||
const createMovementHandler = useCallback(
|
const createMovementHandler = useCallback(
|
||||||
async (payload: CreateMovementPayload, documents: File[] = []) => {
|
async (payload: CreateMovementPayload) => {
|
||||||
const formData = new FormData();
|
const res = await MovementApi.createMovement(payload);
|
||||||
formData.append('data', JSON.stringify(payload));
|
|
||||||
documents.forEach((file, index) => {
|
|
||||||
formData.append(`documents[${index}]`, file);
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await MovementApi.create(
|
|
||||||
formData as unknown as CreateMovementPayload
|
|
||||||
);
|
|
||||||
if (isResponseError(res)) {
|
if (isResponseError(res)) {
|
||||||
setMovementFormErrorMessage(res.message);
|
setMovementFormErrorMessage(res.message);
|
||||||
return;
|
return;
|
||||||
@@ -218,20 +211,23 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const payload: CreateMovementPayload = {
|
const payload: CreateMovementPayload = {
|
||||||
transfer_reason: values.transfer_reason,
|
data: {
|
||||||
transfer_date: values.transfer_date,
|
transfer_reason: values.transfer_reason,
|
||||||
source_warehouse_id: values.source_warehouse_id,
|
transfer_date: values.transfer_date,
|
||||||
destination_warehouse_id: values.destination_warehouse_id,
|
source_warehouse_id: values.source_warehouse_id,
|
||||||
products: values.products.map((p) => ({
|
destination_warehouse_id: values.destination_warehouse_id,
|
||||||
product_id: p.product_id,
|
products: values.products.map((p) => ({
|
||||||
product_qty: parseInt(p.product_qty.toString()) || 0,
|
product_id: p.product_id,
|
||||||
})),
|
product_qty: parseInt(p.product_qty.toString()) || 0,
|
||||||
deliveries: deliveriesPayload,
|
})),
|
||||||
|
deliveries: deliveriesPayload,
|
||||||
|
},
|
||||||
|
documents: documents.length > 0 ? documents : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'add':
|
case 'add':
|
||||||
await createMovementHandler(payload, documents);
|
await createMovementHandler(payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1537,27 +1533,51 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
{type === 'detail' ? (
|
{type === 'detail' ? (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-col items-start gap-2'>
|
<div className='flex flex-col items-start gap-2'>
|
||||||
<Button
|
{delivery.document_path ? (
|
||||||
color='primary'
|
<Button
|
||||||
className='w-full min-w-52 flex items-center justify-center gap-2'
|
color='primary'
|
||||||
disabled={!delivery.document_path}
|
className='w-full min-w-52 flex items-center justify-center gap-2'
|
||||||
href={delivery.document_path ?? undefined}
|
href={`${S3_PUBLIC_BASE_URL}/${delivery.document_path.startsWith('/') ? delivery.document_path.slice(1) : delivery.document_path}`}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
{delivery.document_path ? (
|
<Icon
|
||||||
<>
|
icon='material-symbols:file-open-outline'
|
||||||
<Icon
|
width={20}
|
||||||
icon='material-symbols:file-open-outline'
|
height={20}
|
||||||
width={20}
|
/>
|
||||||
height={20}
|
Lihat Dokumen
|
||||||
/>
|
</Button>
|
||||||
Lihat Dokumen
|
) : delivery.document &&
|
||||||
</>
|
delivery.document instanceof File === false ? (
|
||||||
) : (
|
<Button
|
||||||
'-'
|
color='primary'
|
||||||
)}
|
className='w-full min-w-52 flex items-center justify-center gap-2'
|
||||||
</Button>
|
href={`${S3_PUBLIC_BASE_URL}/${delivery.document.path.startsWith('/') ? delivery.document.path.slice(1) : delivery.document.path}`}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:file-open-outline'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
{delivery.document.name}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
color='neutral'
|
||||||
|
className='w-full min-w-52 flex items-center justify-center gap-2 cursor-not-allowed'
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:description'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
Tidak ada dokumen
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -77,10 +77,6 @@ const MarketingDetail = ({
|
|||||||
confirmationModal.openModal();
|
confirmationModal.openModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deliveryClickHandler = () => {
|
|
||||||
deliveryModal.openModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteClickHandler = () => {
|
const deleteClickHandler = () => {
|
||||||
deleteModal.openModal();
|
deleteModal.openModal();
|
||||||
};
|
};
|
||||||
@@ -135,7 +131,7 @@ const MarketingDetail = ({
|
|||||||
<div className='flex-row flex gap-3'>
|
<div className='flex-row flex gap-3'>
|
||||||
{initialValues?.latest_approval?.step_number == 1 && (
|
{initialValues?.latest_approval?.step_number == 1 && (
|
||||||
<>
|
<>
|
||||||
{/* <RequirePermission permissions='lti.marketing.sales_order.approve'>
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
<Button
|
<Button
|
||||||
color='success'
|
color='success'
|
||||||
onClick={approveClickHandler}
|
onClick={approveClickHandler}
|
||||||
@@ -147,20 +143,9 @@ const MarketingDetail = ({
|
|||||||
<Icon icon='mdi:check' width={24} height={24} />
|
<Icon icon='mdi:check' width={24} height={24} />
|
||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
color='success'
|
|
||||||
onClick={approveClickHandler}
|
|
||||||
disabled={
|
|
||||||
initialValues?.latest_approval?.step_number == 1 &&
|
|
||||||
initialValues?.latest_approval?.action == 'REJECTED'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:check' width={24} height={24} />
|
|
||||||
Approve
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{/* <RequirePermission permissions='lti.marketing.sales_order.approve'>
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
<Button
|
<Button
|
||||||
color='error'
|
color='error'
|
||||||
onClick={rejectClickHandler}
|
onClick={rejectClickHandler}
|
||||||
@@ -172,23 +157,12 @@ const MarketingDetail = ({
|
|||||||
<Icon icon='mdi:close' width={24} height={24} />
|
<Icon icon='mdi:close' width={24} height={24} />
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
color='error'
|
|
||||||
onClick={rejectClickHandler}
|
|
||||||
disabled={
|
|
||||||
initialValues?.latest_approval?.step_number == 1 &&
|
|
||||||
initialValues?.latest_approval?.action == 'REJECTED'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:close' width={24} height={24} />
|
|
||||||
Reject
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{initialValues?.latest_approval?.step_number != 1 && (
|
{initialValues?.latest_approval?.step_number != 1 && (
|
||||||
<>
|
<>
|
||||||
{/* <RequirePermission
|
<RequirePermission
|
||||||
permissions={
|
permissions={
|
||||||
initialValues?.latest_approval?.step_number == 3
|
initialValues?.latest_approval?.step_number == 3
|
||||||
? 'lti.marketing.delivery_order.update'
|
? 'lti.marketing.delivery_order.update'
|
||||||
@@ -209,21 +183,7 @@ const MarketingDetail = ({
|
|||||||
: 'Tambah '}
|
: 'Tambah '}
|
||||||
Delivery Order
|
Delivery Order
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
color='success'
|
|
||||||
href={
|
|
||||||
initialValues?.latest_approval?.step_number == 3
|
|
||||||
? `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}`
|
|
||||||
: `/marketing/add/delivery-orders?marketingId=${initialValues?.id}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:truck' width={24} height={24} />
|
|
||||||
{initialValues?.latest_approval?.step_number == 3
|
|
||||||
? 'Edit '
|
|
||||||
: 'Tambah '}
|
|
||||||
Delivery Order
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -466,7 +426,7 @@ const MarketingDetail = ({
|
|||||||
<div className='flex flex-row gap-3'>
|
<div className='flex flex-row gap-3'>
|
||||||
{initialValues?.latest_approval?.step_number != 3 && (
|
{initialValues?.latest_approval?.step_number != 3 && (
|
||||||
<>
|
<>
|
||||||
{/* <RequirePermission permissions='lti.marketing.sales_order.update'>
|
<RequirePermission permissions='lti.marketing.sales_order.update'>
|
||||||
<Button
|
<Button
|
||||||
color='warning'
|
color='warning'
|
||||||
type='button'
|
type='button'
|
||||||
@@ -475,27 +435,15 @@ const MarketingDetail = ({
|
|||||||
<Icon icon='mdi:pencil' width={24} height={24} />
|
<Icon icon='mdi:pencil' width={24} height={24} />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
color='warning'
|
|
||||||
type='button'
|
|
||||||
href={`/marketing/detail/${initialValues?.latest_approval?.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:pencil' width={24} height={24} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* <RequirePermission permissions='lti.marketing.sales_order.delete'>
|
<RequirePermission permissions='lti.marketing.sales_order.delete'>
|
||||||
<Button color='error' onClick={deleteClickHandler}>
|
<Button color='error' onClick={deleteClickHandler}>
|
||||||
<Icon icon='mdi:delete' width={24} height={24} />
|
<Icon icon='mdi:delete' width={24} height={24} />
|
||||||
Hapus
|
Hapus
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button color='error' onClick={deleteClickHandler}>
|
|
||||||
<Icon icon='mdi:delete' width={24} height={24} />
|
|
||||||
Hapus
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
|||||||
@@ -635,12 +635,6 @@ const MarketingForm = ({
|
|||||||
wrapper: 'bg-white w-full',
|
wrapper: 'bg-white w-full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <div className='text-blue-500'>
|
|
||||||
{JSON.stringify(formik.values)}
|
|
||||||
</div>
|
|
||||||
<div className='text-red-500'>
|
|
||||||
{JSON.stringify(formik.errors)}
|
|
||||||
</div> */}
|
|
||||||
<MemoizedDeliveryOrderProductTable
|
<MemoizedDeliveryOrderProductTable
|
||||||
formType={formType}
|
formType={formType}
|
||||||
data={deliveryOrderValues}
|
data={deliveryOrderValues}
|
||||||
@@ -690,7 +684,7 @@ const MarketingForm = ({
|
|||||||
{/* Actions button */}
|
{/* Actions button */}
|
||||||
{formType == 'edit' && (
|
{formType == 'edit' && (
|
||||||
<div className='flex flex-row justify-start'>
|
<div className='flex flex-row justify-start'>
|
||||||
{/* <RequirePermission permissions='lti.marketing.sales_order.delete'>
|
<RequirePermission permissions='lti.marketing.sales_order.delete'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
color='error'
|
color='error'
|
||||||
@@ -700,16 +694,7 @@ const MarketingForm = ({
|
|||||||
<Icon icon='mdi:trash' width={24} height={24} />
|
<Icon icon='mdi:trash' width={24} height={24} />
|
||||||
Hapus
|
Hapus
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission> */}
|
</RequirePermission>
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
color='error'
|
|
||||||
onClick={handleDelete}
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:trash' width={24} height={24} />
|
|
||||||
Hapus
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SelectInput, {
|
|||||||
useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { KandangApi, WarehouseApi } from '@/services/api/master-data';
|
import { WarehouseApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
@@ -180,9 +180,6 @@ const SalesOrderProductForm = ({
|
|||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* <small className='block text-rose-500'>
|
|
||||||
{JSON.stringify(formik.errors)}
|
|
||||||
</small> */}
|
|
||||||
<div className='grid sm:grid-cols-2 gap-4 z-200'>
|
<div className='grid sm:grid-cols-2 gap-4 z-200'>
|
||||||
<PatternInput
|
<PatternInput
|
||||||
name='vehicle_number'
|
name='vehicle_number'
|
||||||
|
|||||||
@@ -32,38 +32,6 @@ const DeliveryOrderProductTable = ({
|
|||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
const cols = [
|
const cols = [
|
||||||
// {
|
|
||||||
// id: 'select',
|
|
||||||
// header: ({
|
|
||||||
// table,
|
|
||||||
// }: {
|
|
||||||
// table: TanStack.Table<DeliveryOrderProductFormValues>;
|
|
||||||
// }) => (
|
|
||||||
// <div className='w-full flex flex-row justify-center'>
|
|
||||||
// <CheckboxInput
|
|
||||||
// name='allRow'
|
|
||||||
// checked={table.getIsAllRowsSelected()}
|
|
||||||
// indeterminate={table.getIsSomeRowsSelected()}
|
|
||||||
// onChange={table.getToggleAllRowsSelectedHandler()}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// ),
|
|
||||||
// cell: ({
|
|
||||||
// row,
|
|
||||||
// }: {
|
|
||||||
// row: TanStack.Row<DeliveryOrderProductFormValues>;
|
|
||||||
// }) => (
|
|
||||||
// <div>
|
|
||||||
// <CheckboxInput
|
|
||||||
// name='row'
|
|
||||||
// checked={row.getIsSelected()}
|
|
||||||
// disabled={!row.getCanSelect()}
|
|
||||||
// indeterminate={row.getIsSomeSelected()}
|
|
||||||
// onChange={row.getToggleSelectedHandler()}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
|
accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
|
||||||
header: 'No. Pengiriman',
|
header: 'No. Pengiriman',
|
||||||
@@ -188,18 +156,6 @@ const DeliveryOrderProductTable = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!props.row.original.qty && '-'}
|
{!props.row.original.qty && '-'}
|
||||||
{/* {formType == 'add_deliver' && (
|
|
||||||
<Button
|
|
||||||
color='error'
|
|
||||||
className='p-1'
|
|
||||||
onClick={() =>
|
|
||||||
onDeleteRef.current(props.row.original.id as number)
|
|
||||||
}
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:trash' width={16} height={16} />
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -248,22 +204,6 @@ const DeliveryOrderProductTable = ({
|
|||||||
<Icon icon='mdi:plus' width={16} height={16} />
|
<Icon icon='mdi:plus' width={16} height={16} />
|
||||||
Tambah Pengiriman
|
Tambah Pengiriman
|
||||||
</Button>
|
</Button>
|
||||||
{/* {selectedRowIds.length > 0 && (
|
|
||||||
<Button
|
|
||||||
type='button'
|
|
||||||
variant='outline'
|
|
||||||
color='error'
|
|
||||||
className='justify-start w-fit py-1 text-sm'
|
|
||||||
onClick={onBulkDelete}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:trash' width={16} height={16} />
|
|
||||||
Hapus
|
|
||||||
{selectedRowIds.length > 0
|
|
||||||
? ` (${selectedRowIds.length})`
|
|
||||||
: ''}{' '}
|
|
||||||
Pengiriman
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -872,7 +872,7 @@ const RecordingTable = () => {
|
|||||||
'mb-20':
|
'mb-20':
|
||||||
isResponseSuccess(recordings) && recordings?.data?.length === 0,
|
isResponseSuccess(recordings) && recordings?.data?.length === 0,
|
||||||
}),
|
}),
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full overflow-visible!',
|
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
headerColumnClassName:
|
headerColumnClassName:
|
||||||
|
|||||||
@@ -1,91 +1,93 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import UniformityBarChart from '@/components/pages/production/uniformity/chart/UniformityBarChart';
|
import UniformityBarChart from '@/components/pages/production/uniformity/chart/UniformityBarChart';
|
||||||
import UniformityGaugeChart from '@/components/pages/production/uniformity/chart/UniformityGaugeChart';
|
import UniformityGaugeChart from '@/components/pages/production/uniformity/chart/UniformityGaugeChart';
|
||||||
import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton';
|
import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton';
|
||||||
import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton';
|
import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton';
|
||||||
|
import {
|
||||||
|
UniformityDetailItem,
|
||||||
|
Uniformity,
|
||||||
|
} from '@/types/api/production/uniformity';
|
||||||
|
|
||||||
interface BarChartData {
|
interface UniformityChartProps {
|
||||||
name: string;
|
uniformityData?: Uniformity | null;
|
||||||
uv: number;
|
uniformityDetails?: UniformityDetailItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GaugeChartData {
|
const UniformityChart = ({
|
||||||
value: number;
|
uniformityData,
|
||||||
label: string;
|
uniformityDetails,
|
||||||
kandang?: string;
|
}: UniformityChartProps) => {
|
||||||
week?: string;
|
const defaultUniformityDetails: UniformityDetailItem[] = [
|
||||||
currentValue?: number;
|
{ id: 1, weight: 61, range: 'Ideal' },
|
||||||
totalValue?: number;
|
{ id: 2, weight: 62, range: 'Ideal' },
|
||||||
}
|
{ id: 3, weight: 63, range: 'Ideal' },
|
||||||
|
{ id: 4, weight: 64, range: 'Ideal' },
|
||||||
const UniformityChart = () => {
|
{ id: 5, weight: 65, range: 'Ideal' },
|
||||||
// TODO: Replace with actual API call
|
{ id: 6, weight: 66, range: 'Ideal' },
|
||||||
const barChartData: BarChartData[] = [
|
{ id: 7, weight: 67, range: 'Ideal' },
|
||||||
{
|
|
||||||
name: '48-52',
|
|
||||||
uv: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '52-56',
|
|
||||||
uv: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '56-60',
|
|
||||||
uv: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '60-64',
|
|
||||||
uv: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '64-68',
|
|
||||||
uv: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '68-72',
|
|
||||||
uv: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '72-76',
|
|
||||||
uv: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '76-80',
|
|
||||||
uv: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '84-88',
|
|
||||||
uv: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '88-92',
|
|
||||||
uv: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '92-96',
|
|
||||||
uv: 160,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: Replace with actual API call
|
const detailsToUse = uniformityDetails || defaultUniformityDetails;
|
||||||
// const gaugeChartData: GaugeChartData = {
|
|
||||||
// value: 0,
|
|
||||||
// label: '',
|
|
||||||
// kandang: 'Kandang Cirangga',
|
|
||||||
// week: 'Week 2',
|
|
||||||
// currentValue: 512,
|
|
||||||
// totalValue: 1024,
|
|
||||||
// };
|
|
||||||
|
|
||||||
const gaugeChartData: GaugeChartData = {
|
const barChartData = useMemo(() => {
|
||||||
value: 52,
|
if (!uniformityData) {
|
||||||
label: 'Uniformity',
|
return [];
|
||||||
kandang: 'Kandang Cirangga',
|
}
|
||||||
week: 'Week 2',
|
|
||||||
currentValue: 512,
|
if (!detailsToUse || detailsToUse.length === 0) {
|
||||||
totalValue: 1024,
|
return [];
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const weights = detailsToUse.map((d) => d.weight);
|
||||||
|
const minWeight = Math.floor(Math.min(...weights) / 5) * 5;
|
||||||
|
const maxWeight = Math.ceil(Math.max(...weights) / 5) * 5;
|
||||||
|
|
||||||
|
const rangeSize = maxWeight - minWeight < 11 ? 4 : 5;
|
||||||
|
const ranges: string[] = [];
|
||||||
|
|
||||||
|
for (let start = minWeight; start <= maxWeight; start += rangeSize) {
|
||||||
|
const end = start + rangeSize;
|
||||||
|
ranges.push(`${start}-${end}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalIdealCount = detailsToUse.filter(
|
||||||
|
(d) => d.range === 'Ideal'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
return ranges.map((range) => {
|
||||||
|
const [minStr, maxStr] = range.split('-').map(Number);
|
||||||
|
const min = minStr;
|
||||||
|
const max = maxStr;
|
||||||
|
|
||||||
|
const birdsInRange = detailsToUse.filter(
|
||||||
|
(d) => d.weight >= min && d.weight < max
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const hasIdeal = detailsToUse.some(
|
||||||
|
(d) => d.range === 'Ideal' && d.weight >= min && d.weight < max
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: range,
|
||||||
|
uv: birdsInRange,
|
||||||
|
isIdeal: hasIdeal,
|
||||||
|
idealCount: hasIdeal ? totalIdealCount : undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [uniformityData, detailsToUse]);
|
||||||
|
|
||||||
|
const gaugeChartData = useMemo(() => {
|
||||||
|
if (!uniformityData) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: uniformityData.uniformity,
|
||||||
|
label: 'Uniformity',
|
||||||
|
week: `Week ${uniformityData.week}`,
|
||||||
|
currentValue: uniformityData.uniform_qty,
|
||||||
|
totalValue: uniformityData.chick_qty_of_weight,
|
||||||
|
};
|
||||||
|
}, [uniformityData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'>
|
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'>
|
||||||
@@ -98,14 +100,14 @@ const UniformityChart = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='w-full h-full flex items-center justify-center'>
|
<div className='w-full h-full flex items-center justify-center'>
|
||||||
{barChartData.length === 0 ? (
|
{!uniformityData || barChartData.length === 0 ? (
|
||||||
<UniformityBarChartSkeleton />
|
<UniformityBarChartSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<UniformityBarChart data={barChartData} />
|
<UniformityBarChart data={barChartData} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{gaugeChartData.value === 0 ? (
|
{!uniformityData || !gaugeChartData ? (
|
||||||
<Card
|
<Card
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
title='Weekly Performance ⓘ'
|
title='Weekly Performance ⓘ'
|
||||||
@@ -128,7 +130,6 @@ const UniformityChart = () => {
|
|||||||
<UniformityGaugeChart
|
<UniformityGaugeChart
|
||||||
value={gaugeChartData.value}
|
value={gaugeChartData.value}
|
||||||
label={gaugeChartData.label}
|
label={gaugeChartData.label}
|
||||||
kandang={gaugeChartData.kandang}
|
|
||||||
week={gaugeChartData.week}
|
week={gaugeChartData.week}
|
||||||
currentValue={gaugeChartData.currentValue}
|
currentValue={gaugeChartData.currentValue}
|
||||||
totalValue={gaugeChartData.totalValue}
|
totalValue={gaugeChartData.totalValue}
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export default function UniformityPageWrapper({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='w-full p-4'>
|
<div className='w-full p-4'>
|
||||||
<UniformityTable
|
<UniformityTable />
|
||||||
refresh={() => !isOpen && router.push('/production/uniformity')}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import Button from '@/components/Button';
|
|||||||
import UniformityChart from '@/components/pages/production/uniformity/UniformityChart';
|
import UniformityChart from '@/components/pages/production/uniformity/UniformityChart';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { UniformityApi } from '@/services/api/uniformity';
|
import { UniformityApi } from '@/services/api/uniformity';
|
||||||
import { type Uniformity } from '@/types/api/production/uniformity';
|
import {
|
||||||
|
DetailOptionType,
|
||||||
|
type Uniformity,
|
||||||
|
type UniformityDetail,
|
||||||
|
} from '@/types/api/production/uniformity';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { type BaseApiResponse } from '@/types/api/api-general';
|
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
@@ -45,27 +49,12 @@ import Dropdown from '@/components/Dropdown';
|
|||||||
import Menu from '@/components/menu/Menu';
|
import Menu from '@/components/menu/Menu';
|
||||||
import MenuItem from '@/components/menu/MenuItem';
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
|
|
||||||
const isUniformityLocked = (uniformity: Uniformity): boolean => {
|
|
||||||
// Uniformity data is never locked - checkbox is always enabled
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const canApproveRejectUniformity = (uniformity: Uniformity): boolean => {
|
|
||||||
return uniformity.status === 'CREATED' || uniformity.status === 'Pengajuan';
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UniformityPreviewData {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UniformityConfirmationPreview = ({
|
const UniformityConfirmationPreview = ({
|
||||||
uniformity,
|
uniformity,
|
||||||
}: {
|
}: {
|
||||||
uniformity?: Uniformity;
|
uniformity?: Uniformity;
|
||||||
}) => {
|
}) => {
|
||||||
const data: UniformityPreviewData[] = [
|
const data: DetailOptionType[] = [
|
||||||
{
|
{
|
||||||
id: 'tanggal',
|
id: 'tanggal',
|
||||||
label: 'Tanggal',
|
label: 'Tanggal',
|
||||||
@@ -91,7 +80,7 @@ const UniformityConfirmationPreview = ({
|
|||||||
{
|
{
|
||||||
id: 'file-uniformity',
|
id: 'file-uniformity',
|
||||||
label: 'File Uniformity',
|
label: 'File Uniformity',
|
||||||
value: '-', // File name tidak tersedia di GET ALL response
|
value: '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'status',
|
id: 'status',
|
||||||
@@ -100,7 +89,7 @@ const UniformityConfirmationPreview = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns: ColumnDef<UniformityPreviewData>[] = [
|
const columns: ColumnDef<DetailOptionType>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'label',
|
accessorKey: 'label',
|
||||||
header: 'Label',
|
header: 'Label',
|
||||||
@@ -148,7 +137,52 @@ const UniformityConfirmationPreview = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
const UniformityChartWrapper = ({
|
||||||
|
uniformitySwrKey,
|
||||||
|
}: {
|
||||||
|
uniformitySwrKey: string;
|
||||||
|
}) => {
|
||||||
|
const { data: uniformities } = useSWR(
|
||||||
|
uniformitySwrKey,
|
||||||
|
UniformityApi.getAllFetcher
|
||||||
|
);
|
||||||
|
|
||||||
|
const uniformityData = useMemo(() => {
|
||||||
|
if (isResponseSuccess(uniformities) && uniformities?.data?.length > 0) {
|
||||||
|
return uniformities.data[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [uniformities]);
|
||||||
|
|
||||||
|
const shouldFetchDetails = !!uniformityData;
|
||||||
|
const uniformityDetailSwrKey = useMemo(() => {
|
||||||
|
if (!uniformityData) return null;
|
||||||
|
return `${UniformityApi.basePath}/${uniformityData.id}?with_details=true`;
|
||||||
|
}, [uniformityData]);
|
||||||
|
|
||||||
|
const { data: uniformityDetailResponse } = useSWR(
|
||||||
|
uniformityDetailSwrKey,
|
||||||
|
shouldFetchDetails ? UniformityApi.getAllFetcher : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const uniformityDetails = useMemo(() => {
|
||||||
|
if (shouldFetchDetails && isResponseSuccess(uniformityDetailResponse)) {
|
||||||
|
const detailData =
|
||||||
|
uniformityDetailResponse.data as unknown as UniformityDetail;
|
||||||
|
return detailData.uniformity_details;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [shouldFetchDetails, uniformityDetailResponse]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UniformityChart
|
||||||
|
uniformityData={uniformityData}
|
||||||
|
uniformityDetails={uniformityDetails}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UniformityTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const isSuccess = useUniformityStore((s) => s.isSuccess);
|
const isSuccess = useUniformityStore((s) => s.isSuccess);
|
||||||
@@ -355,6 +389,12 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
mutate: refreshUniformities,
|
mutate: refreshUniformities,
|
||||||
} = useSWR(uniformitySwrKey, UniformityApi.getAllFetcher);
|
} = useSWR(uniformitySwrKey, UniformityApi.getAllFetcher);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess) {
|
||||||
|
refreshUniformities();
|
||||||
|
}
|
||||||
|
}, [isSuccess, refreshUniformities]);
|
||||||
|
|
||||||
// ===== FILTER HANDLERS =====
|
// ===== FILTER HANDLERS =====
|
||||||
const handleFilterLocationChange = useCallback(
|
const handleFilterLocationChange = useCallback(
|
||||||
(val: OptionType | OptionType[] | null) => {
|
(val: OptionType | OptionType[] | null) => {
|
||||||
@@ -836,7 +876,7 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
<div className='my-4 divider'></div>
|
<div className='my-4 divider'></div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<UniformityChart />
|
<UniformityChartWrapper uniformitySwrKey={uniformitySwrKey} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Bar,
|
Bar,
|
||||||
BarChart,
|
BarChart,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
|
Cell,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -25,6 +26,8 @@ interface CustomTooltipProps {
|
|||||||
interface BarChartData {
|
interface BarChartData {
|
||||||
name: string;
|
name: string;
|
||||||
uv: number;
|
uv: number;
|
||||||
|
isIdeal?: boolean;
|
||||||
|
idealCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UniformityBarChartProps {
|
interface UniformityBarChartProps {
|
||||||
@@ -33,7 +36,25 @@ interface UniformityBarChartProps {
|
|||||||
|
|
||||||
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
|
function CustomTooltip({ payload, label, active }: CustomTooltipProps) {
|
||||||
if (active && payload && payload.length && label !== undefined) {
|
if (active && payload && payload.length && label !== undefined) {
|
||||||
|
const data = payload[0] as unknown as { payload: BarChartData };
|
||||||
|
const chartData = data.payload as BarChartData;
|
||||||
const labelStr = String(label);
|
const labelStr = String(label);
|
||||||
|
|
||||||
|
if (chartData.isIdeal && chartData.idealCount !== undefined) {
|
||||||
|
return (
|
||||||
|
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
|
||||||
|
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
|
||||||
|
<div className='flex items-center gap-2 mt-2 justify-between'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<div className='w-5 h-5 bg-[#0069E0] rounded-md'></div>
|
||||||
|
{chartData.idealCount} of Birds
|
||||||
|
</div>
|
||||||
|
<span>{labelStr}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
|
<div className='bg-[#18181B] p-2.5 shadow-sm text-white rounded-2xl rounded-bl-none'>
|
||||||
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
|
<p className='m-0 font-bold text-white/50'>Uniformity 2025</p>
|
||||||
@@ -105,9 +126,6 @@ const UniformityBarChart: React.FC<UniformityBarChartProps> = ({ data }) => {
|
|||||||
<Bar
|
<Bar
|
||||||
name='Birds'
|
name='Birds'
|
||||||
dataKey='uv'
|
dataKey='uv'
|
||||||
fill='#FFFFFF'
|
|
||||||
stroke='#DDD'
|
|
||||||
strokeWidth={2}
|
|
||||||
radius={[25, 25, 0, 0]}
|
radius={[25, 25, 0, 0]}
|
||||||
activeBar={
|
activeBar={
|
||||||
<Rectangle
|
<Rectangle
|
||||||
@@ -117,7 +135,16 @@ const UniformityBarChart: React.FC<UniformityBarChartProps> = ({ data }) => {
|
|||||||
radius={[25, 25, 0, 0]}
|
radius={[25, 25, 0, 0]}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={entry.isIdeal ? 'url(#activeBarGradient)' : '#FFFFFF'}
|
||||||
|
stroke={entry.isIdeal ? '#18181B' : '#DDD'}
|
||||||
|
strokeWidth={entry.isIdeal ? 0 : 2}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
|
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import { formatNumber } from '@/lib/helper';
|
import { formatNumber } from '@/lib/helper';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
interface UniformityGaugeChartProps {
|
interface UniformityGaugeChartProps {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
kandang?: string;
|
|
||||||
week?: string;
|
week?: string;
|
||||||
currentValue?: number;
|
currentValue?: number;
|
||||||
totalValue?: number;
|
totalValue?: number;
|
||||||
|
onWeekChange?: (direction: 'prev' | 'next') => void;
|
||||||
|
hasPrevWeek?: boolean;
|
||||||
|
hasNextWeek?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UniformityGaugeChart: React.FC<UniformityGaugeChartProps> = ({
|
const UniformityGaugeChart: React.FC<UniformityGaugeChartProps> = ({
|
||||||
value,
|
value,
|
||||||
label,
|
label,
|
||||||
kandang,
|
|
||||||
week,
|
week,
|
||||||
currentValue,
|
currentValue,
|
||||||
totalValue,
|
totalValue,
|
||||||
|
onWeekChange,
|
||||||
|
hasPrevWeek = false,
|
||||||
|
hasNextWeek = false,
|
||||||
}) => {
|
}) => {
|
||||||
const numberOfSegments = 50;
|
const numberOfSegments = 50;
|
||||||
const filledSegments = Math.round((value / 100) * numberOfSegments);
|
const filledSegments = Math.round((value / 100) * numberOfSegments);
|
||||||
@@ -34,7 +38,7 @@ const UniformityGaugeChart: React.FC<UniformityGaugeChartProps> = ({
|
|||||||
const inactiveColor = '#f0f0f0';
|
const inactiveColor = '#f0f0f0';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full items-center'>
|
||||||
<div className='h-64 w-full relative flex justify-center'>
|
<div className='h-64 w-full relative flex justify-center'>
|
||||||
<div className='relative w-full h-full flex flex-col items-center justify-end'>
|
<div className='relative w-full h-full flex flex-col items-center justify-end'>
|
||||||
<ResponsiveContainer width='100%' height='100%'>
|
<ResponsiveContainer width='100%' height='100%'>
|
||||||
@@ -73,34 +77,49 @@ const UniformityGaugeChart: React.FC<UniformityGaugeChartProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Card
|
<div className='flex items-center justify-center gap-2 w-full'>
|
||||||
variant='bordered'
|
<button
|
||||||
className={{
|
onClick={() => onWeekChange?.('prev')}
|
||||||
wrapper: 'w-full',
|
disabled={!hasPrevWeek}
|
||||||
}}
|
className='p-2 rounded-lg border border-gray-200 bg-white hover:bg-gray-50 disabled:opacity-30 disabled:cursor-not-allowed transition-colors shadow-sm'
|
||||||
>
|
aria-label='Previous week'
|
||||||
<section className='flex items-center gap-4'>
|
>
|
||||||
<div className='w-12 h-12 bg-base-200 rounded-lg flex items-center justify-center border border-gray-200 shrink-0'>
|
<Icon icon='heroicons:chevron-left' width={20} height={20} />
|
||||||
<Icon icon='heroicons:calendar-date-range' width={24} height={24} />
|
</button>
|
||||||
</div>
|
<Card
|
||||||
<div className='grid grid-cols-1 min-w-0'>
|
variant='bordered'
|
||||||
<div className='flex items-center space-x-2 text-[#18181B80] text-sm mb-1'>
|
className={{
|
||||||
<span className='font-medium truncate'>{kandang}</span>
|
wrapper: 'max-w-xs',
|
||||||
<span className='shrink-0'>•</span>
|
}}
|
||||||
<span className='text-[#0069E0] font-semibold truncate'>
|
>
|
||||||
{week}
|
<section className='flex items-center justify-center gap-4'>
|
||||||
</span>
|
<div className='grid grid-cols-1 min-w-0 text-center'>
|
||||||
|
<div className='flex items-center justify-center space-x-2 text-[#18181B80] text-sm mb-1'>
|
||||||
|
<span className='text-[#0069E0] font-semibold truncate'>
|
||||||
|
{week}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-xl font-bold text-[#18181B80]'>
|
||||||
|
<span className='text-[#0069E0] break-all'>
|
||||||
|
{formatNumber(currentValue ?? 0)}
|
||||||
|
</span>
|
||||||
|
<span className='mx-1 text-gray-400 text-base'>From</span>
|
||||||
|
<span className='break-all'>
|
||||||
|
{formatNumber(totalValue ?? 0)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xl font-bold text-[#18181B80]'>
|
</section>
|
||||||
<span className='text-[#0069E0] break-all'>
|
</Card>
|
||||||
{formatNumber(currentValue ?? 0)}
|
<button
|
||||||
</span>
|
onClick={() => onWeekChange?.('next')}
|
||||||
<span className='mx-1 text-gray-400 text-base'>From</span>
|
disabled={!hasNextWeek}
|
||||||
<span className='break-all'>{formatNumber(totalValue ?? 0)}</span>
|
className='p-2 rounded-lg border border-gray-200 bg-white hover:bg-gray-50 disabled:opacity-30 disabled:cursor-not-allowed transition-colors shadow-sm'
|
||||||
</div>
|
aria-label='Next week'
|
||||||
</div>
|
>
|
||||||
</section>
|
<Icon icon='heroicons:chevron-right' width={20} height={20} />
|
||||||
</Card>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -119,11 +119,13 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
|||||||
const statusValue = latest_approval?.action ?? '-';
|
const statusValue = latest_approval?.action ?? '-';
|
||||||
|
|
||||||
const valueMap: Record<string, string> = {
|
const valueMap: Record<string, string> = {
|
||||||
tanggal: formatDate(info_umum.tanggal, 'DD MMMM YYYY'),
|
tanggal: info_umum?.tanggal
|
||||||
'lokasi-farm': info_umum.lokasi_farm,
|
? formatDate(info_umum.tanggal, 'DD MMMM YYYY')
|
||||||
'project-flock': info_umum.project_flock,
|
: '-',
|
||||||
kandang: info_umum.kandang,
|
'lokasi-farm': info_umum?.lokasi_farm ?? '-',
|
||||||
'document-name': info_umum.file_name,
|
'project-flock': info_umum?.project_flock ?? '-',
|
||||||
|
kandang: info_umum?.kandang ?? '-',
|
||||||
|
'document-name': info_umum?.file_name ?? '-',
|
||||||
'approval-status': statusValue,
|
'approval-status': statusValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -229,49 +229,52 @@ const UniformityDetailsPreview = ({
|
|||||||
{/* Form Section */}
|
{/* Form Section */}
|
||||||
<div className='divider mt-3.5'></div>
|
<div className='divider mt-3.5'></div>
|
||||||
<section className='w-full px-6'>
|
<section className='w-full px-6'>
|
||||||
{uniformity_details && uniformity_details.length > 0 ? (
|
{info_umum || sampling || result ? (
|
||||||
<div className='flex flex-col gap-4'>
|
<div className='flex flex-col gap-4'>
|
||||||
{/* Sampling and Range */}
|
{/* Sampling and Range */}
|
||||||
<div className=''>
|
{sampling && (
|
||||||
<p className='text-sm font-medium mb-5'>Sampling and Range</p>
|
<div className=''>
|
||||||
<Table<DetailOptionType>
|
<p className='text-sm font-medium mb-5'>Sampling and Range</p>
|
||||||
data={samplingTableData}
|
<Table<DetailOptionType>
|
||||||
columns={columnsSampling}
|
data={samplingTableData}
|
||||||
pageSize={4}
|
columns={columnsSampling}
|
||||||
className={{
|
pageSize={4}
|
||||||
containerClassName: 'mb-0',
|
className={{
|
||||||
paginationClassName: 'hidden',
|
containerClassName: 'mb-0',
|
||||||
}}
|
paginationClassName: 'hidden',
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
{/* Result */}
|
</div>
|
||||||
<div className=''>
|
)}
|
||||||
<p className='text-sm font-medium mb-5'>Result</p>
|
|
||||||
<Table<DetailOptionType>
|
|
||||||
data={resultTableData}
|
|
||||||
columns={resultColumns}
|
|
||||||
pageSize={4}
|
|
||||||
className={{
|
|
||||||
containerClassName: 'mb-0',
|
|
||||||
paginationClassName: 'hidden',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Body Weight Details Button */}
|
{/* Result */}
|
||||||
<div className='mt-4'>
|
{result && (
|
||||||
<Button
|
<div className=''>
|
||||||
type='button'
|
<p className='text-sm font-medium mb-5'>Result</p>
|
||||||
onClick={fetchWeightData}
|
<Table<DetailOptionType>
|
||||||
disabled={isLoading}
|
data={resultTableData}
|
||||||
className='w-full'
|
columns={resultColumns}
|
||||||
>
|
pageSize={4}
|
||||||
{isLoading ? 'Loading...' : 'Show Body Weight Details'}
|
className={{
|
||||||
</Button>
|
containerClassName: 'mb-0',
|
||||||
</div>
|
paginationClassName: 'hidden',
|
||||||
{/*{!uniformity_details || uniformity_details.length === 0 ? (
|
}}
|
||||||
<></>
|
/>
|
||||||
) : null}*/}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!uniformity_details || uniformity_details.length === 0 ? (
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
onClick={fetchWeightData}
|
||||||
|
disabled={isLoading}
|
||||||
|
className='w-full'
|
||||||
|
>
|
||||||
|
{isLoading ? 'Loading...' : 'Show Body Weight Details'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* Body Weight Details */}
|
{/* Body Weight Details */}
|
||||||
{uniformity_details && uniformity_details.length > 0 && (
|
{uniformity_details && uniformity_details.length > 0 && (
|
||||||
|
|||||||
@@ -97,12 +97,16 @@ const UniformityForm = ({
|
|||||||
setInputValue: setLocationSelectInputValue,
|
setInputValue: setLocationSelectInputValue,
|
||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocations,
|
isLoadingOptions: isLoadingLocations,
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
|
||||||
|
page: '1',
|
||||||
|
limit: '100',
|
||||||
|
});
|
||||||
|
|
||||||
// ===== FETCH PROJECT FLOCKS DATA =====
|
// ===== FETCH PROJECT FLOCKS DATA =====
|
||||||
const projectFlocksUrl = useMemo(() => {
|
const projectFlocksUrl = useMemo(() => {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
search: projectFlockSearchValue || '',
|
search: projectFlockSearchValue || '',
|
||||||
|
page: '1',
|
||||||
limit: '100',
|
limit: '100',
|
||||||
});
|
});
|
||||||
if (selectedLocation) {
|
if (selectedLocation) {
|
||||||
@@ -141,6 +145,7 @@ const UniformityForm = ({
|
|||||||
const approvedProjectFlockKandangsUrl = useMemo(() => {
|
const approvedProjectFlockKandangsUrl = useMemo(() => {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
step_name: 'Disetujui',
|
step_name: 'Disetujui',
|
||||||
|
page: '1',
|
||||||
limit: '100',
|
limit: '100',
|
||||||
});
|
});
|
||||||
return `${ProjectFlockKandangApi.basePath}?${params.toString()}`;
|
return `${ProjectFlockKandangApi.basePath}?${params.toString()}`;
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ const UniformityGaugeChartSkeleton: React.FC<
|
|||||||
const inactiveColor = '#f0f0f0';
|
const inactiveColor = '#f0f0f0';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full items-center'>
|
||||||
<div className='h-64 w-full relative flex justify-center min-h-[256px]'>
|
<div className='h-64 w-full relative flex justify-center min-h-[256px]'>
|
||||||
<div className='relative w-full h-full flex flex-col items-center justify-end min-w-0'>
|
<div className='relative w-full h-full flex flex-col items-center justify-end min-w-0'>
|
||||||
<ResponsiveContainer width='100%' height={256}>
|
<ResponsiveContainer width='100%' height={256}>
|
||||||
|
|||||||
@@ -52,9 +52,14 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [purchaseOrderFormErrorMessage, setPurchaseOrderFormErrorMessage] =
|
const [purchaseOrderFormErrorMessage, setPurchaseOrderFormErrorMessage] =
|
||||||
useState('');
|
useState('');
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
const isRejected = initialValues?.latest_approval?.action === 'REJECTED';
|
const isRejected = initialValues?.latest_approval?.action === 'REJECTED';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setKey((prev) => prev + 1);
|
||||||
|
}, [initialValues?.id]);
|
||||||
|
|
||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
const isRepeaterInputError = (
|
const isRepeaterInputError = (
|
||||||
idx: number,
|
idx: number,
|
||||||
@@ -164,6 +169,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
validationSchema: PurchaseRequestAcceptApprovalFormSchema,
|
validationSchema: PurchaseRequestAcceptApprovalFormSchema,
|
||||||
validateOnChange: true,
|
validateOnChange: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
|
enableReinitialize: false,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const payload: CreateAcceptApprovalRequestPayload = {
|
const payload: CreateAcceptApprovalRequestPayload = {
|
||||||
action: 'APPROVED',
|
action: 'APPROVED',
|
||||||
@@ -238,7 +244,12 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
travel_number: item.travel_number || '',
|
travel_number: item.travel_number || '',
|
||||||
travel_document_path: item.travel_document_path || '',
|
travel_document_path: item.travel_document_path || '',
|
||||||
vehicle_number: item.vehicle_number || '',
|
vehicle_number: item.vehicle_number || '',
|
||||||
expedition_vendor: null,
|
expedition_vendor: item.expedition_vendor
|
||||||
|
? {
|
||||||
|
value: item.expedition_vendor.id,
|
||||||
|
label: item.expedition_vendor.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
expedition_vendor_id: item.expedition_vendor_id || 0,
|
expedition_vendor_id: item.expedition_vendor_id || 0,
|
||||||
received_qty: item.total_qty || '',
|
received_qty: item.total_qty || '',
|
||||||
transport_per_item: item.transport_per_item || '',
|
transport_per_item: item.transport_per_item || '',
|
||||||
@@ -246,7 +257,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
});
|
});
|
||||||
formik.setFieldValue('items', updatedItems);
|
formik.setFieldValue('items', updatedItems);
|
||||||
}
|
}
|
||||||
}, [purchaseItems, initialValues]);
|
}, [purchaseItems, initialValues, key]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -336,7 +347,11 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={formik.handleSubmit} className='w-full flex flex-col gap-6'>
|
<form
|
||||||
|
key={key}
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
className='w-full flex flex-col gap-6'
|
||||||
|
>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<h2 className='text-lg font-semibold mb-4'>
|
<h2 className='text-lg font-semibold mb-4'>
|
||||||
{type === 'add'
|
{type === 'add'
|
||||||
@@ -699,7 +714,9 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
color='warning'
|
color='warning'
|
||||||
className='px-4'
|
className='px-4'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
formik.resetForm();
|
if (type === 'add') {
|
||||||
|
formik.resetForm();
|
||||||
|
}
|
||||||
setPurchaseOrderFormErrorMessage('');
|
setPurchaseOrderFormErrorMessage('');
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
onModalClose?.();
|
onModalClose?.();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
import { isResponseError } from '@/lib/api-helper';
|
import { isResponseError } from '@/lib/api-helper';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
|
import { ProductionResultReportApi } from '@/services/api/report/production-result';
|
||||||
|
|
||||||
const ProductionResultContent = () => {
|
const ProductionResultContent = () => {
|
||||||
const [projectFlockKandangs, setProjectFlockKandangs] = useState<
|
const [projectFlockKandangs, setProjectFlockKandangs] = useState<
|
||||||
@@ -145,8 +146,11 @@ const ProductionResultContent = () => {
|
|||||||
|
|
||||||
const exportToExcelHandler = async () => {
|
const exportToExcelHandler = async () => {
|
||||||
setIsLoadingExportingToExcel(true);
|
setIsLoadingExportingToExcel(true);
|
||||||
// TODO: Implement export functionality in API service first if needed
|
|
||||||
toast.error('Fitur export belum tersedia');
|
await ProductionResultReportApi.exportProductionResultToExcel(
|
||||||
|
projectFlockKandangs
|
||||||
|
);
|
||||||
|
|
||||||
setIsLoadingExportingToExcel(false);
|
setIsLoadingExportingToExcel(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -319,7 +323,13 @@ const ProductionResultContent = () => {
|
|||||||
align='end'
|
align='end'
|
||||||
direction='bottom'
|
direction='bottom'
|
||||||
trigger={
|
trigger={
|
||||||
<Button>
|
<Button
|
||||||
|
disabled={
|
||||||
|
!selectedArea ||
|
||||||
|
!selectedLocation ||
|
||||||
|
!selectedProjectFlock
|
||||||
|
}
|
||||||
|
>
|
||||||
Export{' '}
|
Export{' '}
|
||||||
<Icon
|
<Icon
|
||||||
icon='heroicons-outline:download'
|
icon='heroicons-outline:download'
|
||||||
|
|||||||
+1
-1
@@ -352,7 +352,7 @@ const ProductionResultProjectFlockKandangTable = ({
|
|||||||
productionResults?.data?.length === 0,
|
productionResults?.data?.length === 0,
|
||||||
}),
|
}),
|
||||||
headerColumnClassName:
|
headerColumnClassName:
|
||||||
'px-4 py-3 border-base-content/10 text-base-content/50',
|
'px-4 py-3 border-x border-base-content/10 text-base-content/50',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -75,8 +75,13 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'/expense/realization/edit/': ['lti.expense.update.realization'],
|
'/expense/realization/edit/': ['lti.expense.update.realization'],
|
||||||
|
|
||||||
// Finance
|
// Finance
|
||||||
'/finance/': ['lti.finance.transaction.list'],
|
'/finance/': ['lti.finance.transactions.list'],
|
||||||
'/finance/detail/': ['lti.finance.transaction.detail'],
|
'/finance/detail/': [
|
||||||
|
'lti.finance.transactions.detail',
|
||||||
|
'lti.finance.initial_balances.detail',
|
||||||
|
'lti.finance.injections.detail',
|
||||||
|
'lti.finance.payments.detail',
|
||||||
|
],
|
||||||
'/finance/add/': ['lti.finance.payments.create'],
|
'/finance/add/': ['lti.finance.payments.create'],
|
||||||
'/finance/detail/edit/': ['lti.finance.payments.update'],
|
'/finance/detail/edit/': ['lti.finance.payments.update'],
|
||||||
'/finance/add/initial-balance/': ['lti.finance.initial_balances.create'],
|
'/finance/add/initial-balance/': ['lti.finance.initial_balances.create'],
|
||||||
@@ -94,10 +99,7 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'/report/logistic-stock/': ['lti.repport.purchasesupplier.list'],
|
'/report/logistic-stock/': ['lti.repport.purchasesupplier.list'],
|
||||||
'/report/expense/': ['lti.repport.expense.list'],
|
'/report/expense/': ['lti.repport.expense.list'],
|
||||||
'/report/marketing/': ['lti.repport.delivery.list'],
|
'/report/marketing/': ['lti.repport.delivery.list'],
|
||||||
|
'/report/production-result/': ['lti.repport.production_result.list'],
|
||||||
// TODO: change to real permission
|
|
||||||
// '/report/production-result/': ['lti.repport.production_result.list'],
|
|
||||||
'/report/production-result/': ['lti.repport.delivery.list'],
|
|
||||||
|
|
||||||
// Inventory
|
// Inventory
|
||||||
'/inventory/adjustment/': ['lti.inventory.list'],
|
'/inventory/adjustment/': ['lti.inventory.list'],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import {
|
import {
|
||||||
CreateProductWarehousePayload,
|
CreateProductWarehousePayload,
|
||||||
ProductWarehouse,
|
ProductWarehouse,
|
||||||
@@ -20,11 +21,38 @@ export const ProductWarehouseApi = new BaseApiService<
|
|||||||
UpdateProductWarehousePayload
|
UpdateProductWarehousePayload
|
||||||
>('/inventory/product-warehouses');
|
>('/inventory/product-warehouses');
|
||||||
|
|
||||||
export const MovementApi = new BaseApiService<
|
export class MovementApiService extends BaseApiService<
|
||||||
Movement,
|
Movement,
|
||||||
CreateMovementPayload,
|
CreateMovementPayload,
|
||||||
unknown
|
unknown
|
||||||
>('/inventory/transfers');
|
> {
|
||||||
|
constructor(basePath: string) {
|
||||||
|
super(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createMovement(
|
||||||
|
payload: CreateMovementPayload
|
||||||
|
): Promise<BaseApiResponse<Movement> | undefined> {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Append data as JSON string
|
||||||
|
formData.append('data', JSON.stringify(payload.data));
|
||||||
|
|
||||||
|
// Append documents if any
|
||||||
|
if (payload.documents && payload.documents.length > 0) {
|
||||||
|
payload.documents.forEach((file) => {
|
||||||
|
formData.append('documents', file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.customRequest<BaseApiResponse<Movement>>('', {
|
||||||
|
method: 'POST',
|
||||||
|
payload: formData as unknown as Record<string, unknown>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MovementApi = new MovementApiService('/inventory/transfers');
|
||||||
|
|
||||||
export const InventoryAdjustmentApi = new BaseApiService<
|
export const InventoryAdjustmentApi = new BaseApiService<
|
||||||
InventoryAdjustment,
|
InventoryAdjustment,
|
||||||
|
|||||||
@@ -1,145 +1,12 @@
|
|||||||
import { sleep } from '@/lib/helper';
|
import * as XLSX from 'xlsx';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import { httpClientFetcher } from '@/services/http/client';
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { ProductionResult } from '@/types/api/report/production-result';
|
import { ProductionResult } from '@/types/api/report/production-result';
|
||||||
|
import { BaseProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
// TODO: delete this dummy data
|
|
||||||
const PRODUCTION_RESULT_DUMMY_DATA: BaseApiResponse<ProductionResult[]> = {
|
|
||||||
code: 200,
|
|
||||||
status: 'success',
|
|
||||||
message: 'Get Laporan Hasil Produksi successfully',
|
|
||||||
meta: {
|
|
||||||
page: 1,
|
|
||||||
limit: 1,
|
|
||||||
total_pages: 2,
|
|
||||||
total_results: 2,
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1001,
|
|
||||||
email: 'user@example.com',
|
|
||||||
name: 'John Doe',
|
|
||||||
},
|
|
||||||
project_flock: {
|
|
||||||
id: 1,
|
|
||||||
name: 'PROJECT',
|
|
||||||
category: 'LAYING',
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Cikaum',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: '2025-01-01T08:00:00Z',
|
|
||||||
updated_at: '2025-01-02T10:30:00Z',
|
|
||||||
|
|
||||||
woa: 25,
|
|
||||||
|
|
||||||
bw: 62.5,
|
|
||||||
std_bw: 60,
|
|
||||||
uniformity: 88,
|
|
||||||
std_uniformity: '90% up',
|
|
||||||
|
|
||||||
dep_kum: 3.2,
|
|
||||||
dep_std: 2.5,
|
|
||||||
|
|
||||||
butiran_utuh: 850,
|
|
||||||
butiran_putih: 50,
|
|
||||||
butiran_retak: 70,
|
|
||||||
butiran_pecah: 30,
|
|
||||||
butiran_jumlah: 1000,
|
|
||||||
total_butir: 1000,
|
|
||||||
|
|
||||||
kg_utuh: 52.3,
|
|
||||||
kg_putih: 3.1,
|
|
||||||
kg_retak: 4.2,
|
|
||||||
kg_pecah: 1.9,
|
|
||||||
kg_jumlah: 61.5,
|
|
||||||
total_kg: 61.5,
|
|
||||||
|
|
||||||
persen_utuh: 85,
|
|
||||||
persen_putih: 5,
|
|
||||||
persen_retak: 7,
|
|
||||||
persen_pecah: 3,
|
|
||||||
|
|
||||||
hd: 92,
|
|
||||||
hd_std: 90,
|
|
||||||
fi: 115,
|
|
||||||
fi_std: 667,
|
|
||||||
em: 85,
|
|
||||||
em_std: 83,
|
|
||||||
ew: 62,
|
|
||||||
ew_std: 60,
|
|
||||||
fcr: 2.1,
|
|
||||||
fcr_std: 2.0,
|
|
||||||
hh: 96,
|
|
||||||
hh_std: 95,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1001,
|
|
||||||
email: 'user@example.com',
|
|
||||||
name: 'John Doe',
|
|
||||||
},
|
|
||||||
project_flock: {
|
|
||||||
id: 1,
|
|
||||||
name: 'PROJECT',
|
|
||||||
category: 'LAYING',
|
|
||||||
kandang: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Cikaum',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: '2025-01-01T08:00:00Z',
|
|
||||||
updated_at: '2025-01-02T10:30:00Z',
|
|
||||||
|
|
||||||
woa: 25,
|
|
||||||
|
|
||||||
bw: 62.5,
|
|
||||||
std_bw: 60,
|
|
||||||
uniformity: 88,
|
|
||||||
std_uniformity: '90% up',
|
|
||||||
|
|
||||||
dep_kum: 3.2,
|
|
||||||
dep_std: 2.5,
|
|
||||||
|
|
||||||
butiran_utuh: 850,
|
|
||||||
butiran_putih: 50,
|
|
||||||
butiran_retak: 70,
|
|
||||||
butiran_pecah: 30,
|
|
||||||
butiran_jumlah: 1000,
|
|
||||||
total_butir: 1000,
|
|
||||||
|
|
||||||
kg_utuh: 52.3,
|
|
||||||
kg_putih: 3.1,
|
|
||||||
kg_retak: 4.2,
|
|
||||||
kg_pecah: 1.9,
|
|
||||||
kg_jumlah: 61.5,
|
|
||||||
total_kg: 61.5,
|
|
||||||
|
|
||||||
persen_utuh: 85,
|
|
||||||
persen_putih: 5,
|
|
||||||
persen_retak: 7,
|
|
||||||
persen_pecah: 3,
|
|
||||||
|
|
||||||
hd: 92,
|
|
||||||
hd_std: 90,
|
|
||||||
fi: 115,
|
|
||||||
fi_std: 110,
|
|
||||||
em: 85,
|
|
||||||
em_std: 83,
|
|
||||||
ew: 62,
|
|
||||||
ew_std: 60,
|
|
||||||
fcr: 2.1,
|
|
||||||
fcr_std: 2.0,
|
|
||||||
hh: 96,
|
|
||||||
hh_std: 95,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ProductionResultReportApiService extends BaseApiService<
|
export class ProductionResultReportApiService extends BaseApiService<
|
||||||
ProductionResult,
|
ProductionResult,
|
||||||
@@ -153,14 +20,117 @@ export class ProductionResultReportApiService extends BaseApiService<
|
|||||||
async getAllProductionResultFetcher(
|
async getAllProductionResultFetcher(
|
||||||
endpoint: string
|
endpoint: string
|
||||||
): Promise<BaseApiResponse<ProductionResult[]>> {
|
): Promise<BaseApiResponse<ProductionResult[]>> {
|
||||||
// return await httpClientFetcher<BaseApiResponse<ProductionResult[]>>(
|
return await httpClientFetcher<BaseApiResponse<ProductionResult[]>>(
|
||||||
// endpoint
|
endpoint
|
||||||
// );
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await sleep(1000);
|
async exportProductionResultToExcel(
|
||||||
|
projectFlockKandangs: BaseProjectFlockKandang[] | null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const mappedProductionResults: {
|
||||||
|
projectFlockKandang: BaseProjectFlockKandang;
|
||||||
|
productionResult: ProductionResult[] | null;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
return PRODUCTION_RESULT_DUMMY_DATA;
|
projectFlockKandangs?.forEach(async (projectFlockKandang) => {
|
||||||
|
const getProductionResultPath = `${this.basePath}/${projectFlockKandang.id}?page=1&limit=99999999`;
|
||||||
|
const getProductionResultRes = await httpClient<
|
||||||
|
BaseApiResponse<ProductionResult[]>
|
||||||
|
>(getProductionResultPath);
|
||||||
|
|
||||||
|
mappedProductionResults.push({
|
||||||
|
projectFlockKandang,
|
||||||
|
productionResult: isResponseSuccess(getProductionResultRes)
|
||||||
|
? getProductionResultRes.data
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = mappedProductionResults;
|
||||||
|
if (!rows || rows.length === 0) {
|
||||||
|
toast.error('Tidak ada data untuk diexport.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by Project Flock Kandang Name
|
||||||
|
const groupedData: Record<
|
||||||
|
string,
|
||||||
|
Record<string, string | number | undefined>[]
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const kandangName = row.projectFlockKandang.kandang.name || 'Unknown';
|
||||||
|
if (!groupedData[kandangName]) {
|
||||||
|
groupedData[kandangName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
row.productionResult?.forEach((productionResult) => {
|
||||||
|
groupedData[kandangName].push({
|
||||||
|
woa: productionResult.woa,
|
||||||
|
bw: productionResult.bw,
|
||||||
|
std_bw: productionResult.std_bw,
|
||||||
|
uniformity: productionResult.uniformity,
|
||||||
|
std_uniformity: productionResult.std_uniformity,
|
||||||
|
dep_kum: productionResult.dep_kum,
|
||||||
|
dep_std: productionResult.dep_std,
|
||||||
|
butiran_utuh: productionResult.butiran_utuh,
|
||||||
|
butiran_putih: productionResult.butiran_putih,
|
||||||
|
butiran_retak: productionResult.butiran_retak,
|
||||||
|
butiran_pecah: productionResult.butiran_pecah,
|
||||||
|
butiran_jumlah: productionResult.butiran_jumlah,
|
||||||
|
total_butir: productionResult.total_butir,
|
||||||
|
kg_utuh: productionResult.kg_utuh,
|
||||||
|
kg_putih: productionResult.kg_putih,
|
||||||
|
kg_retak: productionResult.kg_retak,
|
||||||
|
kg_pecah: productionResult.kg_pecah,
|
||||||
|
kg_jumlah: productionResult.kg_jumlah,
|
||||||
|
total_kg: productionResult.total_kg,
|
||||||
|
persen_utuh: productionResult.persen_utuh,
|
||||||
|
persen_putih: productionResult.persen_putih,
|
||||||
|
persen_retak: productionResult.persen_retak,
|
||||||
|
persen_pecah: productionResult.persen_pecah,
|
||||||
|
hd: productionResult.hd,
|
||||||
|
hd_std: productionResult.hd_std,
|
||||||
|
fi: productionResult.fi,
|
||||||
|
fi_std: productionResult.fi_std,
|
||||||
|
em: productionResult.em,
|
||||||
|
em_std: productionResult.em_std,
|
||||||
|
ew: productionResult.ew,
|
||||||
|
ew_std: productionResult.ew_std,
|
||||||
|
fcr: productionResult.fcr,
|
||||||
|
fcr_std: productionResult.fcr_std,
|
||||||
|
hh: productionResult.hh,
|
||||||
|
hh_std: productionResult.hh_std,
|
||||||
|
project_flock_name: productionResult.project_flock.name,
|
||||||
|
project_flock_category: productionResult.project_flock.category,
|
||||||
|
kandang_name: productionResult.project_flock.kandang.name,
|
||||||
|
created_at: formatDate(productionResult.created_at, 'YYYY-MM-DD'),
|
||||||
|
updated_at: formatDate(productionResult.updated_at, 'YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
Object.keys(groupedData).forEach((sheetName) => {
|
||||||
|
const ws = XLSX.utils.json_to_sheet(groupedData[sheetName]);
|
||||||
|
// Sheet names cannot exceed 31 chars
|
||||||
|
const safeSheetName = sheetName.substring(0, 31);
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, safeSheetName);
|
||||||
|
});
|
||||||
|
|
||||||
|
const productionResultExcelFileName = `laporan-hasil-produksi-${formatDate(Date.now(), 'YYYY-MM-DD')}-${rows[0].projectFlockKandang.project_flock.flock_name}.xlsx`;
|
||||||
|
|
||||||
|
XLSX.writeFile(wb, productionResultExcelFileName);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('Gagal melakukan export laporan hasil produksi! Coba lagi.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProductionResultReportApi = new ProductionResultReportApiService();
|
export const ProductionResultReportApi = new ProductionResultReportApiService(
|
||||||
|
'/reports/production-result'
|
||||||
|
);
|
||||||
|
|||||||
Vendored
+22
-14
@@ -112,34 +112,42 @@ export type ClosingProductionData = {
|
|||||||
final_population: number;
|
final_population: number;
|
||||||
feed_in: number;
|
feed_in: number;
|
||||||
feed_used: number;
|
feed_used: number;
|
||||||
feed_used_per_head: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sales: {
|
sales: {
|
||||||
chicken: {
|
chicken: {
|
||||||
sales_population: number;
|
sales_population: number;
|
||||||
sales_weight: number;
|
sales_weight: number;
|
||||||
average_weight: number;
|
avg_weight: number;
|
||||||
chicken_average_selling_price: number;
|
avg_selling_price: number;
|
||||||
};
|
};
|
||||||
egg?: {
|
egg?: {
|
||||||
egg_pieces: number;
|
egg_pieces: number;
|
||||||
egg_mass_kg: number;
|
egg_mass: number;
|
||||||
average_egg_weight_kg: number;
|
avg_egg_weight: number;
|
||||||
egg_average_selling_price: number;
|
avg_selling_price: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
performance: {
|
performance: {
|
||||||
depletion: number;
|
depletion: number;
|
||||||
age_day: number;
|
age_day: number;
|
||||||
mortality_std: number;
|
mor_std: number;
|
||||||
mortality_act: number;
|
mor_act: number;
|
||||||
deff_mortality: number;
|
mor_diff: number;
|
||||||
fcr_std: number;
|
awg_act: number;
|
||||||
|
awg_std: number;
|
||||||
|
feed_intake: number;
|
||||||
|
feed_intake_std: number;
|
||||||
fcr_act: number;
|
fcr_act: number;
|
||||||
deff_fcr: number;
|
fcr_std: number;
|
||||||
awg: number;
|
fcr_diff: number;
|
||||||
|
hen_day_act?: number;
|
||||||
|
hen_day_std?: number;
|
||||||
|
egg_mass?: number;
|
||||||
|
egg_mass_std?: number;
|
||||||
|
egg_weight?: number;
|
||||||
|
egg_weight_std?: number;
|
||||||
|
hen_housed_act?: number;
|
||||||
|
hen_housed_std?: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -14,6 +14,14 @@ type MovementWarehouse = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MovementDocument = {
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
ext: string;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type BaseMovement = {
|
export type BaseMovement = {
|
||||||
id: number;
|
id: number;
|
||||||
transfer_reason: string;
|
transfer_reason: string;
|
||||||
@@ -39,6 +47,7 @@ export type BaseMovement = {
|
|||||||
document_path: string;
|
document_path: string;
|
||||||
shipping_cost_item: number;
|
shipping_cost_item: number;
|
||||||
shipping_cost_total: number;
|
shipping_cost_total: number;
|
||||||
|
document?: MovementDocument;
|
||||||
items: {
|
items: {
|
||||||
id: number;
|
id: number;
|
||||||
stock_transfer_detail_id: number;
|
stock_transfer_detail_id: number;
|
||||||
@@ -49,7 +58,7 @@ export type BaseMovement = {
|
|||||||
|
|
||||||
export type Movement = BaseMetadata & BaseMovement;
|
export type Movement = BaseMetadata & BaseMovement;
|
||||||
|
|
||||||
export type CreateMovementPayload = {
|
export type CreateMovementPayloadData = {
|
||||||
transfer_reason: string;
|
transfer_reason: string;
|
||||||
transfer_date: string;
|
transfer_date: string;
|
||||||
source_warehouse_id: number;
|
source_warehouse_id: number;
|
||||||
@@ -71,3 +80,8 @@ export type CreateMovementPayload = {
|
|||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateMovementPayload = {
|
||||||
|
data: CreateMovementPayloadData;
|
||||||
|
documents?: File[];
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user