mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
chore: prettier format
This commit is contained in:
+11
-12
@@ -10,15 +10,15 @@ stages:
|
|||||||
paths:
|
paths:
|
||||||
- node_modules/
|
- node_modules/
|
||||||
variables:
|
variables:
|
||||||
NPM_CONFIG_PRODUCTION: "false"
|
NPM_CONFIG_PRODUCTION: 'false'
|
||||||
NODE_ENV: ""
|
NODE_ENV: ''
|
||||||
script:
|
script:
|
||||||
- echo "Installing dependencies..."
|
- echo "Installing dependencies..."
|
||||||
- npm ci --no-audit --no-fund
|
- npm ci --no-audit --no-fund
|
||||||
- echo "Building Next.js static export..."
|
- echo "Building Next.js static export..."
|
||||||
- npx next build
|
- npx next build
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "out-$CI_COMMIT_SHORT_SHA"
|
name: 'out-$CI_COMMIT_SHORT_SHA'
|
||||||
paths:
|
paths:
|
||||||
- out/
|
- out/
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
@@ -27,7 +27,7 @@ stages:
|
|||||||
stage: deploy
|
stage: deploy
|
||||||
image:
|
image:
|
||||||
name: amazon/aws-cli:latest
|
name: amazon/aws-cli:latest
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
entrypoint: ['/bin/sh', '-c']
|
||||||
script:
|
script:
|
||||||
- set -e
|
- set -e
|
||||||
- aws --version
|
- aws --version
|
||||||
@@ -106,22 +106,21 @@ build:dev:
|
|||||||
environment:
|
environment:
|
||||||
name: development
|
name: development
|
||||||
variables:
|
variables:
|
||||||
NEXT_PUBLIC_API_BASE_URL: "https://dev-api-lti.mbugroup.id"
|
NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id'
|
||||||
NEXT_PUBLIC_SSO_LOGIN_URL: "https://dev-api-sso.mbugroup.id"
|
NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-api-sso.mbugroup.id'
|
||||||
|
|
||||||
deploy:dev:
|
deploy:dev:
|
||||||
<<: *deploy_template
|
<<: *deploy_template
|
||||||
needs: ["build:dev"]
|
needs: ['build:dev']
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH == "development"'
|
- if: '$CI_COMMIT_BRANCH == "development"'
|
||||||
variables:
|
variables:
|
||||||
S3_BUCKET: "dev-lti-erp.mbugroup.id"
|
S3_BUCKET: 'dev-lti-erp.mbugroup.id'
|
||||||
AWS_REGION: "ap-southeast-3"
|
AWS_REGION: 'ap-southeast-3'
|
||||||
CLOUDFRONT_DISTRIBUTION_ID: "E1Z8XTA8XF1GIV"
|
CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV'
|
||||||
environment:
|
environment:
|
||||||
name: development
|
name: development
|
||||||
url: https://dev-lti-erp.mbugroup.id
|
url: https://dev-lti-erp.mbugroup.id
|
||||||
|
|
||||||
# ====== PRODUCTION ======
|
# ====== PRODUCTION ======
|
||||||
# build:production:
|
# build:production:
|
||||||
# <<: *build_template
|
# <<: *build_template
|
||||||
@@ -143,5 +142,5 @@ deploy:dev:
|
|||||||
# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
|
# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
|
||||||
# environment:
|
# environment:
|
||||||
# name: production
|
# name: production
|
||||||
# url: https://royalgoldcapital.com
|
# url: https://royalgoldcapital.com
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
npm run format
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run build
|
npm run build
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
version: "3.9"
|
version: '3.9'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dev-web-lti:
|
dev-web-lti:
|
||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "3002:3000"
|
- '3002:3000'
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -19,13 +19,13 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpus: "3.0"
|
cpus: '3.0'
|
||||||
memory: 3G
|
memory: 3G
|
||||||
reservations:
|
reservations:
|
||||||
cpus: "1.0"
|
cpus: '1.0'
|
||||||
memory: 512M
|
memory: 512M
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- 'host.docker.internal:host-gateway'
|
||||||
# Optional: aktifkan healthcheck jika punya endpoint
|
# Optional: aktifkan healthcheck jika punya endpoint
|
||||||
# healthcheck:
|
# healthcheck:
|
||||||
# test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/healthz || exit 1"]
|
# test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/healthz || exit 1"]
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ export const FormActions = <T,>({
|
|||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{type === 'detail' && showApproveReject && (onApprove || onReject) && (
|
{type === 'detail' &&
|
||||||
|
showApproveReject &&
|
||||||
|
(onApprove || onReject) && (
|
||||||
<>
|
<>
|
||||||
{onApprove && (
|
{onApprove && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const PatternInput = ({
|
|||||||
patternChar = '#',
|
patternChar = '#',
|
||||||
onChange,
|
onChange,
|
||||||
...restProps
|
...restProps
|
||||||
}: PatternInputProps) => {
|
}: PatternInputProps) => {
|
||||||
const valueChangeHandler: OnValueChange = (
|
const valueChangeHandler: OnValueChange = (
|
||||||
patternFormatValues,
|
patternFormatValues,
|
||||||
sourceInfo
|
sourceInfo
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({
|
|||||||
.required('Produk wajib diisi!'),
|
.required('Produk wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MovementFormSchema: Yup.ObjectSchema<MovementFormSchemaType> = Yup.object({
|
export const MovementFormSchema: Yup.ObjectSchema<MovementFormSchemaType> =
|
||||||
|
Yup.object({
|
||||||
transfer_reason: Yup.string().required('Alasan transfer wajib diisi!'),
|
transfer_reason: Yup.string().required('Alasan transfer wajib diisi!'),
|
||||||
transfer_date: Yup.string().required('Tanggal transfer wajib diisi!'),
|
transfer_date: Yup.string().required('Tanggal transfer wajib diisi!'),
|
||||||
source_warehouse: Yup.object({
|
source_warehouse: Yup.object({
|
||||||
@@ -179,7 +180,7 @@ export const MovementFormSchema: Yup.ObjectSchema<MovementFormSchemaType> = Yup.
|
|||||||
.of(DeliveryObjectSchema)
|
.of(DeliveryObjectSchema)
|
||||||
.min(1, 'Minimal harus ada 1 pengiriman!')
|
.min(1, 'Minimal harus ada 1 pengiriman!')
|
||||||
.required('Pengiriman wajib diisi!'),
|
.required('Pengiriman wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
|
export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -15,4 +15,6 @@ export const ProductCategoryFormSchema: Yup.ObjectSchema<ProductCategoryFormSche
|
|||||||
|
|
||||||
export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema;
|
export const UpdateProductCategoryFormSchema = ProductCategoryFormSchema;
|
||||||
|
|
||||||
export type ProductCategoryFormValues = Yup.InferType<typeof ProductCategoryFormSchema>;
|
export type ProductCategoryFormValues = Yup.InferType<
|
||||||
|
typeof ProductCategoryFormSchema
|
||||||
|
>;
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
|
|||||||
uom: Yup.object({
|
uom: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable().required('Satuan wajib diisi!'),
|
})
|
||||||
|
.nullable()
|
||||||
|
.required('Satuan wajib diisi!'),
|
||||||
|
|
||||||
uom_id: Yup.number()
|
uom_id: Yup.number()
|
||||||
.required('Satuan wajib diisi!')
|
.required('Satuan wajib diisi!')
|
||||||
@@ -40,7 +42,9 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
|
|||||||
product_category: Yup.object({
|
product_category: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable().required('Kategori produk wajib diisi!'),
|
})
|
||||||
|
.nullable()
|
||||||
|
.required('Kategori produk wajib diisi!'),
|
||||||
|
|
||||||
product_category_id: Yup.number()
|
product_category_id: Yup.number()
|
||||||
.required('Kategori produk wajib diisi!')
|
.required('Kategori produk wajib diisi!')
|
||||||
|
|||||||
@@ -121,9 +121,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
supplier_ids: values.supplier_ids.filter(
|
supplier_ids: values.supplier_ids.filter(
|
||||||
(id): id is number => typeof id === 'number'
|
(id): id is number => typeof id === 'number'
|
||||||
),
|
),
|
||||||
flags: values.flags.filter(
|
flags: values.flags.filter((f): f is string => typeof f === 'string'),
|
||||||
(f): f is string => typeof f === 'string'
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'add':
|
case 'add':
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { SortingState } from '@tanstack/react-table';
|
import { SortingState } from '@tanstack/react-table';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
@@ -9,9 +8,7 @@ import { useModal } from '@/components/Modal';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import { OptionType } from '@/components/input/SelectInput';
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
import SelectInput from '@/components/input/SelectInput';
|
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
|
||||||
import { TableToolbar } from '@/components/table/TableToolbar';
|
import { TableToolbar } from '@/components/table/TableToolbar';
|
||||||
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
@@ -20,14 +17,106 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
|||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
import { type CellContext } from '@tanstack/react-table';
|
import { type CellContext } from '@tanstack/react-table';
|
||||||
import { type Recording } from '@/types/api/production/recording';
|
import { type Recording } from '@/types/api/production/recording';
|
||||||
import { type BaseApiResponse } from '@/types/api/api-general';
|
|
||||||
import { RecordingApi } from '@/services/api/production';
|
const dummyRecordings: Recording[] = [
|
||||||
import { AreaApi } from '@/services/api/master-data';
|
{
|
||||||
import { LocationApi } from '@/services/api/master-data';
|
id: 1,
|
||||||
import { KandangApi } from '@/services/api/master-data';
|
flock: {
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
id: 1,
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
name: 'Flock Recording 1',
|
||||||
import toast from 'react-hot-toast';
|
created_at: '2024-01-01',
|
||||||
|
updated_at: '2024-01-01',
|
||||||
|
created_user: {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recording_date: '2024-01-01',
|
||||||
|
location: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Location 1',
|
||||||
|
address: 'Jl. Contoh No. 1',
|
||||||
|
area: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Area 1',
|
||||||
|
},
|
||||||
|
created_at: '2024-01-01',
|
||||||
|
updated_at: '2024-01-01',
|
||||||
|
created_user: {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coop: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Coop 1',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
location: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Location 1',
|
||||||
|
address: 'Jl. Contoh No. 1',
|
||||||
|
area: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Area 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pic: {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'pic@example.com',
|
||||||
|
name: 'PIC User',
|
||||||
|
},
|
||||||
|
created_at: '2024-01-01',
|
||||||
|
updated_at: '2024-01-01',
|
||||||
|
created_user: {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
capacity: 100000,
|
||||||
|
},
|
||||||
|
feed_data: [
|
||||||
|
{
|
||||||
|
feed_name: 'Feed 1',
|
||||||
|
feed_qty: 100,
|
||||||
|
feed_stock: 500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body_weight: [
|
||||||
|
{
|
||||||
|
chicken_weight: 2.5,
|
||||||
|
chicken_count: 1000,
|
||||||
|
average_chicken_weight: 2.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vaccination: [
|
||||||
|
{
|
||||||
|
vaccine_name: 'Vaccine 1',
|
||||||
|
total_stock: 200,
|
||||||
|
used_stock: 150,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mortality: [
|
||||||
|
{
|
||||||
|
condition: 'NORMAL',
|
||||||
|
count: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
created_at: '2024-01-01',
|
||||||
|
updated_at: '2024-01-01',
|
||||||
|
created_user: {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -77,34 +166,12 @@ const RowOptionsMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RecordingTable = () => {
|
const RecordingTable = () => {
|
||||||
const {
|
const [search, setSearch] = useState('');
|
||||||
state: tableFilterState,
|
const [page, setPage] = useState(1);
|
||||||
updateFilter,
|
const [pageSize, setPageSize] = useState(10);
|
||||||
setPage,
|
|
||||||
setPageSize,
|
|
||||||
toQueryString: getTableFilterQueryString,
|
|
||||||
} = useTableFilter({
|
|
||||||
initial: {
|
|
||||||
search: '',
|
|
||||||
areaFilter: '',
|
|
||||||
locationFilter: '',
|
|
||||||
kandangFilter: '',
|
|
||||||
periodFilter: '',
|
|
||||||
},
|
|
||||||
paramMap: {
|
|
||||||
page: 'page',
|
|
||||||
pageSize: 'limit',
|
|
||||||
search: 'search',
|
|
||||||
areaFilter: 'area_id',
|
|
||||||
locationFilter: 'location_id',
|
|
||||||
kandangFilter: 'kandang_id',
|
|
||||||
periodFilter: 'period',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [selectedRecordings, setSelectedRecordings] = useState<number[]>([]);
|
||||||
const [selectedRecording, setSelectedRecording] = useState<Recording | undefined>(undefined);
|
const [, setSelectedRecording] = useState<Recording | undefined>(undefined);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isBulkApproveLoading, setIsBulkApproveLoading] = useState(false);
|
const [isBulkApproveLoading, setIsBulkApproveLoading] = useState(false);
|
||||||
const [isBulkRejectLoading, setIsBulkRejectLoading] = useState(false);
|
const [isBulkRejectLoading, setIsBulkRejectLoading] = useState(false);
|
||||||
@@ -113,81 +180,12 @@ const RecordingTable = () => {
|
|||||||
const bulkApproveModal = useModal();
|
const bulkApproveModal = useModal();
|
||||||
const bulkRejectModal = useModal();
|
const bulkRejectModal = useModal();
|
||||||
|
|
||||||
// State for dropdown search
|
|
||||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
|
||||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
|
||||||
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
|
|
||||||
|
|
||||||
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
|
||||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(null);
|
|
||||||
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: recordings,
|
|
||||||
isLoading,
|
|
||||||
mutate: refreshRecordings,
|
|
||||||
} = useSWR(
|
|
||||||
`${RecordingApi.basePath}${getTableFilterQueryString()}`,
|
|
||||||
RecordingApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch data for dropdowns
|
|
||||||
const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({
|
|
||||||
search: areaSelectInputValue,
|
|
||||||
limit: '100',
|
|
||||||
}).toString()}`;
|
|
||||||
const {
|
|
||||||
data: areas,
|
|
||||||
isLoading: isLoadingAreas,
|
|
||||||
} = useSWR(areaUrl, AreaApi.getAllFetcher);
|
|
||||||
|
|
||||||
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
|
||||||
search: locationSelectInputValue,
|
|
||||||
area_id: selectedArea != null ? selectedArea.value.toString() : '',
|
|
||||||
limit: '100',
|
|
||||||
}).toString()}`;
|
|
||||||
const {
|
|
||||||
data: locations,
|
|
||||||
isLoading: isLoadingLocations,
|
|
||||||
} = useSWR(locationUrl, LocationApi.getAllFetcher);
|
|
||||||
|
|
||||||
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
|
||||||
search: kandangSelectInputValue,
|
|
||||||
location_id:
|
|
||||||
selectedLocation != null ? selectedLocation.value.toString() : '',
|
|
||||||
limit: '100',
|
|
||||||
}).toString()}`;
|
|
||||||
const {
|
|
||||||
data: kandangs,
|
|
||||||
isLoading: isLoadingKandang,
|
|
||||||
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
|
||||||
|
|
||||||
// Data to Options Mapping
|
|
||||||
const optionsArea = isResponseSuccess(areas)
|
|
||||||
? areas?.data.map((area) => ({
|
|
||||||
value: area.id,
|
|
||||||
label: area.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
const optionsLocation = isResponseSuccess(locations)
|
|
||||||
? locations?.data.map((location) => ({
|
|
||||||
value: location.id,
|
|
||||||
label: location.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
const optionsKandang = isResponseSuccess(kandangs)
|
|
||||||
? kandangs?.data.map((kandang) => ({
|
|
||||||
value: kandang.id,
|
|
||||||
label: kandang.name,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const searchChangeHandler = useCallback(
|
const searchChangeHandler = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFilter('search', e.target.value);
|
setSearch(e.target.value);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
},
|
},
|
||||||
[updateFilter, setPage]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageSizeChangeHandler = useCallback(
|
const pageSizeChangeHandler = useCallback(
|
||||||
@@ -196,80 +194,52 @@ const RecordingTable = () => {
|
|||||||
setPageSize(newVal.value as number);
|
setPageSize(newVal.value as number);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
},
|
},
|
||||||
[setPageSize, setPage]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const paginatedData = useMemo(() => {
|
const paginatedData = useMemo(() => {
|
||||||
if (!recordings || recordings.status !== 'success') return [];
|
const filteredData = dummyRecordings.filter(
|
||||||
|
(recording) =>
|
||||||
return recordings.data;
|
recording.flock.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
}, [recordings]);
|
recording.location.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
recording.coop.name.toLowerCase().includes(search.toLowerCase())
|
||||||
const selectedRowIds = Object.keys(rowSelection).map((item) => parseInt(item));
|
);
|
||||||
|
const start = (page - 1) * pageSize;
|
||||||
|
return filteredData.slice(start, start + pageSize);
|
||||||
|
}, [page, pageSize, search]);
|
||||||
|
|
||||||
const bulkApproveHandler = async () => {
|
const bulkApproveHandler = async () => {
|
||||||
setIsBulkApproveLoading(true);
|
setIsBulkApproveLoading(true);
|
||||||
|
console.log(
|
||||||
const approveResponse = await RecordingApi.customRequest<
|
'Approved recordings:',
|
||||||
BaseApiResponse<Recording[]>
|
paginatedData.filter((_, idx) => selectedRecordings.includes(idx))
|
||||||
>('approvals', {
|
);
|
||||||
method: 'POST',
|
setTimeout(() => {
|
||||||
payload: {
|
|
||||||
action: 'APPROVED',
|
|
||||||
approvable_ids: selectedRowIds,
|
|
||||||
notes: 'Bulk Approved',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isResponseSuccess(approveResponse)) {
|
|
||||||
await refreshRecordings();
|
|
||||||
setRowSelection({});
|
|
||||||
bulkApproveModal.closeModal();
|
|
||||||
toast.success(`Successfully approved ${selectedRowIds.length} recordings!`);
|
|
||||||
}
|
|
||||||
if (isResponseError(approveResponse)) {
|
|
||||||
toast.error(approveResponse?.message as string);
|
|
||||||
bulkApproveModal.closeModal();
|
|
||||||
}
|
|
||||||
setIsBulkApproveLoading(false);
|
setIsBulkApproveLoading(false);
|
||||||
|
setSelectedRecordings([]);
|
||||||
|
bulkApproveModal.closeModal();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkRejectHandler = async () => {
|
const bulkRejectHandler = async () => {
|
||||||
setIsBulkRejectLoading(true);
|
setIsBulkRejectLoading(true);
|
||||||
|
console.log(
|
||||||
const rejectResponse = await RecordingApi.customRequest<
|
'Rejected recordings:',
|
||||||
BaseApiResponse<Recording[]>
|
paginatedData.filter((_, idx) => selectedRecordings.includes(idx))
|
||||||
>('approvals', {
|
);
|
||||||
method: 'POST',
|
setTimeout(() => {
|
||||||
payload: {
|
|
||||||
action: 'REJECTED',
|
|
||||||
approvable_ids: selectedRowIds,
|
|
||||||
notes: 'Bulk Rejected',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isResponseSuccess(rejectResponse)) {
|
|
||||||
refreshRecordings();
|
|
||||||
setRowSelection({});
|
|
||||||
bulkRejectModal.closeModal();
|
|
||||||
toast.success(`Successfully rejected ${selectedRowIds.length} recordings!`);
|
|
||||||
}
|
|
||||||
if (isResponseError(rejectResponse)) {
|
|
||||||
toast.error(rejectResponse?.message as string);
|
|
||||||
bulkRejectModal.closeModal();
|
|
||||||
}
|
|
||||||
setIsBulkRejectLoading(false);
|
setIsBulkRejectLoading(false);
|
||||||
|
setSelectedRecordings([]);
|
||||||
|
bulkRejectModal.closeModal();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const singleDeleteHandler = async () => {
|
const singleDeleteHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
await RecordingApi.delete(selectedRecording?.id as number);
|
|
||||||
refreshRecordings();
|
|
||||||
|
|
||||||
singleDeleteModal.closeModal();
|
|
||||||
toast.success('Successfully delete Recording!');
|
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
|
singleDeleteModal.closeModal();
|
||||||
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -281,189 +251,21 @@ const RecordingTable = () => {
|
|||||||
label: 'Tambah',
|
label: 'Tambah',
|
||||||
}}
|
}}
|
||||||
search={{
|
search={{
|
||||||
value: tableFilterState.search,
|
value: search,
|
||||||
onChange: searchChangeHandler,
|
onChange: searchChangeHandler,
|
||||||
placeholder: 'Cari Recording',
|
placeholder: 'Cari Recording',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TableRowSizeSelector
|
<TableRowSizeSelector
|
||||||
value={tableFilterState.pageSize}
|
value={pageSize}
|
||||||
onChange={pageSizeChangeHandler}
|
onChange={pageSizeChangeHandler}
|
||||||
options={ROWS_OPTIONS}
|
options={ROWS_OPTIONS}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Filter Dropdowns - Desktop */}
|
|
||||||
<div className='hidden sm:grid sm:grid-cols-4 gap-4 mt-4'>
|
|
||||||
<SelectInput
|
|
||||||
label='Area'
|
|
||||||
placeholder='Pilih Area'
|
|
||||||
options={optionsArea}
|
|
||||||
value={selectedArea}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedArea(selectedValue);
|
|
||||||
setSelectedLocation(null);
|
|
||||||
setSelectedKandang(null);
|
|
||||||
updateFilter('areaFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
updateFilter('locationFilter', '');
|
|
||||||
updateFilter('kandangFilter', '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setAreaSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingAreas}
|
|
||||||
isClearable
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Lokasi'
|
|
||||||
placeholder='Pilih Lokasi'
|
|
||||||
options={optionsLocation}
|
|
||||||
value={selectedLocation}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedLocation(selectedValue);
|
|
||||||
setSelectedKandang(null);
|
|
||||||
updateFilter('locationFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
updateFilter('kandangFilter', '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setLocationSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingLocations}
|
|
||||||
isClearable
|
|
||||||
isDisabled={!selectedArea}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Kandang'
|
|
||||||
placeholder='Pilih Kandang'
|
|
||||||
options={optionsKandang}
|
|
||||||
value={selectedKandang}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedKandang(selectedValue);
|
|
||||||
updateFilter('kandangFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setKandangSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingKandang}
|
|
||||||
isClearable
|
|
||||||
isDisabled={!selectedLocation}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Periode'
|
|
||||||
placeholder='Pilih Periode'
|
|
||||||
options={[
|
|
||||||
{ value: '1', label: 'Periode 1' },
|
|
||||||
{ value: '2', label: 'Periode 2' },
|
|
||||||
{ value: '3', label: 'Periode 3' },
|
|
||||||
]}
|
|
||||||
value={
|
|
||||||
tableFilterState.periodFilter
|
|
||||||
? { value: tableFilterState.periodFilter, label: `Periode ${tableFilterState.periodFilter}` }
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
updateFilter('periodFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
isClearable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filter Dropdowns - Mobile */}
|
|
||||||
<div className='sm:hidden flex flex-col gap-3 mt-4'>
|
|
||||||
<SelectInput
|
|
||||||
label='Area'
|
|
||||||
placeholder='Pilih Area'
|
|
||||||
options={optionsArea}
|
|
||||||
value={selectedArea}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedArea(selectedValue);
|
|
||||||
setSelectedLocation(null);
|
|
||||||
setSelectedKandang(null);
|
|
||||||
updateFilter('areaFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
updateFilter('locationFilter', '');
|
|
||||||
updateFilter('kandangFilter', '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setAreaSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingAreas}
|
|
||||||
isClearable
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Lokasi'
|
|
||||||
placeholder='Pilih Lokasi'
|
|
||||||
options={optionsLocation}
|
|
||||||
value={selectedLocation}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedLocation(selectedValue);
|
|
||||||
setSelectedKandang(null);
|
|
||||||
updateFilter('locationFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
updateFilter('kandangFilter', '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setLocationSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingLocations}
|
|
||||||
isClearable
|
|
||||||
isDisabled={!selectedArea}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Kandang'
|
|
||||||
placeholder='Pilih Kandang'
|
|
||||||
options={optionsKandang}
|
|
||||||
value={selectedKandang}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
setSelectedKandang(selectedValue);
|
|
||||||
updateFilter('kandangFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
onInputChange={(value) => setKandangSelectInputValue(value)}
|
|
||||||
isLoading={isLoadingKandang}
|
|
||||||
isClearable
|
|
||||||
isDisabled={!selectedLocation}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SelectInput
|
|
||||||
label='Periode'
|
|
||||||
placeholder='Pilih Periode'
|
|
||||||
options={[
|
|
||||||
{ value: '1', label: 'Periode 1' },
|
|
||||||
{ value: '2', label: 'Periode 2' },
|
|
||||||
{ value: '3', label: 'Periode 3' },
|
|
||||||
]}
|
|
||||||
value={
|
|
||||||
tableFilterState.periodFilter
|
|
||||||
? { value: tableFilterState.periodFilter, label: `Periode ${tableFilterState.periodFilter}` }
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
onChange={(selected) => {
|
|
||||||
const selectedValue = selected as OptionType | null;
|
|
||||||
updateFilter('periodFilter', selectedValue ? selectedValue.value.toString() : '');
|
|
||||||
setPage(1);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
|
||||||
isClearable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bulk action buttons */}
|
{/* Bulk action buttons */}
|
||||||
<div className={'flex justify-end items-center'}>
|
<div className={'flex justify-end items-center'}>
|
||||||
{selectedRowIds.length > 0 && (
|
{selectedRecordings.length > 0 && (
|
||||||
<div className='flex gap-2 mb-4'>
|
<div className='flex gap-2 mb-4'>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -476,7 +278,7 @@ const RecordingTable = () => {
|
|||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
/>
|
/>
|
||||||
Approve ({selectedRowIds.length})
|
Approve ({selectedRecordings.length})
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -489,7 +291,7 @@ const RecordingTable = () => {
|
|||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
/>
|
/>
|
||||||
Reject ({selectedRowIds.length})
|
Reject ({selectedRecordings.length})
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -497,7 +299,7 @@ const RecordingTable = () => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={bulkApproveModal.ref}
|
ref={bulkApproveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
text={`Apakah anda yakin ingin menyetujui ${selectedRowIds.length} data Recording yang dipilih?`}
|
text={`Apakah anda yakin ingin menyetujui ${selectedRecordings.length} data Recording yang dipilih?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
@@ -512,7 +314,7 @@ const RecordingTable = () => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={bulkRejectModal.ref}
|
ref={bulkRejectModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={`Apakah anda yakin ingin menolak ${selectedRowIds.length} data Recording yang dipilih?`}
|
text={`Apakah anda yakin ingin menolak ${selectedRecordings.length} data Recording yang dipilih?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
@@ -530,83 +332,75 @@ const RecordingTable = () => {
|
|||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
|
accessorKey: 'id',
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
<div className='w-full flex flex-row justify-center'>
|
<input
|
||||||
<CheckboxInput
|
type='checkbox'
|
||||||
name='allRow'
|
className='checkbox'
|
||||||
checked={table.getIsAllRowsSelected()}
|
checked={
|
||||||
indeterminate={table.getIsSomeRowsSelected()}
|
table.getRowModel().rows.length > 0 &&
|
||||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
table
|
||||||
|
.getRowModel()
|
||||||
|
.rows.every((row) => selectedRecordings.includes(row.index))
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedRecordings(
|
||||||
|
table.getRowModel().rows.map((row) => row.index)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSelectedRecordings([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div>
|
<input
|
||||||
<CheckboxInput
|
type='checkbox'
|
||||||
name='row'
|
className='checkbox'
|
||||||
checked={row.getIsSelected()}
|
checked={selectedRecordings.includes(row.index)}
|
||||||
disabled={!row.getCanSelect()}
|
onChange={(e) => {
|
||||||
indeterminate={row.getIsSomeSelected()}
|
if (e.target.checked) {
|
||||||
onChange={row.getToggleSelectedHandler()}
|
setSelectedRecordings([...selectedRecordings, row.index]);
|
||||||
|
} else {
|
||||||
|
setSelectedRecordings(
|
||||||
|
selectedRecordings.filter((i) => i !== row.index)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: '#',
|
header: '#',
|
||||||
cell: (props) => tableFilterState.pageSize * (tableFilterState.page - 1) + props.row.index + 1,
|
cell: (props) => pageSize * (page - 1) + props.row.index + 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Nama Project',
|
accessorKey: 'flock.name',
|
||||||
cell: (props) => `Project ${props.row.original.project_flock_kandang_id}`,
|
header: 'Flock',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Umur (hari)',
|
accessorKey: 'recording_date',
|
||||||
cell: (props) => props.row.original.day,
|
header: 'Tanggal Recording',
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'record_date',
|
|
||||||
header: 'Waktu Recording',
|
|
||||||
cell: (props) =>
|
cell: (props) =>
|
||||||
new Date(props.row.original.record_date).toLocaleDateString(),
|
new Date(props.row.original.recording_date).toLocaleDateString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Populasi Awal',
|
accessorKey: 'location.name',
|
||||||
cell: (props) => props.row.original.total_chick?.toLocaleString() || '-',
|
header: 'Lokasi',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'BW',
|
accessorKey: 'coop.name',
|
||||||
cell: (props) => props.row.original.avg_daily_gain?.toFixed(2) || '-',
|
header: 'Kandang',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Pakan',
|
accessorKey: 'mortality',
|
||||||
cell: (props) => props.row.original.cum_intake?.toLocaleString() || '-',
|
header: 'Total Mortality',
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'FCR',
|
|
||||||
cell: (props) => props.row.original.fcr_value?.toFixed(2) || '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'total_depletion',
|
|
||||||
header: 'Total Deplesi',
|
|
||||||
cell: (props) => props.row.original.total_depletion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Deplesi (%)',
|
|
||||||
cell: (props) => props.row.original.daily_depletion_rate?.toFixed(2) || '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Populasi Akhir',
|
|
||||||
cell: (props) => (props.row.original.total_chick - props.row.original.total_depletion)?.toLocaleString() || '-',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Ketepatan Waktu',
|
|
||||||
cell: (props) => props.row.original.ontime ? 'Tepat Waktu' : 'Terlambat',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Tanggal Submit',
|
|
||||||
cell: (props) =>
|
cell: (props) =>
|
||||||
new Date(props.row.original.created_at).toLocaleString(),
|
props.row.original.mortality.reduce(
|
||||||
|
(acc, curr) => acc + curr.count,
|
||||||
|
0
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
@@ -651,15 +445,13 @@ const RecordingTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
pageSize={tableFilterState.pageSize}
|
pageSize={pageSize}
|
||||||
page={recordings?.status === 'success' ? recordings.meta?.page : tableFilterState.page}
|
page={page}
|
||||||
totalItems={recordings?.status === 'success' ? recordings.meta?.total_results : 0}
|
totalItems={dummyRecordings.length}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
isLoading={isLoading}
|
isLoading={false}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
rowSelection={rowSelection}
|
|
||||||
setRowSelection={setRowSelection}
|
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn({
|
containerClassName: cn({
|
||||||
'mb-20': paginatedData.length === 0,
|
'mb-20': paginatedData.length === 0,
|
||||||
@@ -678,7 +470,7 @@ const RecordingTable = () => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={singleDeleteModal.ref}
|
ref={singleDeleteModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={`Apakah anda yakin ingin menghapus data Recording ini (ID: ${selectedRecording?.id})?`}
|
text={`Apakah anda yakin ingin menghapus data Recording ini?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,222 +1,212 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
|
||||||
import {
|
import { Recording } from '@/types/api/production/recording';
|
||||||
Recording,
|
|
||||||
CreateRecordingPayload,
|
|
||||||
} from '@/types/api/production/recording';
|
|
||||||
|
|
||||||
export const RecordingFormSchema = Yup.object({
|
export const RecordingFormSchema = Yup.object({
|
||||||
project_flock_kandang: Yup.object({
|
flock: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
project_flock_kandang_id: Yup.number()
|
flock_id: Yup.number()
|
||||||
.default(0)
|
.default(0)
|
||||||
.typeError('Project Flock Kandang wajib diisi!')
|
.typeError('Flock wajib diisi!')
|
||||||
.test(
|
.test(
|
||||||
'is-valid-project-flock-kandang',
|
'is-valid-flock',
|
||||||
'Project Flock Kandang wajib diisi!',
|
'Flock wajib diisi!',
|
||||||
(value) => value !== undefined && value !== null && value > 0
|
(value) => value !== undefined && value !== null && value > 0
|
||||||
)
|
)
|
||||||
.required('Project Flock Kandang wajib diisi!')
|
.required('Flock wajib diisi!'),
|
||||||
|
location: Yup.object({
|
||||||
|
value: Yup.number().min(1).required(),
|
||||||
|
label: Yup.string().required(),
|
||||||
|
}).nullable(),
|
||||||
|
location_id: Yup.number()
|
||||||
|
.default(0)
|
||||||
|
.typeError('Lokasi wajib diisi!')
|
||||||
.test(
|
.test(
|
||||||
'not-already-recorded',
|
'is-valid-location',
|
||||||
'Project Flock ini sudah direcord hari ini!',
|
'Lokasi wajib diisi!',
|
||||||
function(value) {
|
(value) => value !== undefined && value !== null && value > 0
|
||||||
const recordedProjectFlockIds = this.options.context?.recordedProjectFlockIds as Set<number>;
|
)
|
||||||
const formType = this.options.context?.type as 'add' | 'edit' | 'detail';
|
.required('Lokasi wajib diisi!'),
|
||||||
if (formType !== 'add') return true;
|
coop: Yup.object({
|
||||||
if (value && recordedProjectFlockIds?.has(value)) {
|
value: Yup.number().min(1).required(),
|
||||||
return false;
|
label: Yup.string().required(),
|
||||||
}
|
}).nullable(),
|
||||||
|
coop_id: Yup.number()
|
||||||
|
.default(0)
|
||||||
|
.typeError('Kandang wajib diisi!')
|
||||||
|
.test(
|
||||||
|
'is-valid-coop',
|
||||||
|
'Kandang wajib diisi!',
|
||||||
|
(value) => value !== undefined && value !== null && value > 0
|
||||||
|
)
|
||||||
|
.required('Kandang wajib diisi!'),
|
||||||
|
recording_date: Yup.date()
|
||||||
|
.required('Tanggal recording wajib diisi')
|
||||||
|
.typeError('Format tanggal tidak valid'),
|
||||||
|
feed_data: Yup.array()
|
||||||
|
.of(
|
||||||
|
Yup.object({
|
||||||
|
feed_id: Yup.string().required('Nama pakan wajib diisi!'),
|
||||||
|
feed_qty: Yup.mixed<number | ''>().notRequired(),
|
||||||
|
feed_stock: Yup.number()
|
||||||
|
.required('Jumlah pakan yang digunakan wajib diisi!')
|
||||||
|
.min(1, 'Jumlah pakan minimal 1!')
|
||||||
|
.typeError('Jumlah pakan yang digunakan harus berupa angka!')
|
||||||
|
.test(
|
||||||
|
'is-not-exceed-qty',
|
||||||
|
'Jumlah pakan yang digunakan tidak boleh melebihi stok tersedia!',
|
||||||
|
function (value) {
|
||||||
|
const { feed_qty } = this.parent;
|
||||||
|
if (value === undefined) return true;
|
||||||
|
if (
|
||||||
|
feed_qty === undefined ||
|
||||||
|
feed_qty === '' ||
|
||||||
|
typeof feed_qty !== 'number'
|
||||||
|
)
|
||||||
return true;
|
return true;
|
||||||
|
return value <= feed_qty;
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
body_weights: Yup.array()
|
})
|
||||||
|
)
|
||||||
|
.min(1, 'Minimal harus ada 1 data pakan!')
|
||||||
|
.required('Data pakan wajib diisi!'),
|
||||||
|
body_weight: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
weight: Yup.number()
|
chicken_weight: Yup.number()
|
||||||
.required('Berat ayam wajib diisi!')
|
.required('Berat ayam wajib diisi!')
|
||||||
.min(1, 'Berat ayam minimal 1 gram!')
|
.min(1, 'Berat ayam minimal 1 gram!')
|
||||||
.typeError('Berat ayam harus berupa angka!'),
|
.typeError('Berat ayam harus berupa angka!'),
|
||||||
qty: Yup.number()
|
chicken_count: Yup.number()
|
||||||
.required('Jumlah ayam wajib diisi!')
|
.required('Jumlah ayam wajib diisi!')
|
||||||
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
||||||
.typeError('Jumlah ayam harus berupa angka!')
|
.typeError('Jumlah ayam harus berupa angka!'),
|
||||||
.default(1),
|
average_chicken_weight: Yup.number()
|
||||||
average_weight: Yup.number()
|
.required('Rata-rata berat ayam wajib diisi!')
|
||||||
.optional()
|
.min(1, 'Rata-rata berat ayam minimal 1 gram!')
|
||||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
.typeError('Rata-rata berat ayam harus berupa angka!'),
|
||||||
.typeError('Rata-rata berat harus berupa angka!')
|
|
||||||
.default(0),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
||||||
.required('Data bobot badan wajib diisi!'),
|
.required('Data bobot badan wajib diisi!'),
|
||||||
stocks: Yup.array()
|
vaccination: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
product_warehouse_id: Yup.number()
|
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'),
|
||||||
.required('Produk wajib diisi!')
|
total_stock: Yup.mixed<number | ''>().notRequired(),
|
||||||
.min(1, 'Produk wajib diisi!')
|
used_stock: Yup.number()
|
||||||
.typeError('Produk harus berupa angka!'),
|
.required('Jumlah vaksin yang digunakan wajib diisi!')
|
||||||
usage_amount: Yup.number()
|
.min(1, 'Jumlah vaksin minimal 1!')
|
||||||
.required('Jumlah penggunaan wajib diisi!')
|
.typeError('Jumlah vaksin yang digunakan harus berupa angka!')
|
||||||
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
.test(
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
'is-not-exceed-total',
|
||||||
notes: Yup.string().optional(),
|
'Jumlah vaksin yang digunakan tidak boleh melebihi stok tersedia!',
|
||||||
|
function (value) {
|
||||||
|
const { total_stock } = this.parent;
|
||||||
|
if (value === undefined) return true;
|
||||||
|
if (
|
||||||
|
total_stock === undefined ||
|
||||||
|
total_stock === '' ||
|
||||||
|
typeof total_stock !== 'number'
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
return value <= total_stock;
|
||||||
|
}
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data stok!')
|
.min(1, 'Minimal harus ada 1 data vaksinasi!')
|
||||||
.required('Data stok wajib diisi!'),
|
.required('Data vaksinasi wajib diisi!'),
|
||||||
depletions: Yup.array()
|
mortality: Yup.array()
|
||||||
.of(
|
.of(
|
||||||
Yup.object({
|
Yup.object({
|
||||||
total: Yup.number()
|
condition: Yup.mixed<string>()
|
||||||
.required('Jumlah depletions wajib diisi!')
|
|
||||||
.min(1, 'Jumlah depletions minimal 1!')
|
|
||||||
.typeError('Jumlah depletions harus berupa angka!'),
|
|
||||||
notes: Yup.string()
|
|
||||||
.required('Kondisi depletions wajib diisi!')
|
|
||||||
.oneOf(
|
.oneOf(
|
||||||
RECORDING_FLAG_OPTIONS.map((option) => option.value),
|
RECORDING_FLAG_OPTIONS.map((opt) => opt.value),
|
||||||
'Kondisi depletions tidak valid!'
|
'Kondisi tidak valid!'
|
||||||
)
|
)
|
||||||
.typeError('Kondisi depletions harus berupa teks!')
|
.required('Kondisi wajib diisi!'),
|
||||||
.min(1, 'Kondisi depletions wajib diisi!'),
|
count: Yup.number()
|
||||||
|
.required('Jumlah mortalitas wajib diisi!')
|
||||||
|
.min(1, 'Jumlah mortalitas minimal 1 ekor!')
|
||||||
|
.typeError('Jumlah mortalitas harus berupa angka!'),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'Minimal harus ada 1 data depletions!')
|
.min(1, 'Minimal harus ada 1 data mortalitas!')
|
||||||
.required('Data depletions wajib diisi!'),
|
.required('Data mortalitas wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateRecordingFormSchema = Yup.object({
|
export const UpdateRecordingFormSchema = RecordingFormSchema;
|
||||||
project_flock_kandang: Yup.object({
|
|
||||||
value: Yup.number().min(1).required(),
|
|
||||||
label: Yup.string().required(),
|
|
||||||
}).nullable(),
|
|
||||||
project_flock_kandang_id: Yup.number()
|
|
||||||
.default(0)
|
|
||||||
.typeError('Project Flock Kandang wajib diisi!')
|
|
||||||
.test(
|
|
||||||
'is-valid-project-flock-kandang',
|
|
||||||
'Project Flock Kandang wajib diisi!',
|
|
||||||
(value) => value !== undefined && value !== null && value > 0
|
|
||||||
)
|
|
||||||
.required('Project Flock Kandang wajib diisi!'),
|
|
||||||
body_weights: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
weight: Yup.number()
|
|
||||||
.required('Berat ayam wajib diisi!')
|
|
||||||
.min(1, 'Berat ayam minimal 1 gram!')
|
|
||||||
.typeError('Berat ayam harus berupa angka!'),
|
|
||||||
qty: Yup.number()
|
|
||||||
.required('Jumlah ayam wajib diisi!')
|
|
||||||
.min(1, 'Jumlah ayam minimal 1 ekor!')
|
|
||||||
.typeError('Jumlah ayam harus berupa angka!')
|
|
||||||
.default(1),
|
|
||||||
average_weight: Yup.number()
|
|
||||||
.optional()
|
|
||||||
.min(0, 'Rata-rata berat tidak boleh negatif!')
|
|
||||||
.typeError('Rata-rata berat harus berupa angka!')
|
|
||||||
.default(0),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data bobot badan!')
|
|
||||||
.required('Data bobot badan wajib diisi!'),
|
|
||||||
stocks: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
product_warehouse_id: Yup.number()
|
|
||||||
.required('Produk wajib diisi!')
|
|
||||||
.min(1, 'Produk wajib diisi!')
|
|
||||||
.typeError('Produk harus berupa angka!'),
|
|
||||||
usage_amount: Yup.number()
|
|
||||||
.required('Jumlah penggunaan wajib diisi!')
|
|
||||||
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
|
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
|
||||||
notes: Yup.string().optional(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data stok!')
|
|
||||||
.required('Data stok wajib diisi!'),
|
|
||||||
depletions: Yup.array()
|
|
||||||
.of(
|
|
||||||
Yup.object({
|
|
||||||
total: Yup.number()
|
|
||||||
.required('Jumlah depletions wajib diisi!')
|
|
||||||
.min(1, 'Jumlah depletions minimal 1!')
|
|
||||||
.typeError('Jumlah depletions harus berupa angka!'),
|
|
||||||
notes: Yup.string()
|
|
||||||
.required('Kondisi depletions wajib diisi!')
|
|
||||||
.oneOf(
|
|
||||||
RECORDING_FLAG_OPTIONS.map((option) => option.value),
|
|
||||||
'Kondisi depletions tidak valid!'
|
|
||||||
)
|
|
||||||
.typeError('Kondisi depletions harus berupa teks!')
|
|
||||||
.min(1, 'Kondisi depletions wajib diisi!'),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.min(1, 'Minimal harus ada 1 data depletions!')
|
|
||||||
.required('Data depletions wajib diisi!'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type RecordingFormValues = Yup.InferType<typeof RecordingFormSchema>;
|
export type RecordingFormValues = Yup.InferType<typeof RecordingFormSchema>;
|
||||||
|
|
||||||
type RecordingFormData = Partial<Recording> & {
|
|
||||||
body_weights?: CreateRecordingPayload['body_weights'];
|
|
||||||
stocks?: CreateRecordingPayload['stocks'];
|
|
||||||
depletions?: CreateRecordingPayload['depletions'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRecordingFormInitialValues = (
|
export const getRecordingFormInitialValues = (
|
||||||
initialValues?: RecordingFormData
|
initialValues?: Recording
|
||||||
): RecordingFormValues => ({
|
): RecordingFormValues => ({
|
||||||
project_flock_kandang: initialValues?.project_flock_kandang_id
|
flock: initialValues?.flock
|
||||||
? {
|
? {
|
||||||
value: initialValues.project_flock_kandang_id,
|
value: initialValues.flock.id,
|
||||||
label: `Project Flock #${initialValues.project_flock_kandang_id}`,
|
label: initialValues.flock.name,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
|
flock_id: initialValues?.flock?.id ?? 0,
|
||||||
body_weights: initialValues?.body_weights?.map(
|
location: initialValues?.location
|
||||||
(bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
|
? {
|
||||||
weight: bw.weight,
|
value: initialValues.location.id,
|
||||||
qty: bw.qty,
|
label: initialValues.location.name,
|
||||||
average_weight: bw.qty > 0 ? Math.round(bw.weight / bw.qty) : 0,
|
}
|
||||||
})
|
: null,
|
||||||
) ?? [
|
location_id: initialValues?.location?.id ?? 0,
|
||||||
|
coop: initialValues?.coop
|
||||||
|
? {
|
||||||
|
value: initialValues.coop.id,
|
||||||
|
label: initialValues.coop.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
coop_id: initialValues?.coop?.id ?? 0,
|
||||||
|
recording_date: initialValues?.recording_date
|
||||||
|
? new Date(initialValues.recording_date)
|
||||||
|
: new Date(),
|
||||||
|
feed_data: initialValues?.feed_data
|
||||||
|
? initialValues.feed_data.map((feed) => ({
|
||||||
|
feed_id: feed.feed_name,
|
||||||
|
feed_qty: feed.feed_qty,
|
||||||
|
feed_stock: feed.feed_stock,
|
||||||
|
}))
|
||||||
|
: [
|
||||||
{
|
{
|
||||||
weight: 0,
|
feed_id: '',
|
||||||
qty: 0,
|
feed_qty: '',
|
||||||
average_weight: 0,
|
feed_stock: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
stocks: initialValues?.stocks?.map(
|
body_weight: initialValues?.body_weight ?? [
|
||||||
(stock: NonNullable<CreateRecordingPayload['stocks']>[0]) => ({
|
|
||||||
product_warehouse_id: stock.product_warehouse_id,
|
|
||||||
usage_amount: stock.usage_amount,
|
|
||||||
notes: stock.notes,
|
|
||||||
})
|
|
||||||
) ?? [
|
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
chicken_weight: 0,
|
||||||
usage_amount: 0,
|
chicken_count: 0,
|
||||||
notes: '',
|
average_chicken_weight: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
depletions: initialValues?.depletions?.map(
|
vaccination: initialValues?.vaccination
|
||||||
(depletion: NonNullable<CreateRecordingPayload['depletions']>[0]) => ({
|
? initialValues.vaccination.map((vaccine) => ({
|
||||||
product_warehouse_id: depletion.product_warehouse_id,
|
vaccine_id: vaccine.vaccine_name,
|
||||||
total: depletion.total,
|
total_stock: vaccine.total_stock,
|
||||||
notes: depletion.notes,
|
used_stock: vaccine.used_stock,
|
||||||
})
|
}))
|
||||||
) ?? [
|
: [
|
||||||
{
|
{
|
||||||
product_warehouse_id: 0,
|
vaccine_id: '',
|
||||||
total: 0,
|
total_stock: '',
|
||||||
notes: '',
|
used_stock: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mortality: initialValues?.mortality ?? [
|
||||||
|
{
|
||||||
|
condition: '',
|
||||||
|
count: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const useRecordingFormHandlers = (initialValuesId?: number) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.success(res?.message as string);
|
toast.success(res?.message as string);
|
||||||
router.push('/production/recording');
|
router.push('/flock/recording');
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
@@ -38,7 +38,7 @@ export const useRecordingFormHandlers = (initialValuesId?: number) => {
|
|||||||
}
|
}
|
||||||
toast.success(res?.message as string);
|
toast.success(res?.message as string);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
router.push('/production/recording');
|
router.push('/flock/recording');
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
);
|
);
|
||||||
@@ -55,7 +55,7 @@ export const useRecordingFormHandlers = (initialValuesId?: number) => {
|
|||||||
deleteModal.closeModal();
|
deleteModal.closeModal();
|
||||||
toast.success('Successfully delete Recording!');
|
toast.success('Successfully delete Recording!');
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
router.push('/production/recording');
|
router.push('/flock/recording');
|
||||||
}, [deleteModal, initialValuesId, router]);
|
}, [deleteModal, initialValuesId, router]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ export class StaffApprovalService extends BaseApiService<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class AcceptApprovalService extends BaseApiService<
|
export class AcceptApprovalService extends BaseApiService<
|
||||||
Purchase,
|
Purchase,
|
||||||
|
|||||||
+48
-33
@@ -1,45 +1,60 @@
|
|||||||
import { BaseMetadata, User } from '@/types/api/api-general';
|
import { BaseMetadata } from '@/types/api/api-general';
|
||||||
|
import { Location } from '@/types/api/master-data/location';
|
||||||
export type ProductionMetrics = {
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
total_depletion: number;
|
import { Flock } from '@/types/api/master-data/flock';
|
||||||
cum_depletion_rate: number;
|
|
||||||
daily_gain: number;
|
|
||||||
avg_daily_gain: number;
|
|
||||||
cum_intake: number;
|
|
||||||
fcr_value: number;
|
|
||||||
total_chick: number;
|
|
||||||
daily_depletion_rate: number;
|
|
||||||
cum_depletion: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BaseRecording = {
|
export type BaseRecording = {
|
||||||
id: number;
|
id: number;
|
||||||
project_flock_kandang_id: number;
|
flock: Flock;
|
||||||
record_datetime: string;
|
recording_date: string;
|
||||||
record_date: string;
|
location: Location;
|
||||||
status: number;
|
coop: Kandang;
|
||||||
ontime: boolean;
|
feed_data: {
|
||||||
day: number;
|
feed_name: string;
|
||||||
created_user: User;
|
feed_qty: number;
|
||||||
} & ProductionMetrics;
|
feed_stock: number;
|
||||||
|
}[];
|
||||||
|
body_weight: {
|
||||||
|
chicken_weight: number;
|
||||||
|
chicken_count: number;
|
||||||
|
average_chicken_weight: number;
|
||||||
|
}[];
|
||||||
|
vaccination: {
|
||||||
|
vaccine_name: string;
|
||||||
|
total_stock: number;
|
||||||
|
used_stock: number;
|
||||||
|
}[];
|
||||||
|
mortality: {
|
||||||
|
condition: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type Recording = BaseMetadata & BaseRecording;
|
export type Recording = BaseMetadata & BaseRecording;
|
||||||
|
|
||||||
export type CreateRecordingPayload = {
|
export type CreateRecordingPayload = {
|
||||||
project_flock_kandang_id: number;
|
flock_id: number;
|
||||||
body_weights: {
|
recording_date: string;
|
||||||
weight: number;
|
location_id: number;
|
||||||
qty: number;
|
coop_id: number;
|
||||||
|
feed_data: {
|
||||||
|
feed_id: string;
|
||||||
|
feed_qty: number;
|
||||||
|
feed_stock: number;
|
||||||
}[];
|
}[];
|
||||||
stocks?: {
|
body_weight: {
|
||||||
product_warehouse_id: number;
|
chicken_weight: number;
|
||||||
usage_amount: number;
|
chicken_count: number;
|
||||||
notes: string;
|
average_chicken_weight: number;
|
||||||
}[];
|
}[];
|
||||||
depletions?: {
|
vaccination: {
|
||||||
product_warehouse_id?: number;
|
vaccine_id: string;
|
||||||
total: number;
|
total_stock: number;
|
||||||
notes: string;
|
used_stock: number;
|
||||||
|
}[];
|
||||||
|
mortality: {
|
||||||
|
condition: string;
|
||||||
|
count: number;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user