Merge branch 'staging' into 'production'

Staging

See merge request mbugroup/lti-web-client!261
This commit is contained in:
Adnan Zahir
2026-01-27 10:36:16 +07:00
19 changed files with 735 additions and 212 deletions
+5 -1
View File
@@ -25,5 +25,9 @@ export default function Home() {
); );
} }
return <>Loading...</>; return (
<main className='w-full h-full min-h-screen flex flex-row justify-center items-center'>
<span className='loading loading-spinner loading-lg'></span>
</main>
);
} }
+71 -10
View File
@@ -1,11 +1,12 @@
'use client'; 'use client';
import { ReactNode, useCallback, useEffect, useState } from 'react'; import { Fragment, ReactNode, useCallback, useEffect, useState } from 'react';
import { import {
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getExpandedRowModel,
getSortedRowModel, getSortedRowModel,
TableOptions, TableOptions,
useReactTable, useReactTable,
@@ -15,6 +16,7 @@ import {
OnChangeFn, OnChangeFn,
Row, Row,
HeaderContext, HeaderContext,
ExpandedState,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { rankItem } from '@tanstack/match-sorter-utils'; import { rankItem } from '@tanstack/match-sorter-utils';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
@@ -33,6 +35,9 @@ interface TableClassNames {
bodyRowClassName?: string; bodyRowClassName?: string;
selectedBodyRowClassName?: string; selectedBodyRowClassName?: string;
bodyColumnClassName?: string; bodyColumnClassName?: string;
bodySubRowClassName?: (depth: number) => string;
selectedBodySubRowClassName?: (depth: number) => string;
bodySubRowColumnClassName?: (depth: number) => string;
tableFooterClassName?: string; tableFooterClassName?: string;
footerRowClassName?: string; footerRowClassName?: string;
footerColumnClassName?: string; footerColumnClassName?: string;
@@ -60,6 +65,7 @@ export interface TableProps<TData extends object> {
enableRowSelection?: boolean | ((row: Row<TData>) => boolean); enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
renderFooter?: boolean; renderFooter?: boolean;
withCheckbox?: boolean; withCheckbox?: boolean;
withPagination?: boolean;
rowOptions?: number[]; rowOptions?: number[];
/** /**
* Custom row renderer. Should return a complete <tr> element or null. * Custom row renderer. Should return a complete <tr> element or null.
@@ -67,6 +73,10 @@ export interface TableProps<TData extends object> {
* Return null to render the default row. * Return null to render the default row.
*/ */
renderCustomRow?: (row: Row<TData>) => ReactNode | null; renderCustomRow?: (row: Row<TData>) => ReactNode | null;
getRowCanExpand?: (row: Row<TData>) => boolean;
renderSubComponent?: (props: { row: Row<TData> }) => React.ReactElement;
expanded?: ExpandedState;
getSubRows?: (originalRow: TData, index: number) => TData[] | undefined;
} }
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}]; const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
@@ -92,7 +102,12 @@ export const TABLE_DEFAULT_STYLING = {
bodyRowClassName: bodyRowClassName:
'transition-all duration-200 border-t border-base-content/10 bg-transparent', 'transition-all duration-200 border-t border-base-content/10 bg-transparent',
selectedBodyRowClassName: 'bg-primary/5', selectedBodyRowClassName: 'bg-primary/5',
bodyColumnClassName: 'px-4 py-3 text-base-content', bodyColumnClassName: 'px-4 py-3 text-base-content font-medium',
bodySubRowClassName: (depth: number) =>
'transition-all duration-200 border-t border-base-content/10 bg-transparent',
selectedBodySubRowClassName: (depth: number) => 'bg-primary/5',
bodySubRowColumnClassName: (depth: number) =>
'px-4 py-3 text-base-content font-medium',
paginationClassName: 'px-3', paginationClassName: 'px-3',
tableFooterClassName: 'font-semibold border-base-content/10', tableFooterClassName: 'font-semibold border-base-content/10',
footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10', footerRowClassName: 'bg-base-200 border-t-2 border-base-content/10',
@@ -120,8 +135,13 @@ const Table = <TData extends object>({
enableRowSelection, enableRowSelection,
renderFooter = false, renderFooter = false,
withCheckbox = false, withCheckbox = false,
withPagination = true,
rowOptions = [10, 20, 50, 100], rowOptions = [10, 20, 50, 100],
renderCustomRow, renderCustomRow,
getRowCanExpand,
renderSubComponent,
expanded = {},
getSubRows,
}: TableProps<TData>) => { }: TableProps<TData>) => {
const isServerSideTable = const isServerSideTable =
totalItems !== undefined && totalItems !== undefined &&
@@ -154,10 +174,14 @@ const Table = <TData extends object>({
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination, onPaginationChange: setPagination,
getExpandedRowModel: getExpandedRowModel(),
getRowCanExpand: getRowCanExpand ?? (getSubRows ? undefined : () => false),
getSubRows,
manualSorting, manualSorting,
state: { state: {
pagination, pagination,
globalFilter: fuzzySearchValue, globalFilter: fuzzySearchValue,
expanded,
}, },
filterFns: { filterFns: {
fuzzy: fuzzyFilter, fuzzy: fuzzyFilter,
@@ -228,7 +252,10 @@ const Table = <TData extends object>({
<div <div
className={cn( className={cn(
TABLE_DEFAULT_STYLING.containerClassName, TABLE_DEFAULT_STYLING.containerClassName,
tableClassNames.containerClassName tableClassNames.containerClassName,
{
'mb-0': !withPagination,
}
)} )}
> >
<div <div
@@ -352,14 +379,19 @@ const Table = <TData extends object>({
} }
return ( return (
<Fragment key={row.id}>
<tr <tr
key={row.id} data-depth={row.depth}
className={cn( className={cn(
TABLE_DEFAULT_STYLING.bodyRowClassName, row.depth > 0
tableClassNames.bodyRowClassName, ? tableClassNames.bodySubRowClassName(row.depth)
: tableClassNames.bodyRowClassName,
{ {
[tableClassNames.selectedBodyRowClassName]: [tableClassNames.selectedBodyRowClassName!]:
row.getIsSelected(), row.getIsSelected() && row.depth === 0,
[tableClassNames.selectedBodySubRowClassName(
row.depth
)!]: row.getIsSelected() && row.depth > 0,
} }
)} )}
> >
@@ -369,7 +401,11 @@ const Table = <TData extends object>({
className={cn( className={cn(
{ 'first:w-9 first:pr-0': withCheckbox }, { 'first:w-9 first:pr-0': withCheckbox },
TABLE_DEFAULT_STYLING.bodyColumnClassName, TABLE_DEFAULT_STYLING.bodyColumnClassName,
tableClassNames.bodyColumnClassName row.depth > 0
? tableClassNames.bodySubRowColumnClassName(
row.depth
)
: tableClassNames.bodyColumnClassName
)} )}
> >
{!isLoading && {!isLoading &&
@@ -382,6 +418,28 @@ const Table = <TData extends object>({
</td> </td>
))} ))}
</tr> </tr>
{row.getIsExpanded() && (
<>
{renderSubComponent && (
<tr
className={cn(
TABLE_DEFAULT_STYLING.bodySubRowClassName(1),
tableClassNames.bodySubRowClassName(1),
{
[tableClassNames.selectedBodySubRowClassName(1)]:
row.getIsSelected(),
}
)}
>
<td colSpan={row.getVisibleCells().length}>
{renderSubComponent({ row })}
</td>
</tr>
)}
</>
)}
</Fragment>
); );
})} })}
</tbody> </tbody>
@@ -425,7 +483,10 @@ const Table = <TData extends object>({
!isLoading && !isLoading &&
emptyContent} emptyContent}
{data.length > 0 && table.getRowModel().rows.length > 0 && !isLoading && ( {data.length > 0 &&
table.getRowModel().rows.length > 0 &&
!isLoading &&
withPagination && (
<div <div
className={cn( className={cn(
'mt-5', 'mt-5',
+1 -1
View File
@@ -40,7 +40,7 @@ const StatusBadge = ({
xmlns='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg'
className={cn({ className={cn({
'text-base-content/10': color === 'neutral', 'text-base-content/10': color === 'neutral',
'text-success': color === 'success', 'text-[#008000]': color === 'success',
'text-error': color === 'error', 'text-error': color === 'error',
'text-primary': color === 'info', 'text-primary': color === 'info',
})} })}
+21 -3
View File
@@ -167,7 +167,15 @@ const ConfirmationModal = ({
{children && <div className='w-full'>{children}</div>} {children && <div className='w-full'>{children}</div>}
<div className='w-full grid grid-cols-2 gap-3'> {(secondaryButton || primaryButton) && (
<div
className={cn('w-full grid gap-3', {
'grid-cols-2': secondaryButton && primaryButton,
'grid-cols-1':
(secondaryButton && !primaryButton) ||
(!secondaryButton && primaryButton),
})}
>
{secondaryButton && secondaryButton.text && ( {secondaryButton && secondaryButton.text && (
<Button <Button
{...secondaryButton} {...secondaryButton}
@@ -179,7 +187,13 @@ const ConfirmationModal = ({
? secondaryButton?.isLoading ? secondaryButton?.isLoading
: isPrimaryButtonLoading : isPrimaryButtonLoading
} }
onClick={closeModalHandler} onClick={(e) => {
if (secondaryButton?.onClick) {
secondaryButton.onClick(e);
} else {
closeModalHandler();
}
}}
className={cn( className={cn(
'p-2 rounded-xl text-sm', 'p-2 rounded-xl text-sm',
secondaryButton?.className secondaryButton?.className
@@ -204,12 +218,16 @@ const ConfirmationModal = ({
? primaryButton?.isLoading ? primaryButton?.isLoading
: isPrimaryButtonLoading : isPrimaryButtonLoading
} }
className={cn('p-2 rounded-xl text-sm', primaryButton?.className)} className={cn(
'p-2 rounded-xl text-sm',
primaryButton?.className
)}
> >
{primaryButton?.text ?? 'Ya'} {primaryButton?.text ?? 'Ya'}
</Button> </Button>
)} )}
</div> </div>
)}
</div> </div>
</Modal> </Modal>
); );
@@ -30,7 +30,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
}, },
{ {
label: 'Jenis Transaksi', label: 'Jenis Transaksi',
value: finance.transaction_type, value: formatTitleCase(finance.transaction_type.split('_').join(' ')),
}, },
{ {
label: 'Pihak', label: 'Pihak',
@@ -56,7 +56,9 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
}, },
{ {
label: 'Nomor Rekening', label: 'Nomor Rekening',
value: `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`, value: finance.bank?.alias
? `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`
: '-',
}, },
{ {
label: `Rekening ${formatTitleCase(finance.party?.type)}`, label: `Rekening ${formatTitleCase(finance.party?.type)}`,
@@ -64,7 +66,11 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
}, },
{ {
label: 'Nominal', label: 'Nominal',
value: formatCurrency(Math.abs(finance.nominal)), value: formatCurrency(
finance.transaction_type === 'INJECTION'
? finance.nominal
: Math.abs(finance.nominal)
),
}, },
].filter((item) => { ].filter((item) => {
// Hide party account number row if transaction type is INJECTION // Hide party account number row if transaction type is INJECTION
@@ -416,7 +416,8 @@ const FinanceTable = () => {
}, },
{ {
header: 'Pemasukan (Rp)', header: 'Pemasukan (Rp)',
accessorFn: (finance: Finance) => formatCurrency(finance.income_amount), accessorFn: (finance: Finance) =>
formatCurrency(Math.abs(finance.income_amount)),
}, },
{ {
header: 'Aksi', header: 'Aksi',
@@ -5,6 +5,7 @@ import * as Yup from 'yup';
export type InjectionFormValues = { export type InjectionFormValues = {
bank_id_option: OptionType | null; bank_id_option: OptionType | null;
adjustment_date: string; adjustment_date: string;
injection_type?: OptionType | null | undefined;
nominal: string; nominal: string;
note: string; note: string;
}; };
@@ -18,6 +19,7 @@ export const InjectionFormSchema = Yup.object<InjectionFormValues>({
(value) => value !== null && value !== undefined (value) => value !== null && value !== undefined
), ),
adjustment_date: Yup.string().required('Tanggal penyesuaian wajib diisi'), adjustment_date: Yup.string().required('Tanggal penyesuaian wajib diisi'),
injection_type: Yup.mixed().nullable().required('Tipe injeksi wajib diisi'),
nominal: Yup.string().required('Nominal wajib diisi'), nominal: Yup.string().required('Nominal wajib diisi'),
note: Yup.string().required('Catatan wajib diisi'), note: Yup.string().required('Catatan wajib diisi'),
}); });
@@ -28,6 +28,10 @@ import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Alert from '@/components/Alert'; import Alert from '@/components/Alert';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import {
FINANCE_INJECTION_STATUS,
FINANCE_INJECTION_TYPE_OPTIONS,
} from '@/config/constant';
interface FormFinanceInjectionProps { interface FormFinanceInjectionProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -51,6 +55,11 @@ const FormFinanceInjection = ({
} }
: null, : null,
adjustment_date: initialValues?.payment_date || '', adjustment_date: initialValues?.payment_date || '',
injection_type: initialValues?.nominal
? initialValues.nominal < 0
? FINANCE_INJECTION_TYPE_OPTIONS[1]
: FINANCE_INJECTION_TYPE_OPTIONS[0]
: FINANCE_INJECTION_TYPE_OPTIONS[0],
nominal: initialValues?.nominal?.toString() || '', nominal: initialValues?.nominal?.toString() || '',
note: initialValues?.notes || '', note: initialValues?.notes || '',
}; };
@@ -94,7 +103,10 @@ const FormFinanceInjection = ({
return { return {
bank_id: Number(values.bank_id_option?.value) || 0, bank_id: Number(values.bank_id_option?.value) || 0,
adjustment_date: formatDate(values.adjustment_date, 'YYYY-MM-DD'), adjustment_date: formatDate(values.adjustment_date, 'YYYY-MM-DD'),
nominal: Number(values.nominal.replace(/\D/g, '')) || 0, nominal:
values.injection_type?.value == 'POSITIVE'
? Math.abs(Number(values.nominal))
: -Math.abs(Number(values.nominal)),
notes: values.note, notes: values.note,
}; };
}; };
@@ -203,6 +215,24 @@ const FormFinanceInjection = ({
} }
required required
/> />
<SelectInput
label='Tipe Injeksi'
placeholder='Pilih tipe injeksi'
options={FINANCE_INJECTION_TYPE_OPTIONS}
value={formik.values.injection_type}
onChange={(value) => {
formik.setFieldValue('injection_type', value);
}}
isError={Boolean(
formik.touched.injection_type && formik.errors.injection_type
)}
errorMessage={
formik.touched.injection_type && formik.errors.injection_type
? formik.errors.injection_type
: ''
}
required
/>
<NumberInput <NumberInput
label='Nominal' label='Nominal'
placeholder='Masukkan nominal' placeholder='Masukkan nominal'
@@ -216,8 +246,17 @@ const FormFinanceInjection = ({
? formik.errors.nominal ? formik.errors.nominal
: '' : ''
} }
allowNegative={true} allowNegative={false}
required required
startAdornment={
formik.values.injection_type?.value === 'POSITIVE' ? (
<Icon icon='mdi:plus' />
) : formik.values.injection_type?.value === 'NEGATIVE' ? (
<Icon icon='mdi:minus' />
) : (
''
)
}
/> />
<TextArea <TextArea
label='Catatan' label='Catatan'
@@ -1369,9 +1369,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
} }
readOnly readOnly
disabled disabled
className={{
input: 'bg-base-200',
}}
/> />
<TextInput <TextInput
label='Lokasi' label='Lokasi'
@@ -1382,9 +1379,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
} }
readOnly readOnly
disabled disabled
className={{
input: 'bg-base-200',
}}
/> />
</div> </div>
</Card> </Card>
@@ -1433,9 +1427,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
} }
readOnly readOnly
disabled disabled
className={{
input: 'bg-base-200',
}}
/> />
<TextInput <TextInput
label='Lokasi' label='Lokasi'
@@ -1446,9 +1437,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
} }
readOnly readOnly
disabled disabled
className={{
input: 'bg-base-200',
}}
/> />
</div> </div>
</Card> </Card>
@@ -164,6 +164,9 @@ export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSche
export const UpdateRecordingGrowingFormSchema = export const UpdateRecordingGrowingFormSchema =
RecordingGrowingFormSchema.shape({ RecordingGrowingFormSchema.shape({
location_id: Yup.number().nullable().optional(),
project_flock_id: Yup.number().nullable().optional(),
kandang_id: Yup.number().nullable().optional(),
project_flock_kandang_id: Yup.number() project_flock_kandang_id: Yup.number()
.default(0) .default(0)
.typeError('Project Flock Kandang wajib diisi!') .typeError('Project Flock Kandang wajib diisi!')
@@ -176,6 +179,9 @@ export const UpdateRecordingGrowingFormSchema =
}); });
export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({ export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({
location_id: Yup.number().nullable().optional(),
project_flock_id: Yup.number().nullable().optional(),
kandang_id: Yup.number().nullable().optional(),
project_flock_kandang_id: Yup.number() project_flock_kandang_id: Yup.number()
.default(0) .default(0)
.typeError('Project Flock Kandang wajib diisi!') .typeError('Project Flock Kandang wajib diisi!')
@@ -217,13 +223,25 @@ export const getRecordingGrowingFormInitialValues = (
project_flock_id: 0, project_flock_id: 0,
kandang: null, kandang: null,
kandang_id: 0, kandang_id: 0,
project_flock_kandang: initialValues?.project_flock_kandang_id project_flock_kandang:
(initialValues?.project_flock_kandang_id ??
initialValues?.project_flock?.project_flock_kandang_id)
? { ? {
value: initialValues.project_flock_kandang_id, value:
label: `Project Flock #${initialValues.project_flock_kandang_id}`, initialValues?.project_flock_kandang_id ??
initialValues?.project_flock?.project_flock_kandang_id ??
0,
label: `Project Flock #${
initialValues?.project_flock_kandang_id ??
initialValues?.project_flock?.project_flock_kandang_id ??
0
}`,
} }
: null, : null,
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0, project_flock_kandang_id:
initialValues?.project_flock_kandang_id ??
initialValues?.project_flock?.project_flock_kandang_id ??
0,
stocks: initialValues?.stocks?.map((stock) => ({ stocks: initialValues?.stocks?.map((stock) => ({
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: stock.product_warehouse_id,
qty: qty:
@@ -554,15 +554,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const nextDayRecordingUrl = useMemo(() => { const nextDayRecordingUrl = useMemo(() => {
if (!projectFlockKandangLookup) return null; if (!projectFlockKandangLookup) return null;
const projectFlockKandangId = projectFlockKandangLookup.id; const projectFlockKandangId = projectFlockKandangLookup.id;
return `${RecordingApi.basePath}/next-day?project_flock_kandang_id=${projectFlockKandangId}`; return `${RecordingApi.basePath}/next-day?project_flock_kandang_id=${projectFlockKandangId}&record_date=${selectedRecordDate}`;
}, [projectFlockKandangLookup]); }, [projectFlockKandangLookup, selectedRecordDate]);
const { data: nextDayRecordingData } = useSWR( const { data: nextDayRecordingData } = useSWR(
nextDayRecordingUrl, nextDayRecordingUrl,
nextDayRecordingUrl nextDayRecordingUrl
? () => { ? () => {
const projectFlockKandangId = projectFlockKandangLookup!.id; const projectFlockKandangId = projectFlockKandangLookup!.id;
return RecordingApi.nextDayRecording(projectFlockKandangId); return RecordingApi.nextDayRecording(
projectFlockKandangId,
selectedRecordDate
);
} }
: null : null
); );
@@ -1075,14 +1078,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
if ((type as 'add' | 'edit' | 'detail') === 'detail') return null; if ((type as 'add' | 'edit' | 'detail') === 'detail') return null;
const stock = formik.values.stocks?.[stockIdx]; const stock = formik.values.stocks?.[stockIdx];
if (!stock || !stock.product_warehouse_id) return null; if (!stock || !stock.product_warehouse_id) return null;
const availableStock = getAvailableStock(stock.product_warehouse_id);
const requestedUsage = Number(stock.qty) || 0;
if (requestedUsage > availableStock) {
return `Jumlah pakai melebihi stok tersedia! Maksimal: ${formatNumber(availableStock)}`;
}
return null; return null;
}, },
[formik.values.stocks, getAvailableStock, type] [formik.values.stocks, type]
); );
const getStockUsageAdornment = useCallback( const getStockUsageAdornment = useCallback(
@@ -1096,7 +1094,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
if (requestedUsage > 0) { if (requestedUsage > 0) {
return ( return (
<span className='text-sm text-gray-600 whitespace-nowrap'> <span className='text-sm text-gray-600 whitespace-nowrap'>
(sisa: {formatNumber(remainingStock)}) (tersedia: {formatNumber(availableStock)} | pakai:{' '}
{formatNumber(requestedUsage)} | sisa:{' '}
{formatNumber(Math.max(remainingStock, 0))} | dipinjam:{' '}
{formatNumber(Math.max(-remainingStock, 0))})
</span> </span>
); );
} }
@@ -2443,7 +2444,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
); );
}} }}
options={unifiedStockProducts} options={unifiedStockProducts}
placeholder='Pilih Produk' placeholder={
!formik.values.project_flock_kandang_id
? 'Pilih kandang terlebih dahulu'
: 'Pilih Produk'
}
isLoading={isLoadingStockProducts} isLoading={isLoadingStockProducts}
onMenuScrollToBottom={loadMoreStockProducts} onMenuScrollToBottom={loadMoreStockProducts}
isError={ isError={
@@ -2464,7 +2469,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
wrapper: 'w-full min-w-48', wrapper: 'w-full min-w-48',
}} }}
isSearchable isSearchable
isDisabled={type === 'detail'} isDisabled={
type === 'detail' ||
!formik.values.project_flock_kandang_id
}
isClearable={type !== 'detail'} isClearable={type !== 'detail'}
startAdornment={ startAdornment={
stock.product_warehouse_id stock.product_warehouse_id
@@ -0,0 +1,294 @@
'use client';
import { ChangeEventHandler, RefObject, useId, useState } from 'react';
import useSWR from 'swr';
import ConfirmationModal, {
ConfirmationModalProps,
} from '@/components/modal/ConfirmationModal';
import Table from '@/components/Table';
import TextArea from '@/components/input/TextArea';
import { ColumnDef } from '@tanstack/react-table';
import { cn, formatDate, formatNumber } from '@/lib/helper';
import { TransferToLayingFormValues } from '@/components/pages/production/transfer-to-laying/form/TransferToLayingForm.schema';
import { Color } from '@/types/theme';
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
import { isResponseSuccess } from '@/lib/api-helper';
interface TransferToLayingConfirmationModalProps
extends Omit<ConfirmationModalProps, 'children' | 'primaryButton'> {
ref: RefObject<HTMLDialogElement | null>;
type?: 'info' | 'success' | 'error';
transferToLayingIds?: number[];
transferToLayingForm?: TransferToLayingFormValues;
onClose?: () => void;
withNote?: boolean;
noteLabel?: string;
rows?: number;
placeholder?: string;
primaryButton?: {
text?: string;
color?: Color;
isLoading?: boolean;
onClick?: (notes: string) => void;
};
}
interface TransferToLayingConfirmationTableDataType {
label: string;
value: string;
subRows?: TransferToLayingConfirmationTableDataType[];
}
const TransferToLayingConfirmationModalTable = ({
transferToLayingForm,
transferToLayingId,
}: {
transferToLayingForm?: TransferToLayingFormValues;
transferToLayingId?: number;
}) => {
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
useSWR(
transferToLayingId
? ['detail-transfer-to-laying', String(transferToLayingId)]
: undefined,
([_, id]) => TransferToLayingApi.getSingle(Number(id))
);
const confirmationTableColumns: ColumnDef<TransferToLayingConfirmationTableDataType>[] =
[
{
header: 'Label',
accessorKey: 'label',
enableSorting: false,
cell: ({ row }) => {
const isSubRow = row.depth > 0;
return (
<>
{!isSubRow && row.original.label}
{isSubRow && (
<div
className={cn('w-full min-h-full flex items-stretch gap-0')}
>
<div className='w-px mx-4 bg-base-content/10' />
<span className='p-3'>{row.original.label}</span>
</div>
)}
</>
);
},
},
{
header: 'Value',
accessorKey: 'value',
enableSorting: false,
},
];
const confirmationTableData: TransferToLayingConfirmationTableDataType[] = [
{
label: 'Tanggal',
value: formatDate(
transferToLayingId && isResponseSuccess(transferToLaying)
? transferToLaying.data.transfer_date
: transferToLayingForm?.transfer_date,
'DD MMMM YYYY'
),
},
{
label: 'Flock Asal',
value:
transferToLayingId && isResponseSuccess(transferToLaying)
? transferToLaying.data.from_project_flock.flock_name
: (transferToLayingForm?.flockSource?.label ?? '-'),
subRows:
transferToLayingId && isResponseSuccess(transferToLaying)
? (transferToLaying.data.sources?.map(
(sourceProjectFlockKandang) => ({
label:
sourceProjectFlockKandang.source_project_flock_kandang.kandang
.name,
value: formatNumber(
Number(sourceProjectFlockKandang.qty),
'en-US'
),
})
) ?? [])
: (transferToLayingForm?.flockSourceKandangs?.map((kandang) => ({
label: kandang.kandang.label,
value: formatNumber(Number(kandang.quantity), 'en-US'),
})) ?? []),
},
{
label: 'Flock Tujuan',
value:
transferToLayingId && isResponseSuccess(transferToLaying)
? transferToLaying.data.to_project_flock.flock_name
: (transferToLayingForm?.flockDestination?.label ?? '-'),
subRows:
transferToLayingId && isResponseSuccess(transferToLaying)
? (transferToLaying.data.targets?.map(
(targetProjectFlockKandang) => ({
label:
targetProjectFlockKandang.target_project_flock_kandang.kandang
.name,
value: formatNumber(
Number(targetProjectFlockKandang.qty),
'en-US'
),
})
) ?? [])
: (transferToLayingForm?.flockDestinationKandangs?.map((kandang) => ({
label: kandang.kandang.label,
value: formatNumber(Number(kandang.quantity), 'en-US'),
})) ?? []),
},
{
label: 'Jumlah Transfer',
value: formatNumber(
transferToLayingId && isResponseSuccess(transferToLaying)
? Number(
transferToLaying.data.sources.reduce(
(total, source) => total + Number(source.qty),
0
)
)
: Number(transferToLayingForm?.totalQuantity),
'en-US'
),
},
{
label: 'Notes',
value:
transferToLayingId && isResponseSuccess(transferToLaying)
? (transferToLaying.data.notes ?? '-')
: (transferToLayingForm?.reason ?? '-'),
},
];
return (
<Table<TransferToLayingConfirmationTableDataType>
columns={confirmationTableColumns}
data={confirmationTableData}
withPagination={false}
pageSize={10000}
expanded={true}
getSubRows={(row) => row.subRows}
className={{
headerRowClassName: 'border-b border-base-content/10',
bodyRowClassName: 'border-none',
bodySubRowClassName: () => 'border-none',
bodySubRowColumnClassName: () => 'first:p-0',
}}
/>
);
};
const TransferToLayingConfirmationModal = ({
ref,
type = 'success',
transferToLayingForm,
transferToLayingIds,
onClose,
withNote,
rows = 4,
noteLabel,
placeholder = 'Alasan Transfer',
primaryButton,
secondaryButton,
...props
}: TransferToLayingConfirmationModalProps) => {
const randomId = useId();
const [notes, setNotes] = useState('');
const notesChangeHandler: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
setNotes(e.target.value);
};
const closeModalHandler = () => {
onClose?.();
ref.current?.close();
};
return (
<ConfirmationModal
ref={ref}
iconPosition='left'
type={type}
primaryButton={{
...primaryButton,
text: primaryButton?.text ?? 'Oke',
color: primaryButton?.color ?? 'primary',
className: 'rounded-lg',
onClick: (e) => {
if (withNote) {
primaryButton?.onClick?.(notes);
} else if (primaryButton && primaryButton?.onClick) {
primaryButton?.onClick?.('');
} else {
closeModalHandler();
}
setNotes('');
},
}}
secondaryButton={
secondaryButton
? {
text: secondaryButton?.text ?? 'Cancel',
color: secondaryButton?.color ?? 'none',
onClick: (e) => {
if (secondaryButton && secondaryButton?.onClick) {
secondaryButton.onClick?.(e);
} else {
closeModalHandler();
}
setNotes('');
},
}
: undefined
}
className={{
modalBox: 'max-h-full',
}}
{...props}
>
<div className='flex flex-col gap-4'>
{!transferToLayingIds && transferToLayingForm && (
<TransferToLayingConfirmationModalTable
transferToLayingForm={transferToLayingForm}
/>
)}
{transferToLayingIds &&
!transferToLayingForm &&
transferToLayingIds.map((transferToLayingId, idx) => (
<TransferToLayingConfirmationModalTable
key={idx}
transferToLayingId={transferToLayingId}
/>
))}
{withNote && (
<TextArea
name={randomId}
label={noteLabel}
placeholder={placeholder}
value={notes}
onChange={notesChangeHandler}
rows={rows}
/>
)}
</div>
</ConfirmationModal>
);
};
export default TransferToLayingConfirmationModal;
@@ -7,6 +7,7 @@ import {
useMemo, useMemo,
useState, useState,
} from 'react'; } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR, { useSWRConfig } from 'swr'; import useSWR, { useSWRConfig } from 'swr';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -19,8 +20,8 @@ import { OptionType, useSelect } from '@/components/input/SelectInput';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import TextArea from '@/components/input/TextArea'; import TextArea from '@/components/input/TextArea';
import AlertErrorList from '@/components/helper/form/FormErrors'; import AlertErrorList from '@/components/helper/form/FormErrors';
import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal';
import { useRouter, useSearchParams } from 'next/navigation';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { getIn, useFormik } from 'formik'; import { getIn, useFormik } from 'formik';
import { import {
@@ -58,8 +59,11 @@ const TransferToLayingFormModal = () => {
}; };
const { data: transferToLaying, isLoading: isLoadingTransferToLaying } = const { data: transferToLaying, isLoading: isLoadingTransferToLaying } =
useSWR(transferToLayingId ? transferToLayingId : undefined, (id: number) => useSWR(
TransferToLayingApi.getSingle(id) transferToLayingId
? ['detail-transfer-to-laying', transferToLayingId]
: undefined,
([, id]) => TransferToLayingApi.getSingle(Number(id))
); );
/** /**
@@ -71,7 +75,11 @@ const TransferToLayingFormModal = () => {
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const formModal = useModal(); const formModal = useModal();
const successModal = useModal();
const [formikLastValues, setFormikLastValues] = useState<
TransferToLayingFormValues | undefined
>(undefined);
const [formErrorMessage, setFormErrorMessage] = useState<string | null>(null); const [formErrorMessage, setFormErrorMessage] = useState<string | null>(null);
// Flock Source // Flock Source
@@ -133,6 +141,7 @@ const TransferToLayingFormModal = () => {
toast.success(createTransferToLayingRes?.message as string); toast.success(createTransferToLayingRes?.message as string);
router.push('/production/transfer-to-laying'); router.push('/production/transfer-to-laying');
closeModalHandler(false); closeModalHandler(false);
successModal.openModal();
}, },
[router] [router]
); );
@@ -156,6 +165,7 @@ const TransferToLayingFormModal = () => {
toast.success(updateKandangRes?.message as string); toast.success(updateKandangRes?.message as string);
router.push('/production/transfer-to-laying'); router.push('/production/transfer-to-laying');
closeModalHandler(false); closeModalHandler(false);
successModal.openModal();
}, },
[router] [router]
); );
@@ -187,6 +197,8 @@ const TransferToLayingFormModal = () => {
reason: values.reason as string, reason: values.reason as string,
}; };
setFormikLastValues(values);
switch (modalAction) { switch (modalAction) {
case 'add': case 'add':
await createTransferToLayingHandler(transferToLayingPayload); await createTransferToLayingHandler(transferToLayingPayload);
@@ -244,7 +256,7 @@ const TransferToLayingFormModal = () => {
? selectedFlockSourceRawData.kandangs.map((kandang) => { ? selectedFlockSourceRawData.kandangs.map((kandang) => {
const availability = const availability =
flockSourceKandangsAvailability[kandang.project_flock_kandang_id] flockSourceKandangsAvailability[kandang.project_flock_kandang_id]
.available_qty; ?.available_qty;
return { return {
kandang_name: kandang.name, kandang_name: kandang.name,
@@ -375,6 +387,12 @@ const TransferToLayingFormModal = () => {
formik.setValues(filledInitialValues); formik.setValues(filledInitialValues);
setStep(3); setStep(3);
} }
if (isResponseError(transferToLaying)) {
router.push('/production/transfer-to-laying');
closeModalHandler();
toast.error(transferToLaying.message);
}
}; };
const getFlockSourceData = async () => { const getFlockSourceData = async () => {
@@ -437,7 +455,7 @@ const TransferToLayingFormModal = () => {
<div className='w-px border-none bg-base-content/10' /> <div className='w-px border-none bg-base-content/10' />
<h4 className='text-sm font-medium text-base-content/50'> <h4 className='text-sm font-medium text-base-content/50'>
Add Transfer to Laying {modalAction === 'add' ? 'Add' : 'Edit'} Transfer to Laying
</h4> </h4>
</div> </div>
@@ -771,7 +789,12 @@ const TransferToLayingFormModal = () => {
<div className='flex flex-col gap-3'> <div className='flex flex-col gap-3'>
{formik.values.flockSourceKandangs.map((item, index) => { {formik.values.flockSourceKandangs.map((item, index) => {
const isInvalid = const isInvalid =
item.quantity === '' !Boolean(
getIn(
formik.touched,
`flockSourceKandangs[${index}].quantity`
)
) && item.quantity === ''
? false ? false
: Boolean( : Boolean(
getIn( getIn(
@@ -833,7 +856,8 @@ const TransferToLayingFormModal = () => {
: 'neutral' : 'neutral'
} }
text={`Sisa transfer: ${formatNumber( text={`Sisa transfer: ${formatNumber(
totalAvailableChickenForTransfer totalAvailableChickenForTransfer,
'en-US'
)} ekor`} )} ekor`}
className={{ className={{
badge: 'text-nowrap', badge: 'text-nowrap',
@@ -852,7 +876,12 @@ const TransferToLayingFormModal = () => {
{formik.values.flockDestinationKandangs.map( {formik.values.flockDestinationKandangs.map(
(item, index) => { (item, index) => {
const isInvalid = const isInvalid =
item.quantity === '' !Boolean(
getIn(
formik.touched,
`flockDestinationKandangs.${index}.quantity`
)
) && item.quantity === ''
? false ? false
: Boolean( : Boolean(
getIn( getIn(
@@ -968,6 +997,16 @@ const TransferToLayingFormModal = () => {
)} )}
</form> </form>
</Modal> </Modal>
<TransferToLayingConfirmationModal
ref={successModal.ref}
type='success'
text='Data Berhasil Ditambahkan'
subtitleText='Data transfer to laying telah berhasil disimpan.'
transferToLayingForm={formikLastValues}
onClose={() => setFormikLastValues(undefined)}
secondaryButton={undefined}
/>
</> </>
); );
}; };
@@ -14,9 +14,7 @@ import { Icon } from '@iconify/react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import PopoverButton from '@/components/popover/PopoverButton'; import PopoverButton from '@/components/popover/PopoverButton';
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
@@ -24,13 +22,14 @@ import PopoverContent from '@/components/popover/PopoverContent';
import Dropdown from '@/components/Dropdown'; import Dropdown from '@/components/Dropdown';
import StatusBadge from '@/components/helper/StatusBadge'; import StatusBadge from '@/components/helper/StatusBadge';
import TransferToLayingFilterModal from '@/components/pages/production/transfer-to-laying/TransferToLayingFilterModal'; import TransferToLayingFilterModal from '@/components/pages/production/transfer-to-laying/TransferToLayingFilterModal';
import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal';
import { import {
TransferToLaying, TransferToLaying,
TransferToLayingFilter, TransferToLayingFilter,
} from '@/types/api/production/transfer-to-laying'; } from '@/types/api/production/transfer-to-laying';
import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying'; import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate, formatNumber } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
@@ -38,14 +37,11 @@ import { Color } from '@/types/theme';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
props, props,
popoverPosition = 'bottom', popoverPosition = 'bottom',
approveClickHandler,
rejectClickHandler,
deleteClickHandler, deleteClickHandler,
}: { }: {
props: CellContext<TransferToLaying, unknown>; props: CellContext<TransferToLaying, unknown>;
popoverPosition: 'bottom' | 'top'; popoverPosition: 'bottom' | 'top';
approveClickHandler: () => void;
rejectClickHandler: () => void;
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
const showEditButton = const showEditButton =
@@ -54,9 +50,6 @@ const RowOptionsMenu = ({
const showDeleteButton = showEditButton; const showDeleteButton = showEditButton;
// const showApproveButton = showEditButton;
// const showRejectButton = showEditButton;
const popoverId = `transferToLaying#${props.row.original.id}`; const popoverId = `transferToLaying#${props.row.original.id}`;
const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`; const popoverAnchorName = `--anchor-transferToLaying#${props.row.original.id}`;
@@ -260,7 +253,14 @@ const TransferToLayingsTable = () => {
{ {
accessorKey: 'usage_qty', accessorKey: 'usage_qty',
header: 'Kuantitas', header: 'Kuantitas',
cell: (props) => props.getValue() ?? props.row.original.pending_usage_qty, cell: (props) => {
const totalQuantity = props.row.original.targets.reduce(
(total, target) => total + target.qty,
0
);
return formatNumber(totalQuantity, 'en-US');
},
}, },
{ {
accessorKey: 'notes', accessorKey: 'notes',
@@ -304,38 +304,20 @@ const TransferToLayingsTable = () => {
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
const approveClickHandler = () => {
setSelectedTransferToLaying(props.row.original);
// Set row selection
setRowSelection({
[String(props.row.original.id)]: true,
});
approveModal.openModal();
};
const rejectClickHandler = () => {
setSelectedTransferToLaying(props.row.original);
// Set row selection
setRowSelection({
[String(props.row.original.id)]: true,
});
rejectModal.openModal();
};
const deleteClickHandler = () => { const deleteClickHandler = () => {
setSelectedTransferToLaying(props.row.original); setSelectedTransferToLaying(props.row.original);
// Set row selection
setRowSelection({
[String(props.row.original.id)]: true,
});
deleteModal.openModal(); deleteModal.openModal();
}; };
return ( return (
<RowOptionsMenu <RowOptionsMenu
props={props} props={props}
approveClickHandler={approveClickHandler}
rejectClickHandler={rejectClickHandler}
deleteClickHandler={deleteClickHandler} deleteClickHandler={deleteClickHandler}
popoverPosition={isLast2Rows ? 'top' : 'bottom'} popoverPosition={isLast2Rows ? 'top' : 'bottom'}
/> />
@@ -377,6 +359,8 @@ const TransferToLayingsTable = () => {
refreshTransferToLayings(); refreshTransferToLayings();
setRowSelection({});
setSelectedTransferToLaying(undefined);
deleteModal.closeModal(); deleteModal.closeModal();
toast.success('Berhasil menghapus data transfer ke laying!'); toast.success('Berhasil menghapus data transfer ke laying!');
setIsDeleteLoading(false); setIsDeleteLoading(false);
@@ -646,53 +630,70 @@ const TransferToLayingsTable = () => {
onReset={filterResetHandler} onReset={filterResetHandler}
/> />
<ConfirmationModal <TransferToLayingConfirmationModal
ref={deleteModal.ref} ref={deleteModal.ref}
iconPosition='left'
type='error' type='error'
text='Delete This Data?' text='Delete This Data?'
subtitleText='Are you sure you want to delete this data? ' subtitleText='Are you sure you want to delete this data? '
transferToLayingIds={selectedRowIds}
primaryButton={{
isLoading: isDeleteLoading,
color: 'error',
onClick: confirmationModalDeleteClickHandler,
}}
secondaryButton={{ secondaryButton={{
text: 'Cancel', text: 'Cancel',
}} color: 'none',
primaryButton={{ onClick: () => {
text: 'Delete', setRowSelection({});
color: 'error', deleteModal.closeModal();
isLoading: isDeleteLoading, },
onClick: confirmationModalDeleteClickHandler,
}} }}
/> />
<ConfirmationModalWithNotes {/* Approve Modal */}
<TransferToLayingConfirmationModal
ref={approveModal.ref} ref={approveModal.ref}
type='success'
iconPosition='left'
text='Approve This Submission?' text='Approve This Submission?'
subtitleText='Are you sure you want to approve this submission?' subtitleText='Are you sure you want to approve this submission?'
secondaryButton={{ type='success'
text: 'Cancel', transferToLayingIds={selectedRowIds}
}} withNote
noteLabel='Notes Approval'
primaryButton={{ primaryButton={{
text: 'Approve',
color: 'success',
isLoading: isApproveLoading, isLoading: isApproveLoading,
onClick: confirmationModalApproveClickHandler, onClick: confirmationModalApproveClickHandler,
}} }}
/>
<ConfirmationModalWithNotes
ref={rejectModal.ref}
type='error'
iconPosition='left'
text='Reject This Submission?'
subtitleText='Are you sure you want to reject this submission?'
secondaryButton={{ secondaryButton={{
text: 'Cancel', text: 'Cancel',
color: 'none',
onClick: () => {
setRowSelection({});
approveModal.closeModal();
},
}}
/>
{/* Reject Modal */}
<TransferToLayingConfirmationModal
ref={rejectModal.ref}
type='error'
text='Reject This Submission?'
subtitleText='Are you sure you want to reject this submission?'
transferToLayingIds={selectedRowIds}
withNote
noteLabel='Notes Reject'
secondaryButton={{
text: 'Cancel',
color: 'none',
onClick: () => {
setRowSelection({});
rejectModal.closeModal();
},
}} }}
primaryButton={{ primaryButton={{
text: 'Reject',
color: 'error',
isLoading: isRejectLoading, isLoading: isRejectLoading,
color: 'error',
onClick: confirmationModalRejectClickHandler, onClick: confirmationModalRejectClickHandler,
}} }}
/> />
@@ -399,23 +399,58 @@ const UniformityForm = ({
// ===== SIDE EFFECTS ===== // ===== SIDE EFFECTS =====
useEffect(() => { useEffect(() => {
if ( if (
projectFlockKandangLookup?.chick_in_date &&
projectFlockKandangLookup?.project_flock_kandang_id && projectFlockKandangLookup?.project_flock_kandang_id &&
isResponseSuccess(recordingsData) && isResponseSuccess(recordingsData) &&
recordingsData.data recordingsData.data
) { ) {
const matchingRecording = recordingsData.data.find( const matchingRecordings = recordingsData.data.filter(
(recording: Recording) => (recording: Recording) =>
recording.project_flock?.project_flock_kandang_id === recording.project_flock?.project_flock_kandang_id ===
projectFlockKandangLookup.project_flock_kandang_id projectFlockKandangLookup.project_flock_kandang_id
); );
if (matchingRecording?.project_flock?.production_standart?.week) { matchingRecordings.sort(
const weekValue = (a: Recording, b: Recording) =>
matchingRecording.project_flock.production_standart.week; new Date(a.record_datetime).getTime() -
formik.setFieldValue('week', weekValue); new Date(b.record_datetime).getTime()
);
const earliestRecording = matchingRecordings[0];
if (earliestRecording) {
const chickInDate = new Date(projectFlockKandangLookup.chick_in_date);
chickInDate.setHours(0, 0, 0, 0);
const earliestRecordDate = new Date(earliestRecording.record_datetime);
earliestRecordDate.setHours(0, 0, 0, 0);
const initialWeek =
earliestRecording.project_flock?.production_standart?.week || 18;
if (formik.values.date) {
const selectedDate = new Date(formik.values.date);
selectedDate.setHours(0, 0, 0, 0);
const daysDiff = Math.floor(
(selectedDate.getTime() - chickInDate.getTime()) /
(1000 * 60 * 60 * 24)
);
const weeksDiff = Math.floor(daysDiff / 7);
formik.setFieldValue('week', initialWeek + weeksDiff);
} else {
formik.setFieldValue('week', initialWeek);
} }
} }
}, [projectFlockKandangLookup?.project_flock_kandang_id, recordingsData]); }
}, [
projectFlockKandangLookup?.chick_in_date,
projectFlockKandangLookup?.project_flock_kandang_id,
recordingsData,
formik.values.date,
]);
useEffect(() => { useEffect(() => {
const unsub = subscribeValidate(() => { const unsub = subscribeValidate(() => {
+5
View File
@@ -389,6 +389,11 @@ export const FINANCE_INITIAL_BALANCE_TYPE_OPTIONS = [
{ label: 'Saldo Awal Negatif', value: 'NEGATIVE' }, { label: 'Saldo Awal Negatif', value: 'NEGATIVE' },
]; ];
export const FINANCE_INJECTION_TYPE_OPTIONS = [
{ label: 'Saldo Injection Positif', value: 'POSITIVE' },
{ label: 'Saldo Injection Negatif', value: 'NEGATIVE' },
];
export const FINANCE_TRANSACTION_TYPE_OPTIONS = [ export const FINANCE_TRANSACTION_TYPE_OPTIONS = [
{ label: 'Pembelian', value: 'PEMBELIAN' }, { label: 'Pembelian', value: 'PEMBELIAN' },
{ label: 'Penjualan', value: 'PENJUALAN' }, { label: 'Penjualan', value: 'PENJUALAN' },
@@ -312,6 +312,7 @@ export function DailyChecklistContent() {
try { try {
const activitiesRes = await PhaseActivityApi.getAll({ const activitiesRes = await PhaseActivityApi.getAll({
phase_ids: selectedPhaseIds.join(','), phase_ids: selectedPhaseIds.join(','),
limit: '100',
}); });
if (isResponseError(activitiesRes)) { if (isResponseError(activitiesRes)) {
+3 -1
View File
@@ -74,7 +74,8 @@ export class RecordingService extends BaseApiService<
} }
async nextDayRecording( async nextDayRecording(
projectFlockId: number projectFlockId: number,
recordDate?: string
): Promise<BaseApiResponse<NextDayRecording> | undefined> { ): Promise<BaseApiResponse<NextDayRecording> | undefined> {
return await this.customRequest<BaseApiResponse<NextDayRecording>>( return await this.customRequest<BaseApiResponse<NextDayRecording>>(
`next-day`, `next-day`,
@@ -82,6 +83,7 @@ export class RecordingService extends BaseApiService<
method: 'GET', method: 'GET',
params: { params: {
project_flock_kandang_id: projectFlockId, project_flock_kandang_id: projectFlockId,
record_date: recordDate,
}, },
} }
); );
+1
View File
@@ -76,6 +76,7 @@ export type ProjectFlockKandangLookup = {
quantity: number; quantity: number;
available_quantity?: number; available_quantity?: number;
population: number; population: number;
chick_in_date: string;
}; };
export type ProjectFlockAvailableQuantity = { export type ProjectFlockAvailableQuantity = {