mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
Merge branch 'development' into 'staging'
Development See merge request mbugroup/lti-web-client!315
This commit is contained in:
@@ -68,6 +68,8 @@
|
|||||||
|
|
||||||
--shadow-button-soft:
|
--shadow-button-soft:
|
||||||
0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200);
|
0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200);
|
||||||
|
|
||||||
|
--shadow-bg: 0px -2px 4px 0px #00000014;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import Drawer from '@/components/Drawer';
|
import React, { ReactNode, useEffect } from 'react';
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
|
import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
|
||||||
export default function ProjectFlockLayout({
|
export default function ProjectFlockLayout({
|
||||||
children,
|
children,
|
||||||
@@ -23,9 +23,12 @@ export default function ProjectFlockLayout({
|
|||||||
|
|
||||||
const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing;
|
const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing;
|
||||||
|
|
||||||
|
const formModal = useModal();
|
||||||
|
|
||||||
const handleBackdropClick = () => {
|
const handleBackdropClick = () => {
|
||||||
const unsub = useUiStore.getState().subscribeIsValid((isValid) => {
|
const unsub = useUiStore.getState().subscribeIsValid((isValid) => {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
|
formModal.closeModal();
|
||||||
unsub(); // berhenti listen
|
unsub(); // berhenti listen
|
||||||
router.push('/production/project-flock');
|
router.push('/production/project-flock');
|
||||||
}
|
}
|
||||||
@@ -34,6 +37,14 @@ export default function ProjectFlockLayout({
|
|||||||
toggleValidate();
|
toggleValidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && !formModal.open) {
|
||||||
|
formModal.openModal();
|
||||||
|
} else {
|
||||||
|
formModal.closeModal();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* List page always rendered */}
|
{/* List page always rendered */}
|
||||||
@@ -43,18 +54,19 @@ export default function ProjectFlockLayout({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Render Drawer only on /add */}
|
{/* Render Modal only on /add */}
|
||||||
<Drawer
|
<Modal
|
||||||
open={isOpen}
|
ref={formModal.ref}
|
||||||
setOpen={(v) => {
|
position='end'
|
||||||
if (!v) router.push('/production/project-flock');
|
|
||||||
}}
|
|
||||||
closeOnBackdropClick={isDetail ? true : false}
|
|
||||||
onBackdropClick={handleBackdropClick}
|
onBackdropClick={handleBackdropClick}
|
||||||
variant='right'
|
className={{
|
||||||
zIndex='99999'
|
modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none',
|
||||||
sidebarContent={isOpen && <div className=''>{children}</div>}
|
}}
|
||||||
/>
|
>
|
||||||
|
<div className='w-full sm:w-[446px] h-full flex flex-col sm:flex-row items-stretch bg-base-100 rounded-xl overflow-hidden'>
|
||||||
|
{isOpen && children}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode, Ref } from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
interface AlertProps {
|
interface AlertProps {
|
||||||
|
ref?: Ref<HTMLDivElement> | undefined;
|
||||||
variant?: 'outline' | 'dash' | 'soft';
|
variant?: 'outline' | 'dash' | 'soft';
|
||||||
color?: 'info' | 'success' | 'warning' | 'error';
|
color?: 'info' | 'success' | 'warning' | 'error';
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Alert = ({ children, variant, color, className }: AlertProps) => {
|
const Alert = ({ children, ref, variant, color, className }: AlertProps) => {
|
||||||
const alertBaseClassName = cn('alert', {
|
const alertBaseClassName = cn('alert', {
|
||||||
'alert-soft': variant === 'soft',
|
'alert-soft': variant === 'soft',
|
||||||
'alert-outline': variant === 'outline',
|
'alert-outline': variant === 'outline',
|
||||||
@@ -21,7 +22,11 @@ const Alert = ({ children, variant, color, className }: AlertProps) => {
|
|||||||
'alert-error': color === 'error',
|
'alert-error': color === 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div className={cn(alertBaseClassName, className)}>{children}</div>;
|
return (
|
||||||
|
<div ref={ref} className={cn(alertBaseClassName, className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Alert;
|
export default Alert;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Button from '@/components/Button';
|
|||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
interface ApprovalStepsV2Props {
|
interface ApprovalStepsV2Props {
|
||||||
|
title?: string;
|
||||||
approvals?: BaseApproval[];
|
approvals?: BaseApproval[];
|
||||||
steps: {
|
steps: {
|
||||||
step_number: number;
|
step_number: number;
|
||||||
@@ -23,6 +24,7 @@ interface ApprovalStepsV2Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ApprovalStepsV2 = ({
|
const ApprovalStepsV2 = ({
|
||||||
|
title = 'Progress Details',
|
||||||
approvals,
|
approvals,
|
||||||
steps,
|
steps,
|
||||||
maxVisibleSteps = 2,
|
maxVisibleSteps = 2,
|
||||||
@@ -99,7 +101,7 @@ const ApprovalStepsV2 = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||||
Progress Details
|
{title}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
|
|
||||||
interface StatusBadgeProps {
|
interface StatusBadgeProps {
|
||||||
color: Color;
|
color: Color;
|
||||||
text: string;
|
text: ReactNode;
|
||||||
className?: {
|
className?: {
|
||||||
badge?: string;
|
badge?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
};
|
};
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusBadge = ({
|
const StatusBadge = ({
|
||||||
color = 'neutral',
|
color = 'neutral',
|
||||||
text,
|
text,
|
||||||
className,
|
className,
|
||||||
|
onClick,
|
||||||
}: StatusBadgeProps) => {
|
}: StatusBadgeProps) => {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant='soft'
|
variant='soft'
|
||||||
|
onClick={onClick}
|
||||||
className={{
|
className={{
|
||||||
badge: cn(
|
badge: cn(
|
||||||
'px-2 py-1 w-full flex flex-row justify-start gap-1 rounded-lg border border-base-content/10 text-xs font-medium text-base-content',
|
'px-2 py-1 w-full flex flex-row justify-start gap-1 rounded-lg border border-base-content/10 text-xs font-medium text-base-content',
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export interface DrawerHeaderProps {
|
|||||||
|
|
||||||
const DrawerHeader = ({
|
const DrawerHeader = ({
|
||||||
leftIcon = 'mdi:close',
|
leftIcon = 'mdi:close',
|
||||||
leftIconSize = 24,
|
leftIconSize = 20,
|
||||||
leftIconHref,
|
leftIconHref,
|
||||||
leftIconOnClick,
|
leftIconOnClick,
|
||||||
leftIconClassName,
|
leftIconClassName,
|
||||||
@@ -43,7 +43,7 @@ const DrawerHeader = ({
|
|||||||
icon={leftIcon}
|
icon={leftIcon}
|
||||||
width={leftIconSize}
|
width={leftIconSize}
|
||||||
height={leftIconSize}
|
height={leftIconSize}
|
||||||
className={cn('cursor-pointer', leftIconClassName)}
|
className={cn('cursor-pointer text-base-content ', leftIconClassName)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ const DrawerHeader = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-row justify-between items-center px-4 pt-4 pb-4 border-b border-base-content/10',
|
'flex flex-row justify-between items-center p-4 border-b border-base-content/10',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -82,7 +82,7 @@ const DrawerHeader = ({
|
|||||||
{renderLeftIcon()}
|
{renderLeftIcon()}
|
||||||
|
|
||||||
{showDivider && subtitle && (
|
{showDivider && subtitle && (
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
<div className='w-px h-full border-none bg-base-content/10' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import Alert from '@/components/Alert';
|
import Alert from '@/components/Alert';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alert Unique Error List
|
* Alert Unique Error List
|
||||||
@@ -29,10 +31,22 @@ const AlertErrorList = ({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const alertRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formErrorList.length > 0) {
|
||||||
|
alertRef.current?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [formErrorList.length]);
|
||||||
|
|
||||||
if (formErrorList.length === 0) return null;
|
if (formErrorList.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
ref={alertRef}
|
||||||
color='error'
|
color='error'
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex flex-col gap-2 px-3 rounded-lg',
|
'w-full flex flex-col gap-2 px-3 rounded-lg',
|
||||||
@@ -57,6 +71,7 @@ const AlertErrorList = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
type='button'
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
variant='link'
|
variant='link'
|
||||||
className={cn('ml-auto p-0 w-fit text-white', className?.button)}
|
className={cn('ml-auto p-0 w-fit text-white', className?.button)}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const ExpenseStatusBadge = ({ approval }: ExpenseStatusBadgeProps) => {
|
|||||||
color={expenseStatusBadgeColor}
|
color={expenseStatusBadgeColor}
|
||||||
text={isLatestApprovalRejected ? 'Ditolak' : (approval?.step_name ?? '')}
|
text={isLatestApprovalRejected ? 'Ditolak' : (approval?.step_name ?? '')}
|
||||||
className={{
|
className={{
|
||||||
badge: 'w-fit',
|
badge: 'whitespace-nowrap max-w-max w-fit',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const RealizationStatusBadge = ({ approval }: RealizationStatusBadgeProps) => {
|
|||||||
color={realizationStatusBadgeColor}
|
color={realizationStatusBadgeColor}
|
||||||
text={isLatestApprovalRejected ? 'Ditolak' : realizationStatus}
|
text={isLatestApprovalRejected ? 'Ditolak' : realizationStatus}
|
||||||
className={{
|
className={{
|
||||||
badge: 'w-fit',
|
badge: 'whitespace-nowrap max-w-max w-fit',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import {
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
ChangeEventHandler,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { CellContext } from '@tanstack/react-table';
|
import { CellContext } from '@tanstack/react-table';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
@@ -40,6 +35,10 @@ import { Icon } from '@iconify/react';
|
|||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
import { useUiStore } from '@/stores/ui/ui.store';
|
||||||
|
import {
|
||||||
|
FinanceTableFilterSchema,
|
||||||
|
FinanceTableFilterValues,
|
||||||
|
} from './FinanceTableFilter.schema';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
@@ -152,10 +151,10 @@ const FinanceTable = () => {
|
|||||||
} = useTableFilter({
|
} = useTableFilter({
|
||||||
initial: {
|
initial: {
|
||||||
search: searchValue,
|
search: searchValue,
|
||||||
transactionType: '',
|
transactionTypes: '',
|
||||||
bankId: '',
|
bankIds: '',
|
||||||
customerId: '',
|
customerIds: '',
|
||||||
supplierId: '',
|
supplierIds: '',
|
||||||
sortBy: '',
|
sortBy: '',
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
@@ -163,10 +162,10 @@ const FinanceTable = () => {
|
|||||||
paramMap: {
|
paramMap: {
|
||||||
page: 'page',
|
page: 'page',
|
||||||
pageSize: 'limit',
|
pageSize: 'limit',
|
||||||
transactionType: 'transaction_type',
|
transactionTypes: 'transaction_types',
|
||||||
bankId: 'bank_id',
|
bankIds: 'bank_ids',
|
||||||
customerId: 'customer_id',
|
customerIds: 'customer_ids',
|
||||||
supplierId: 'supplier_id',
|
supplierIds: 'supplier_ids',
|
||||||
sortBy: 'sort_date',
|
sortBy: 'sort_date',
|
||||||
startDate: 'start_date',
|
startDate: 'start_date',
|
||||||
endDate: 'end_date',
|
endDate: 'end_date',
|
||||||
@@ -174,18 +173,7 @@ const FinanceTable = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ===== State =====
|
// ===== State =====
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const [pendingFilters, setPendingFilters] = useState({
|
|
||||||
search: searchValue,
|
|
||||||
transactionType: '',
|
|
||||||
bankId: '',
|
|
||||||
customerId: '',
|
|
||||||
supplierId: '',
|
|
||||||
sortBy: '',
|
|
||||||
startDate: '',
|
|
||||||
endDate: '',
|
|
||||||
});
|
|
||||||
const [selectedTransactionType, setSelectedTransactionType] = useState<
|
const [selectedTransactionType, setSelectedTransactionType] = useState<
|
||||||
OptionType | OptionType[] | null
|
OptionType | OptionType[] | null
|
||||||
>(null);
|
>(null);
|
||||||
@@ -201,6 +189,34 @@ const FinanceTable = () => {
|
|||||||
const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null);
|
const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null);
|
||||||
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
|
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
|
||||||
|
// ===== Formik for Filter =====
|
||||||
|
const filterFormik = useFormik<FinanceTableFilterValues>({
|
||||||
|
initialValues: {
|
||||||
|
search: searchValue,
|
||||||
|
transaction_types: '',
|
||||||
|
bank_ids: '',
|
||||||
|
customer_ids: '',
|
||||||
|
supplier_ids: '',
|
||||||
|
sort_by: '',
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
},
|
||||||
|
validationSchema: FinanceTableFilterSchema,
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: (values) => {
|
||||||
|
updateFilter('search', values.search);
|
||||||
|
setSearchValue(values.search);
|
||||||
|
updateFilter('transactionTypes', values.transaction_types);
|
||||||
|
updateFilter('bankIds', values.bank_ids);
|
||||||
|
updateFilter('customerIds', values.customer_ids);
|
||||||
|
updateFilter('supplierIds', values.supplier_ids);
|
||||||
|
updateFilter('sortBy', values.sort_by);
|
||||||
|
updateFilter('startDate', values.start_date);
|
||||||
|
updateFilter('endDate', values.end_date);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ===== Fetch Data =====
|
// ===== Fetch Data =====
|
||||||
const {
|
const {
|
||||||
@@ -237,84 +253,141 @@ const FinanceTable = () => {
|
|||||||
loadMore: bankLoadMore,
|
loadMore: bankLoadMore,
|
||||||
} = useSelect<Bank>(BankApi.basePath, 'id', 'alias');
|
} = useSelect<Bank>(BankApi.basePath, 'id', 'alias');
|
||||||
|
|
||||||
|
const bankSelectOptions = useMemo(() => {
|
||||||
|
if (!isResponseSuccess(bankRawData)) return [];
|
||||||
|
|
||||||
|
return bankOptions.map((bank) => {
|
||||||
|
const bankData = bankRawData.data.find((data) => data.id === bank?.value);
|
||||||
|
return {
|
||||||
|
label: bankData
|
||||||
|
? `${bankData.alias} - ${bankData.account_number} - ${bankData.owner}`
|
||||||
|
: '',
|
||||||
|
value: bank?.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [bankOptions, bankRawData]);
|
||||||
|
|
||||||
// ===== Handler =====
|
// ===== Handler =====
|
||||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const searchChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setPendingFilters((prev) => ({ ...prev, search: e.target.value }));
|
filterFormik.setFieldValue('search', e.target.value);
|
||||||
};
|
};
|
||||||
const transactionTypeChangeHandler = (
|
const transactionTypeChangeHandler = (
|
||||||
val: OptionType | OptionType[] | null
|
val: OptionType | OptionType[] | null
|
||||||
) => {
|
) => {
|
||||||
setSelectedTransactionType(val);
|
setSelectedTransactionType(val);
|
||||||
setPendingFilters((prev) => ({
|
filterFormik.setFieldValue(
|
||||||
...prev,
|
'transaction_types',
|
||||||
transactionType: val
|
val
|
||||||
? Array.isArray(val)
|
? Array.isArray(val)
|
||||||
? val.map((item) => item.value).join(',')
|
? val.map((item) => item.value).join(',')
|
||||||
: (val.value as string)
|
: (val.value as string)
|
||||||
: '',
|
: ''
|
||||||
}));
|
);
|
||||||
};
|
};
|
||||||
const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setSelectedBank(val);
|
setSelectedBank(val);
|
||||||
setPendingFilters((prev) => ({
|
filterFormik.setFieldValue(
|
||||||
...prev,
|
'bank_ids',
|
||||||
bankId: val
|
val
|
||||||
? Array.isArray(val)
|
? Array.isArray(val)
|
||||||
? val.map((item) => item.value).join(',')
|
? val.map((item) => item.value).join(',')
|
||||||
: (val.value as string)
|
: (val.value as string)
|
||||||
: '',
|
: ''
|
||||||
}));
|
);
|
||||||
};
|
};
|
||||||
const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setSelectedCustomerId(val);
|
setSelectedCustomerId(val);
|
||||||
setPendingFilters((prev) => ({
|
filterFormik.setFieldValue(
|
||||||
...prev,
|
'customer_ids',
|
||||||
customerId: val
|
val
|
||||||
? Array.isArray(val)
|
? Array.isArray(val)
|
||||||
? val.map((item) => item.value).join(',')
|
? val.map((item) => item.value).join(',')
|
||||||
: (val.value as string)
|
: (val.value as string)
|
||||||
: '',
|
: ''
|
||||||
}));
|
);
|
||||||
};
|
};
|
||||||
const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setSelectedSupplierId(val);
|
setSelectedSupplierId(val);
|
||||||
setPendingFilters((prev) => ({
|
filterFormik.setFieldValue(
|
||||||
...prev,
|
'supplier_ids',
|
||||||
supplierId: val
|
val
|
||||||
? Array.isArray(val)
|
? Array.isArray(val)
|
||||||
? val.map((item) => item.value).join(',')
|
? val.map((item) => item.value).join(',')
|
||||||
: (val.value as string)
|
: (val.value as string)
|
||||||
: '',
|
: ''
|
||||||
}));
|
);
|
||||||
};
|
};
|
||||||
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
setSelectedSortBy(val as OptionType);
|
setSelectedSortBy(val as OptionType);
|
||||||
setPendingFilters((prev) => ({
|
filterFormik.setFieldValue(
|
||||||
...prev,
|
'sort_by',
|
||||||
sortBy: val ? ((val as OptionType).value as string) : '',
|
val ? ((val as OptionType).value as string) : ''
|
||||||
}));
|
);
|
||||||
};
|
};
|
||||||
const startDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
||||||
setPendingFilters((prev) => ({ ...prev, startDate: e.target.value }));
|
const startDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const endDate = filterFormik.values.end_date;
|
||||||
|
|
||||||
|
filterFormik.setFieldValue('start_date', value);
|
||||||
|
|
||||||
|
if (value && endDate) {
|
||||||
|
const startDate = new Date(value);
|
||||||
|
const endDateObj = new Date(endDate);
|
||||||
|
|
||||||
|
if (endDateObj < startDate) {
|
||||||
|
filterFormik.setFieldError(
|
||||||
|
'end_date',
|
||||||
|
'Tanggal akhir tidak boleh masa lampau'
|
||||||
|
);
|
||||||
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filterFormik.setFieldError('end_date', undefined);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const endDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
||||||
setPendingFilters((prev) => ({ ...prev, endDate: e.target.value }));
|
const endDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
};
|
const value = e.target.value;
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const startDate = filterFormik.values.start_date;
|
||||||
const newVal = val as OptionType;
|
|
||||||
setPageSize(newVal.value as number);
|
filterFormik.setFieldValue('end_date', value);
|
||||||
};
|
|
||||||
const submitFilterHandler = () => {
|
if (value && startDate) {
|
||||||
updateFilter('search', pendingFilters.search);
|
const startDateObj = new Date(startDate);
|
||||||
setSearchValue(pendingFilters.search);
|
const endDate = new Date(value);
|
||||||
updateFilter('transactionType', pendingFilters.transactionType);
|
|
||||||
updateFilter('bankId', pendingFilters.bankId);
|
if (endDate < startDateObj) {
|
||||||
updateFilter('customerId', pendingFilters.customerId);
|
filterFormik.setFieldError(
|
||||||
updateFilter('supplierId', pendingFilters.supplierId);
|
'end_date',
|
||||||
updateFilter('sortBy', pendingFilters.sortBy);
|
'Tanggal akhir tidak boleh masa lampau'
|
||||||
updateFilter('startDate', pendingFilters.startDate);
|
);
|
||||||
updateFilter('endDate', pendingFilters.endDate);
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFormik.setFieldError('end_date', undefined);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetFilterHandler = () => {
|
const resetFilterHandler = () => {
|
||||||
setSelectedTransactionType(null);
|
setSelectedTransactionType(null);
|
||||||
setSelectedBank(null);
|
setSelectedBank(null);
|
||||||
@@ -322,24 +395,14 @@ const FinanceTable = () => {
|
|||||||
setSelectedSupplierId(null);
|
setSelectedSupplierId(null);
|
||||||
setSelectedSortBy(null);
|
setSelectedSortBy(null);
|
||||||
|
|
||||||
const emptyFilters = {
|
filterFormik.resetForm();
|
||||||
search: '',
|
|
||||||
transactionType: '',
|
|
||||||
bankId: '',
|
|
||||||
customerId: '',
|
|
||||||
supplierId: '',
|
|
||||||
sortBy: '',
|
|
||||||
startDate: '',
|
|
||||||
endDate: '',
|
|
||||||
};
|
|
||||||
setPendingFilters(emptyFilters);
|
|
||||||
|
|
||||||
updateFilter('search', '');
|
updateFilter('search', '');
|
||||||
resetSearchValue();
|
resetSearchValue();
|
||||||
updateFilter('transactionType', '');
|
updateFilter('transactionTypes', '');
|
||||||
updateFilter('bankId', '');
|
updateFilter('bankIds', '');
|
||||||
updateFilter('customerId', '');
|
updateFilter('customerIds', '');
|
||||||
updateFilter('supplierId', '');
|
updateFilter('supplierIds', '');
|
||||||
updateFilter('sortBy', '');
|
updateFilter('sortBy', '');
|
||||||
updateFilter('startDate', '');
|
updateFilter('startDate', '');
|
||||||
updateFilter('endDate', '');
|
updateFilter('endDate', '');
|
||||||
@@ -464,26 +527,36 @@ const FinanceTable = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Store current path on mount
|
return () => {
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dateErrorShown]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
previousPathRef.current = window.location.pathname;
|
previousPathRef.current = window.location.pathname;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
// if both paths are within /finance module
|
|
||||||
const isCurrentPathFinance = currentPath.includes('/finance');
|
const isCurrentPathFinance = currentPath.includes('/finance');
|
||||||
const isPreviousPathFinance =
|
const isPreviousPathFinance =
|
||||||
previousPathRef.current?.includes('/finance');
|
previousPathRef.current?.includes('/finance');
|
||||||
|
|
||||||
// reset if we outside finance module entirely
|
|
||||||
if (isPreviousPathFinance && !isCurrentPathFinance) {
|
if (isPreviousPathFinance && !isCurrentPathFinance) {
|
||||||
resetSearchValue();
|
resetSearchValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [resetSearchValue]);
|
}, [resetSearchValue, dateErrorShown]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='size-full p-6 flex flex-col gap-6'>
|
<section className='size-full flex flex-col gap-6'>
|
||||||
<div className='flex justify-end gap-2'>
|
<div className='flex justify-end gap-2'>
|
||||||
<RequirePermission permissions='lti.finance.injections.create'>
|
<RequirePermission permissions='lti.finance.injections.create'>
|
||||||
<Button
|
<Button
|
||||||
@@ -526,7 +599,7 @@ const FinanceTable = () => {
|
|||||||
<Button
|
<Button
|
||||||
color='primary'
|
color='primary'
|
||||||
className='min-w-24'
|
className='min-w-24'
|
||||||
onClick={submitFilterHandler}
|
onClick={() => filterFormik.handleSubmit()}
|
||||||
>
|
>
|
||||||
Cari
|
Cari
|
||||||
</Button>
|
</Button>
|
||||||
@@ -539,6 +612,7 @@ const FinanceTable = () => {
|
|||||||
label='Jenis Transaksi'
|
label='Jenis Transaksi'
|
||||||
value={selectedTransactionType}
|
value={selectedTransactionType}
|
||||||
onChange={transactionTypeChangeHandler}
|
onChange={transactionTypeChangeHandler}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
isClearable
|
isClearable
|
||||||
isMulti
|
isMulti
|
||||||
/>
|
/>
|
||||||
@@ -550,6 +624,7 @@ const FinanceTable = () => {
|
|||||||
onInputChange={customerInputValue}
|
onInputChange={customerInputValue}
|
||||||
onMenuScrollToBottom={customerLoadMore}
|
onMenuScrollToBottom={customerLoadMore}
|
||||||
isLoading={customerIsLoadingOptions}
|
isLoading={customerIsLoadingOptions}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
isClearable
|
isClearable
|
||||||
isMulti
|
isMulti
|
||||||
/>
|
/>
|
||||||
@@ -561,31 +636,18 @@ const FinanceTable = () => {
|
|||||||
onInputChange={supplierInputValue}
|
onInputChange={supplierInputValue}
|
||||||
onMenuScrollToBottom={supplierLoadMore}
|
onMenuScrollToBottom={supplierLoadMore}
|
||||||
isLoading={supplierIsLoadingOptions}
|
isLoading={supplierIsLoadingOptions}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
isClearable
|
isClearable
|
||||||
isMulti
|
isMulti
|
||||||
/>
|
/>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
options={
|
options={bankSelectOptions}
|
||||||
isResponseSuccess(bankRawData)
|
|
||||||
? bankOptions.map((bank) => ({
|
|
||||||
label:
|
|
||||||
bankRawData.data.find((data) => data.id === bank?.value)
|
|
||||||
?.alias +
|
|
||||||
' - ' +
|
|
||||||
bankRawData.data.find((data) => data.id === bank?.value)
|
|
||||||
?.account_number +
|
|
||||||
' - ' +
|
|
||||||
bankRawData.data.find((data) => data.id === bank?.value)
|
|
||||||
?.owner,
|
|
||||||
value: bank?.value,
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
label='Bank'
|
label='Bank'
|
||||||
value={selectedBank}
|
value={selectedBank}
|
||||||
onChange={bankChangeHandler}
|
onChange={bankChangeHandler}
|
||||||
onInputChange={bankInputValue}
|
onInputChange={bankInputValue}
|
||||||
onMenuScrollToBottom={bankLoadMore}
|
onMenuScrollToBottom={bankLoadMore}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
isClearable
|
isClearable
|
||||||
isMulti
|
isMulti
|
||||||
/>
|
/>
|
||||||
@@ -597,22 +659,32 @@ const FinanceTable = () => {
|
|||||||
isClearable
|
isClearable
|
||||||
/>
|
/>
|
||||||
<DateInput
|
<DateInput
|
||||||
name='startDate'
|
name='start_date'
|
||||||
label='Periode Tanggal (Mulai)'
|
label='Periode Tanggal (Mulai)'
|
||||||
value={pendingFilters.startDate}
|
value={filterFormik.values.start_date}
|
||||||
onChange={startDateChangeHandler}
|
onChange={startDateChangeHandler}
|
||||||
|
errorMessage={
|
||||||
|
filterFormik.errors.end_date
|
||||||
|
? filterFormik.errors.end_date
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<DateInput
|
<DateInput
|
||||||
name='endDate'
|
name='end_date'
|
||||||
label='Periode Tanggal (Akhir)'
|
label='Periode Tanggal (Akhir)'
|
||||||
value={pendingFilters.endDate}
|
value={filterFormik.values.end_date}
|
||||||
onChange={endDateChangeHandler}
|
onChange={endDateChangeHandler}
|
||||||
|
errorMessage={
|
||||||
|
filterFormik.errors.end_date
|
||||||
|
? filterFormik.errors.end_date
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
name='search'
|
name='search'
|
||||||
label='Cari'
|
label='Cari'
|
||||||
placeholder='Cari'
|
placeholder='Cari'
|
||||||
value={pendingFilters.search}
|
value={filterFormik.values.search}
|
||||||
onChange={searchChangeHandler}
|
onChange={searchChangeHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
export type FinanceTableFilterType = {
|
||||||
|
search: string;
|
||||||
|
transaction_types: string;
|
||||||
|
bank_ids: string;
|
||||||
|
customer_ids: string;
|
||||||
|
supplier_ids: string;
|
||||||
|
sort_by: string;
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FinanceTableFilterSchema = yup.object({
|
||||||
|
search: yup.string().optional(),
|
||||||
|
transaction_types: yup.string().optional(),
|
||||||
|
bank_ids: yup.string().optional(),
|
||||||
|
customer_ids: yup.string().optional(),
|
||||||
|
supplier_ids: yup.string().optional(),
|
||||||
|
sort_by: yup.string().optional(),
|
||||||
|
start_date: yup.string().optional(),
|
||||||
|
end_date: yup
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.test(
|
||||||
|
'is-greater-than-start',
|
||||||
|
'Tanggal akhir tidak boleh masa lampau',
|
||||||
|
function (value) {
|
||||||
|
const { start_date } = this.parent;
|
||||||
|
if (!start_date || !value) return true;
|
||||||
|
return new Date(value) >= new Date(start_date);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}) as yup.ObjectSchema<FinanceTableFilterType>;
|
||||||
|
|
||||||
|
export type FinanceTableFilterValues = yup.InferType<
|
||||||
|
typeof FinanceTableFilterSchema
|
||||||
|
>;
|
||||||
@@ -623,7 +623,10 @@ const MarketingTable = () => {
|
|||||||
data={allData}
|
data={allData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pageSize={tableFilterState.pageSize}
|
pageSize={tableFilterState.pageSize}
|
||||||
page={tableFilterState.page}
|
page={isResponseSuccess(marketing) ? marketing?.meta?.page : 1}
|
||||||
|
totalItems={
|
||||||
|
isResponseSuccess(marketing) ? marketing?.meta?.total_results : 0
|
||||||
|
}
|
||||||
isLoading={isLoadingMarketing}
|
isLoading={isLoadingMarketing}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn('p-3', {
|
containerClassName: cn('p-3', {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const ChickinFormKandang = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section className='w-full h-full sm:w-[446px] overflow-y-auto'>
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`}
|
subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`}
|
||||||
leftIcon='mdi:arrow-left'
|
leftIcon='mdi:arrow-left'
|
||||||
@@ -198,7 +198,7 @@ const ChickinFormKandang = ({
|
|||||||
afterSubmit={afterSubmitFormChickin}
|
afterSubmit={afterSubmitFormChickin}
|
||||||
/>
|
/>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ChangeEventHandler, RefObject, useId, useState } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import ConfirmationModal, {
|
||||||
|
ConfirmationModalProps,
|
||||||
|
} from '@/components/modal/ConfirmationModal';
|
||||||
|
import TextArea from '@/components/input/TextArea';
|
||||||
|
import { ProjectFlockFormConfirmationTable } from '@/components/pages/production/project-flock/form/ProjectFlockForm';
|
||||||
|
|
||||||
|
import { ProjectFlockFormValues } from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema';
|
||||||
|
import { Color } from '@/types/theme';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { KandangApi } from '@/services/api/master-data';
|
||||||
|
|
||||||
|
interface ProjectFlockConfirmationModalProps
|
||||||
|
extends Omit<ConfirmationModalProps, 'children' | 'primaryButton'> {
|
||||||
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
type?: 'info' | 'success' | 'error';
|
||||||
|
projectFlockIds?: number[];
|
||||||
|
projectFlockForm?: ProjectFlockFormValues;
|
||||||
|
onClose?: () => void;
|
||||||
|
|
||||||
|
withNote?: boolean;
|
||||||
|
noteLabel?: string;
|
||||||
|
rows?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
|
||||||
|
primaryButton?: {
|
||||||
|
text?: string;
|
||||||
|
color?: Color;
|
||||||
|
isLoading?: boolean;
|
||||||
|
onClick?: (notes: string) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectFlockConfirmationModal = ({
|
||||||
|
ref,
|
||||||
|
type = 'success',
|
||||||
|
projectFlockForm,
|
||||||
|
projectFlockIds,
|
||||||
|
onClose,
|
||||||
|
withNote,
|
||||||
|
rows = 4,
|
||||||
|
noteLabel,
|
||||||
|
placeholder = 'Alasan',
|
||||||
|
primaryButton,
|
||||||
|
secondaryButton,
|
||||||
|
...props
|
||||||
|
}: ProjectFlockConfirmationModalProps) => {
|
||||||
|
const randomId = useId();
|
||||||
|
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
|
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||||
|
search: '',
|
||||||
|
location_id: projectFlockForm?.location_id
|
||||||
|
? String(projectFlockForm?.location_id)
|
||||||
|
: '',
|
||||||
|
limit: '500',
|
||||||
|
}).toString()}`;
|
||||||
|
const {
|
||||||
|
data: kandang,
|
||||||
|
isLoading: isLoadingKandang,
|
||||||
|
mutate: refreshKandang,
|
||||||
|
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
|
||||||
|
|
||||||
|
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'>
|
||||||
|
{!projectFlockIds && projectFlockForm && (
|
||||||
|
<ProjectFlockFormConfirmationTable
|
||||||
|
projectFlockForm={projectFlockForm}
|
||||||
|
kandangs={
|
||||||
|
isResponseSuccess(kandang) && kandang?.data ? kandang.data : []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* {projectFlockIds &&
|
||||||
|
!projectFlockForm &&
|
||||||
|
projectFlockIds.map((projectFlockId, idx) => (
|
||||||
|
<ProjectFlockFormConfirmationTable
|
||||||
|
key={idx}
|
||||||
|
projectFlockId={projectFlockId}
|
||||||
|
kandangs={
|
||||||
|
isResponseSuccess(kandang) && kandang?.data ? kandang.data : []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))} */}
|
||||||
|
|
||||||
|
{withNote && (
|
||||||
|
<TextArea
|
||||||
|
name={randomId}
|
||||||
|
label={noteLabel}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={notes}
|
||||||
|
onChange={notesChangeHandler}
|
||||||
|
rows={rows}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ConfirmationModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectFlockConfirmationModal;
|
||||||
@@ -13,6 +13,7 @@ import { useModal } from '@/components/Modal';
|
|||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
|
import Dropdown from '@/components/Dropdown';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn, formatDate, formatTitleCase } from '@/lib/helper';
|
import { cn, formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
@@ -29,6 +30,111 @@ import toast from 'react-hot-toast';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
|
import PopoverButton from '@/components/popover/PopoverButton';
|
||||||
|
import PopoverContent from '@/components/popover/PopoverContent';
|
||||||
|
|
||||||
|
const RowOptionsMenu = ({
|
||||||
|
props,
|
||||||
|
popoverPosition = 'bottom',
|
||||||
|
editClickHandler,
|
||||||
|
detailClickHandler,
|
||||||
|
deleteClickHandler,
|
||||||
|
}: {
|
||||||
|
props: CellContext<ProjectFlock, unknown>;
|
||||||
|
popoverPosition: 'bottom' | 'top';
|
||||||
|
editClickHandler: (id: number) => void;
|
||||||
|
detailClickHandler: (id: number) => void;
|
||||||
|
deleteClickHandler: () => void;
|
||||||
|
}) => {
|
||||||
|
// TODO: change this to real condition
|
||||||
|
const showEditButton = true;
|
||||||
|
|
||||||
|
const showDeleteButton = showEditButton;
|
||||||
|
|
||||||
|
const popoverId = `projectFlock#${props.row.original.id}`;
|
||||||
|
const popoverAnchorName = `--anchor-projectFlock#${props.row.original.id}`;
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
document.getElementById(popoverId)?.hidePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailClickHandlerWrapper = () => {
|
||||||
|
detailClickHandler(props.row.original.id);
|
||||||
|
closePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editClickHandlerWrapper = () => {
|
||||||
|
editClickHandler(props.row.original.id);
|
||||||
|
closePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<PopoverButton
|
||||||
|
tabIndex={0}
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
popoverTarget={popoverId}
|
||||||
|
anchorName={popoverAnchorName}
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
||||||
|
</PopoverButton>
|
||||||
|
|
||||||
|
<PopoverContent
|
||||||
|
id={popoverId}
|
||||||
|
anchorName={popoverAnchorName}
|
||||||
|
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
|
||||||
|
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
|
||||||
|
>
|
||||||
|
<div className='flex flex-col bg-base-100 rounded-xl'>
|
||||||
|
<RequirePermission permissions='lti.production.project_flocks.detail'>
|
||||||
|
<Button
|
||||||
|
// href={`/production/project-flock/detail/?projectFlockId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={detailClickHandlerWrapper}
|
||||||
|
className='p-3 justify-start text-sm font-semibold w-full'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:eye' width={20} height={20} />
|
||||||
|
View Details
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{showEditButton && (
|
||||||
|
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||||
|
<Button
|
||||||
|
// href={`/production/project-flock/detail/edit/?projectFlockId=${props.row.original.id}`}
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={editClickHandlerWrapper}
|
||||||
|
className='p-3 justify-start text-sm font-semibold w-full'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:pencil-square' width={20} height={20} />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showDeleteButton && (
|
||||||
|
<RequirePermission permissions='lti.production.project_flocks.delete'>
|
||||||
|
<hr className='mx-3 border-base-content/10 h-px' />
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='p-3 justify-start text-sm font-semibold w-full'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||||
const {
|
const {
|
||||||
@@ -62,6 +168,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
const selectedRowIds = Object.keys(rowSelection)
|
const selectedRowIds = Object.keys(rowSelection)
|
||||||
.filter((id) => rowSelection[id])
|
.filter((id) => rowSelection[id])
|
||||||
.map((id) => parseInt(id));
|
.map((id) => parseInt(id));
|
||||||
|
|
||||||
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
|
||||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||||
null
|
null
|
||||||
@@ -78,6 +185,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
);
|
);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
// ===== Fetch Data =====
|
// ===== Fetch Data =====
|
||||||
const {
|
const {
|
||||||
@@ -175,14 +284,27 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
: null;
|
: null;
|
||||||
}, [rowSelection]);
|
}, [rowSelection]);
|
||||||
|
|
||||||
|
// const canApprove = useMemo(() => {
|
||||||
|
// if (!selectedSingleRow || isApproveLoading) return false;
|
||||||
|
|
||||||
|
// const isPengajuan = selectedSingleRow.approval?.step_number == 1;
|
||||||
|
// const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED';
|
||||||
|
|
||||||
|
// return isPengajuan && isNotRejected;
|
||||||
|
// }, [selectedSingleRow, isApproveLoading]);
|
||||||
|
|
||||||
const canApprove = useMemo(() => {
|
const canApprove = useMemo(() => {
|
||||||
if (!selectedSingleRow || isApproveLoading) return false;
|
return selectedRowIds.every((id) => {
|
||||||
|
const projectFlock = isResponseSuccess(projectFlocks)
|
||||||
|
? projectFlocks?.data.find((row) => row.id === id)
|
||||||
|
: null;
|
||||||
|
|
||||||
const isPengajuan = selectedSingleRow.approval?.step_number == 1;
|
const isProjectFlockRequesting = projectFlock?.approval?.step_number == 1;
|
||||||
const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED';
|
const isProjectFlockNotRejected =
|
||||||
|
projectFlock?.approval?.action != 'REJECTED';
|
||||||
return isPengajuan && isNotRejected;
|
return isProjectFlockRequesting && isProjectFlockNotRejected;
|
||||||
}, [selectedSingleRow, isApproveLoading]);
|
});
|
||||||
|
}, [selectedRowIds, projectFlocks]);
|
||||||
|
|
||||||
// ====== COLUMNS ======
|
// ====== COLUMNS ======
|
||||||
const columns = useMemo<ColumnDef<ProjectFlock>[]>(
|
const columns = useMemo<ColumnDef<ProjectFlock>[]>(
|
||||||
@@ -256,44 +378,39 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
const approval = props.row.original.approval;
|
const approval = props.row.original.approval;
|
||||||
const isRejected = approval?.action == 'REJECTED';
|
const isRejected = approval?.action == 'REJECTED';
|
||||||
const isApproved = approval?.action == 'APPROVED';
|
const isApproved = approval?.action == 'APPROVED';
|
||||||
return (
|
|
||||||
<Badge
|
let latestApprovalStepName = approval.step_name;
|
||||||
variant='soft'
|
|
||||||
className={{
|
const badgeColor = isRejected
|
||||||
badge: 'rounded-lg px-2 w-full flex flex-row justify-start',
|
? 'error'
|
||||||
}}
|
: isApproved
|
||||||
color={
|
? approval?.step_number == 1
|
||||||
isRejected
|
? 'neutral'
|
||||||
? 'error'
|
: approval?.step_number == 2
|
||||||
: isApproved
|
? 'success'
|
||||||
? approval?.step_number == 1
|
: approval?.step_number == 3
|
||||||
? 'neutral'
|
? 'error'
|
||||||
: approval?.step_number == 2
|
|
||||||
? 'primary'
|
|
||||||
: approval?.step_number == 3
|
|
||||||
? 'success'
|
|
||||||
: 'neutral'
|
|
||||||
: 'neutral'
|
: 'neutral'
|
||||||
}
|
: 'neutral';
|
||||||
>
|
|
||||||
<Icon
|
switch (approval.action.toLowerCase()) {
|
||||||
icon='mdi:circle'
|
case 'pengajuan':
|
||||||
width={12}
|
latestApprovalStepName = 'Pengajuan';
|
||||||
height={12}
|
break;
|
||||||
color={
|
case 'aktif':
|
||||||
approval?.step_number == 1
|
latestApprovalStepName = 'Aktif';
|
||||||
? 'neutral'
|
break;
|
||||||
: approval?.step_number == 2
|
case 'Selesai':
|
||||||
? 'primary'
|
latestApprovalStepName = 'Closing';
|
||||||
: approval?.step_number == 3
|
break;
|
||||||
? 'success'
|
}
|
||||||
: 'neutral'
|
|
||||||
}
|
if (isRejected) {
|
||||||
/>
|
latestApprovalStepName = 'Ditolak';
|
||||||
{isRejected
|
}
|
||||||
? 'Ditolak'
|
|
||||||
: formatTitleCase(approval?.step_name || '')}
|
return (
|
||||||
</Badge>
|
<StatusBadge color={badgeColor} text={latestApprovalStepName} />
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -325,27 +442,88 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
cell: (props) =>
|
cell: (props) =>
|
||||||
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
formatDate(props.row.original.created_at, 'MMM DD, YYYY'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
cell: (props) => {
|
||||||
|
const currentPageSize =
|
||||||
|
props.table.getPaginationRowModel().rows.length;
|
||||||
|
const currentPageRows = props.table.getPaginationRowModel().flatRows;
|
||||||
|
const currentRowRelativeIndex =
|
||||||
|
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||||
|
|
||||||
|
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||||
|
|
||||||
|
const detailClickHandler = (id: number) => {
|
||||||
|
router.push(
|
||||||
|
`/production/project-flock/detail/?projectFlockId=${id}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editClickHandler = (id: number) => {
|
||||||
|
router.push(
|
||||||
|
`/production/project-flock/detail/edit/?projectFlockId=${id}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteClickHandler = () => {
|
||||||
|
// Set row selection
|
||||||
|
setRowSelection({
|
||||||
|
[String(props.row.original.id)]: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RowOptionsMenu
|
||||||
|
props={props}
|
||||||
|
detailClickHandler={detailClickHandler}
|
||||||
|
editClickHandler={editClickHandler}
|
||||||
|
deleteClickHandler={deleteClickHandler}
|
||||||
|
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const exportToExcelHandler = async () => {
|
||||||
|
setIsLoadingExportingToExcel(true);
|
||||||
|
|
||||||
|
toast.error('Not implemented yet!');
|
||||||
|
|
||||||
|
setIsLoadingExportingToExcel(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkApproveClickHandler = () => {
|
||||||
|
setApprovalAction('APPROVED');
|
||||||
|
confirmModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkRejectClickHandler = () => {
|
||||||
|
setApprovalAction('REJECTED');
|
||||||
|
confirmModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='min-h-screen w-full p-4'>
|
<div className='@container min-h-screen w-full'>
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col mb-4'>
|
||||||
<div className='w-full flex flex-col justify-between items-end gap-2'>
|
{/* <div className='w-full flex flex-col justify-between items-end gap-2'>
|
||||||
<div className='flex flex-col sm:flex-row gap-3 w-full'>
|
<div className='flex flex-col sm:flex-row gap-3 w-full'>
|
||||||
<RequirePermission permissions='lti.production.project_flocks.create'>
|
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||||
<Button
|
<Button
|
||||||
color='primary'
|
color='primary'
|
||||||
className='w-full sm:w-fit'
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
router.push('/production/project-flock/add');
|
router.push('/production/project-flock/add');
|
||||||
}}
|
}}
|
||||||
|
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||||
>
|
>
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||||
Tambah
|
Add Flock
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
<div className='ms-auto w-full sm:w-auto'>
|
<div className='ms-auto w-full sm:w-auto'>
|
||||||
@@ -423,6 +601,158 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
|
||||||
|
<div className='w-fit flex flex-row gap-3 flex-wrap'>
|
||||||
|
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||||
|
<Button
|
||||||
|
color='primary'
|
||||||
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
router.push('/production/project-flock/add');
|
||||||
|
}}
|
||||||
|
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||||
|
Add Flock
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
{selectedRowIds.length > 0 && canApprove && (
|
||||||
|
<>
|
||||||
|
<hr className='w-px h-full border-none bg-base-content/10 hidden @sm:block' />
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
onClick={bulkRejectClickHandler}
|
||||||
|
disabled={selectedRowIds.length === 0}
|
||||||
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:x-mark'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-error'
|
||||||
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
|
||||||
|
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
onClick={bulkApproveClickHandler}
|
||||||
|
disabled={selectedRowIds.length === 0}
|
||||||
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:check'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className='text-success'
|
||||||
|
/>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</RequirePermission>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
|
||||||
|
<DebouncedTextInput
|
||||||
|
name='search'
|
||||||
|
placeholder='Search'
|
||||||
|
value={tableFilterState.search ?? ''}
|
||||||
|
onChange={searchChangeHandler}
|
||||||
|
startAdornment={
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:magnifying-glass'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full min-w-24 max-w-3xs',
|
||||||
|
inputWrapper: 'rounded-xl! shadow-button-soft',
|
||||||
|
input:
|
||||||
|
'placeholder:font-semibold placeholder:text-base-content/50',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
// onClick={filterModal.openModal}
|
||||||
|
className={cn(
|
||||||
|
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
|
||||||
|
{
|
||||||
|
// 'border-primary-gradient text-primary': isFilterActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||||
|
Filter
|
||||||
|
{/* {isFilterActive && (
|
||||||
|
<Badge
|
||||||
|
className={{
|
||||||
|
badge:
|
||||||
|
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filterCount}
|
||||||
|
</Badge>
|
||||||
|
)} */}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||||
|
>
|
||||||
|
<div className='flex flex-row items-center gap-1.5'>
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>Export</span>
|
||||||
|
|
||||||
|
<div className='w-px self-stretch bg-base-content/10' />
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon='heroicons:chevron-down'
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={exportToExcelHandler}
|
||||||
|
isLoading={isLoadingExportingToExcel}
|
||||||
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
|
Export to Excel
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table<ProjectFlock>
|
<Table<ProjectFlock>
|
||||||
@@ -448,26 +778,20 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
setRowSelection={setRowSelection}
|
setRowSelection={setRowSelection}
|
||||||
|
withCheckbox
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn({
|
containerClassName: cn('p-3', {
|
||||||
'mb-40':
|
'w-full mb-20':
|
||||||
isResponseSuccess(projectFlocks) &&
|
isResponseSuccess(projectFlocks) &&
|
||||||
projectFlocks?.data?.length > 0,
|
projectFlocks?.data?.length === 0,
|
||||||
}),
|
}),
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
headerColumnClassName: 'text-nowrap',
|
||||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
|
||||||
headerColumnClassName:
|
|
||||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
|
||||||
bodyColumnClassName:
|
|
||||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FloatingActionsButton
|
{/* <FloatingActionsButton
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
action: 'DETAIL',
|
action: 'DETAIL',
|
||||||
@@ -520,7 +844,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
|
|||||||
+213
-208
@@ -94,230 +94,235 @@ const ProjectFlockClosingForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DrawerHeader
|
<section className='w-full h-full sm:w-[446px] overflow-y-auto'>
|
||||||
leftIcon='mdi:arrow-left'
|
<DrawerHeader
|
||||||
leftIconHref={`/production/project-flock/detail?projectFlockId=${projectFlock.id}`}
|
leftIcon='mdi:arrow-left'
|
||||||
subtitle={`Close ${projectFlock.flock_name}`}
|
leftIconHref={`/production/project-flock/detail?projectFlockId=${projectFlock.id}`}
|
||||||
></DrawerHeader>
|
subtitle={`Close ${projectFlock.flock_name}`}
|
||||||
|
></DrawerHeader>
|
||||||
|
|
||||||
{/* Informasi Kandang */}
|
{/* Informasi Kandang */}
|
||||||
<div className='divider'></div>
|
<div className='divider'></div>
|
||||||
<div className='px-4 pb-4 flex flex-col gap-4'>
|
<div className='px-4 pb-4 flex flex-col gap-4'>
|
||||||
<h2 className='text-2xl font-semibold'>Informasi Kandang</h2>
|
<h2 className='text-2xl font-semibold'>Informasi Kandang</h2>
|
||||||
|
|
||||||
{/* Badge Row */}
|
{/* Badge Row */}
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Badge
|
<Badge
|
||||||
variant='soft'
|
variant='soft'
|
||||||
color='success'
|
color='success'
|
||||||
className={{
|
className={{
|
||||||
badge: 'rounded-lg px-2',
|
badge: 'rounded-lg px-2',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:circle' width={12} height={12} color='success' />{' '}
|
<Icon icon='mdi:circle' width={12} height={12} color='success' />{' '}
|
||||||
Aktif
|
Aktif
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||||
<Badge
|
<Badge
|
||||||
color='neutral'
|
color='neutral'
|
||||||
variant='soft'
|
variant='soft'
|
||||||
className={{ badge: 'rounded-lg px-2' }}
|
className={{ badge: 'rounded-lg px-2' }}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:home' width={12} height={12} />
|
<Icon icon='mdi:home' width={12} height={12} />
|
||||||
{` Kapasitas ${formatNumber(projectFlockKandang.kandang?.capacity)} Ekor`}
|
{` Kapasitas ${formatNumber(projectFlockKandang.kandang?.capacity)} Ekor`}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Information Grid */}
|
||||||
|
<div className='grid grid-cols-3 gap-4'>
|
||||||
|
{/* Area */}
|
||||||
|
<div
|
||||||
|
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
||||||
|
relative
|
||||||
|
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
||||||
|
>
|
||||||
|
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
|
||||||
|
</div>
|
||||||
|
<div className='col-span-2'>{projectFlock.area?.name}</div>
|
||||||
|
|
||||||
|
{/* Lokasi */}
|
||||||
|
<div
|
||||||
|
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
||||||
|
relative
|
||||||
|
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
||||||
|
>
|
||||||
|
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
|
||||||
|
</div>
|
||||||
|
<div className='col-span-2'>{projectFlock.location?.name}</div>
|
||||||
|
|
||||||
|
{/* Kandang */}
|
||||||
|
<div
|
||||||
|
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
||||||
|
relative
|
||||||
|
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
||||||
|
>
|
||||||
|
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
|
||||||
|
</div>
|
||||||
|
<div className='col-span-2'>
|
||||||
|
{projectFlockKandang.kandang?.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jumlah DOC */}
|
||||||
|
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
||||||
|
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah
|
||||||
|
DOC
|
||||||
|
</div>
|
||||||
|
<div className='col-span-2'>
|
||||||
|
{formatNumber(
|
||||||
|
projectFlockKandang.chickins?.reduce(
|
||||||
|
(total, chickin) => total + chickin.usage_qty,
|
||||||
|
0
|
||||||
|
) ?? 0
|
||||||
|
)}{' '}
|
||||||
|
Ekor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Information Grid */}
|
{/* Table Biaya */}
|
||||||
<div className='grid grid-cols-3 gap-4'>
|
<div className='divider'></div>
|
||||||
{/* Area */}
|
<div className='px-4 pb-4'>
|
||||||
<div
|
<h2 className='text-2xl font-semibold'>Biaya</h2>
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
<Table<ClosingExpense>
|
||||||
relative
|
data={
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
isResponseSuccess(closingData) ? closingData.data?.expenses : []
|
||||||
>
|
}
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
|
columns={[
|
||||||
</div>
|
{
|
||||||
<div className='col-span-2'>{projectFlock.area?.name}</div>
|
header: 'PO Number',
|
||||||
|
accessorKey: 'po_number',
|
||||||
{/* Lokasi */}
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock.location?.name}</div>
|
|
||||||
|
|
||||||
{/* Kandang */}
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlockKandang.kandang?.name}</div>
|
|
||||||
|
|
||||||
{/* Jumlah DOC */}
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah DOC
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
{formatNumber(
|
|
||||||
projectFlockKandang.chickins?.reduce(
|
|
||||||
(total, chickin) => total + chickin.usage_qty,
|
|
||||||
0
|
|
||||||
) ?? 0
|
|
||||||
)}{' '}
|
|
||||||
Ekor
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Table Biaya */}
|
|
||||||
<div className='divider'></div>
|
|
||||||
<div className='px-4 pb-4'>
|
|
||||||
<h2 className='text-2xl font-semibold'>Biaya</h2>
|
|
||||||
<Table<ClosingExpense>
|
|
||||||
data={
|
|
||||||
isResponseSuccess(closingData) ? closingData.data?.expenses : []
|
|
||||||
}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: 'PO Number',
|
|
||||||
accessorKey: 'po_number',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Total',
|
|
||||||
accessorKey: 'total',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Status',
|
|
||||||
accessorKey: 'status',
|
|
||||||
cell(props) {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg',
|
|
||||||
}}
|
|
||||||
variant='soft'
|
|
||||||
color={
|
|
||||||
props.row.original.step < 5
|
|
||||||
? props.row.original.step == 1
|
|
||||||
? 'neutral'
|
|
||||||
: 'success'
|
|
||||||
: 'error'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{formatTitleCase(props.row.original.step_name)}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
]}
|
header: 'Total',
|
||||||
className={{
|
accessorKey: 'total',
|
||||||
containerClassName: cn('my-4'),
|
},
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
|
{
|
||||||
tableClassName: 'font-inter w-full table-sm min-h-full!',
|
header: 'Status',
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
accessorKey: 'status',
|
||||||
headerColumnClassName:
|
cell(props) {
|
||||||
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
return (
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
<Badge
|
||||||
bodyColumnClassName:
|
className={{
|
||||||
'px-3 py-3 last:flex last:flex-row last:justify-end',
|
badge: 'rounded-lg',
|
||||||
paginationClassName: 'hidden',
|
}}
|
||||||
}}
|
variant='soft'
|
||||||
/>
|
color={
|
||||||
{/* {errorExpense && (
|
props.row.original.step < 5
|
||||||
|
? props.row.original.step == 1
|
||||||
|
? 'neutral'
|
||||||
|
: 'success'
|
||||||
|
: 'error'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formatTitleCase(props.row.original.step_name)}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
className={{
|
||||||
|
containerClassName: cn('my-4'),
|
||||||
|
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
|
||||||
|
tableClassName: 'font-inter w-full table-sm min-h-full!',
|
||||||
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
|
bodyColumnClassName:
|
||||||
|
'px-3 py-3 last:flex last:flex-row last:justify-end',
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* {errorExpense && (
|
||||||
<div className='text-center text-error text-sm'>
|
<div className='text-center text-error text-sm'>
|
||||||
*Pastikan semua biaya sudah selesai sebelum melakukan closing.
|
*Pastikan semua biaya sudah selesai sebelum melakukan closing.
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table Persediaan Gudang */}
|
{/* Table Persediaan Gudang */}
|
||||||
<div className='divider'></div>
|
<div className='divider'></div>
|
||||||
<div className='px-4 pb-4'>
|
<div className='px-4 pb-4'>
|
||||||
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
||||||
<Table<StockItem>
|
<Table<StockItem>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(closingData)
|
isResponseSuccess(closingData)
|
||||||
? closingData.data?.stock_remaining
|
? closingData.data?.stock_remaining
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Product',
|
header: 'Product',
|
||||||
accessorKey: 'product_name',
|
accessorKey: 'product_name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
accessorKey: 'product_category',
|
accessorKey: 'product_category',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Quantity',
|
header: 'Quantity',
|
||||||
accessorKey: 'quantity',
|
accessorKey: 'quantity',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'UOM',
|
header: 'UOM',
|
||||||
accessorKey: 'uom',
|
accessorKey: 'uom',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn('my-4'),
|
containerClassName: cn('my-4'),
|
||||||
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
|
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
|
||||||
tableClassName: 'font-inter w-full table-sm min-h-full!',
|
tableClassName: 'font-inter w-full table-sm min-h-full!',
|
||||||
headerRowClassName: 'border-b border-b-gray-200',
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
headerColumnClassName:
|
headerColumnClassName:
|
||||||
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||||
bodyRowClassName: 'border-b border-b-gray-200',
|
bodyRowClassName: 'border-b border-b-gray-200',
|
||||||
bodyColumnClassName:
|
bodyColumnClassName:
|
||||||
'px-3 py-3 last:flex last:flex-row last:justify-end',
|
'px-3 py-3 last:flex last:flex-row last:justify-end',
|
||||||
paginationClassName: 'hidden',
|
paginationClassName: 'hidden',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* {errorStock && (
|
{/* {errorStock && (
|
||||||
<div className='text-center text-error text-sm'>
|
<div className='text-center text-error text-sm'>
|
||||||
*Masih ada sisa stock yang belum dihabiskan.
|
*Masih ada sisa stock yang belum dihabiskan.
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='p-4 mt-6'>
|
<div className='p-4 mt-6'>
|
||||||
<RequirePermission permissions='lti.production.project_flock_kandangs.closing'>
|
<RequirePermission permissions='lti.production.project_flock_kandangs.closing'>
|
||||||
<Button
|
<Button
|
||||||
className='w-full'
|
className='w-full'
|
||||||
color='error'
|
color='error'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
disabled={!isCanCloseValid}
|
disabled={!isCanCloseValid}
|
||||||
onClick={() => closeModal.openModal()}
|
onClick={() => closeModal.openModal()}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:checkbox-marked-circle-outline' />{' '}
|
<Icon icon='mdi:checkbox-marked-circle-outline' />{' '}
|
||||||
{isCanClose ? 'Close' : 'Unclose'}
|
{isCanClose ? 'Close' : 'Unclose'}
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={closeModal.ref}
|
ref={closeModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={
|
text={
|
||||||
isCanClose
|
isCanClose
|
||||||
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
|
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
|
||||||
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
|
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
|
||||||
}
|
}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: 'Ya',
|
text: 'Ya',
|
||||||
color: 'error',
|
color: 'error',
|
||||||
isLoading: isClosingLoading,
|
isLoading: isClosingLoading,
|
||||||
onClick: confirmationModalCloseClickHandler,
|
onClick: confirmationModalCloseClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,7 @@ import Card from '@/components/Card';
|
|||||||
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
|
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
|
||||||
import Tooltip from '@/components/Tooltip';
|
import Tooltip from '@/components/Tooltip';
|
||||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
import {
|
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
formatCurrency,
|
|
||||||
formatDate,
|
|
||||||
formatNumber,
|
|
||||||
formatTitleCase,
|
|
||||||
} from '@/lib/helper';
|
|
||||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@@ -20,16 +15,15 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
|||||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import ApprovalSteps, {
|
|
||||||
useApprovalSteps,
|
|
||||||
} from '@/components/pages/ApprovalSteps';
|
|
||||||
import {
|
|
||||||
PROJECT_FLOCK_APPROVAL_LINE,
|
|
||||||
PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
|
|
||||||
} from '@/config/approval-line';
|
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { ProjectFlockKandangApi } from '@/services/api/production';
|
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import ApprovalStepsV2 from '@/components/helper/ApprovalStepsV2';
|
||||||
|
import { APPROVAL_WORKFLOWS } from '@/config/constant';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { ProjectFlockFormConfirmationTableType } from '../form/ProjectFlockForm';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
|
import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang';
|
||||||
|
|
||||||
const ProjectFlockDetail = ({
|
const ProjectFlockDetail = ({
|
||||||
projectFlock,
|
projectFlock,
|
||||||
@@ -40,7 +34,7 @@ const ProjectFlockDetail = ({
|
|||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [openBudgets, setOpenBudget] = useState(false);
|
const [openBudgets, setOpenBudget] = useState(false);
|
||||||
const [selectedKandangId, setSelectedKamdangId] = useState<string | null>(
|
const [selectedKandangId, setSelectedKandangId] = useState<string | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -61,30 +55,94 @@ const ProjectFlockDetail = ({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { data: projectFlockApprovalResponse } = useSWR(
|
||||||
approvals,
|
projectFlock.id ? ['approval-project-flock', projectFlock.id] : undefined,
|
||||||
isLoading: approvalsLoading,
|
([, id]) => ProjectFlockApi.getApprovalLineHistory(Number(id))
|
||||||
refresh: refreshApprovals,
|
);
|
||||||
} = useApprovalSteps({
|
|
||||||
latestApproval: projectFlock?.approval,
|
|
||||||
approvalLines: PROJECT_FLOCK_APPROVAL_LINE,
|
|
||||||
moduleName: 'PROJECT_FLOCKS',
|
|
||||||
moduleId: projectFlock?.id?.toString() ?? '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { approvals: kandangApprovals, isLoading: kandangApprovalsLoading } =
|
const projectFlockApproval = isResponseSuccess(projectFlockApprovalResponse)
|
||||||
useApprovalSteps({
|
? projectFlockApprovalResponse.data
|
||||||
latestApproval:
|
: undefined;
|
||||||
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
|
||||||
? projectFlockKandang?.data?.approval
|
const { data: projectFlockKandangApprovalResponse } = useSWR(
|
||||||
: undefined,
|
selectedKandang?.project_flock_kandang_id
|
||||||
approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
|
? [
|
||||||
moduleName: 'PROJECT_FLOCK_KANDANGS',
|
'approval-project-flock-kandang',
|
||||||
moduleId:
|
selectedKandang?.project_flock_kandang_id,
|
||||||
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
]
|
||||||
? projectFlockKandang?.data?.id?.toString()
|
: undefined,
|
||||||
: '',
|
([, id]) => ProjectFlockKandangApi.getApprovalLineHistory(Number(id))
|
||||||
});
|
);
|
||||||
|
|
||||||
|
const projectFlockKandangApproval = isResponseSuccess(
|
||||||
|
projectFlockKandangApprovalResponse
|
||||||
|
)
|
||||||
|
? projectFlockKandangApprovalResponse.data
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const confirmationTableColumns: ColumnDef<ProjectFlockFormConfirmationTableType>[] =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
cell: ({ row }) => row.original.value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const confirmationTableData: ProjectFlockFormConfirmationTableType[] = [
|
||||||
|
{
|
||||||
|
label: 'Tanggal',
|
||||||
|
value: formatDate(projectFlock.created_at, 'DD MMMM YYYY'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Area',
|
||||||
|
value: projectFlock.area.name ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lokasi',
|
||||||
|
value: projectFlock.location.name ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Flock',
|
||||||
|
value: projectFlock.flock_name ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Kategori',
|
||||||
|
value: projectFlock.category ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Standar Produksi',
|
||||||
|
value: projectFlock.production_standard.name ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Periode',
|
||||||
|
value: projectFlock.period ?? '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
@@ -104,12 +162,14 @@ const ProjectFlockDetail = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='h-full w-full flex flex-col gap-4'>
|
<div className='h-full w-full flex flex-col overflow-x-hidden overflow-y-auto'>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<DrawerHeader
|
<DrawerHeader
|
||||||
leftIcon='mdi:close'
|
leftIcon='heroicons:chevron-left'
|
||||||
leftIconHref='/production/project-flock'
|
leftIconHref='/production/project-flock'
|
||||||
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`}
|
leftIconClassName='hover:text-gray-400'
|
||||||
|
subtitle='Detail Flock'
|
||||||
|
className='sticky top-0 z-10 bg-base-100'
|
||||||
>
|
>
|
||||||
<RequirePermission permissions='lti.production.project_flocks.update'>
|
<RequirePermission permissions='lti.production.project_flocks.update'>
|
||||||
<Link
|
<Link
|
||||||
@@ -118,7 +178,7 @@ const ProjectFlockDetail = ({
|
|||||||
>
|
>
|
||||||
<Tooltip content='Edit' position='bottom'>
|
<Tooltip content='Edit' position='bottom'>
|
||||||
<Button variant='link' className='p-0 text-neutral'>
|
<Button variant='link' className='p-0 text-neutral'>
|
||||||
<Icon icon='mdi:square-edit-outline' width={20} height={20} />
|
<Icon icon='heroicons:pencil-square' width={20} height={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -132,332 +192,224 @@ const ProjectFlockDetail = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip content='Hapus' position='bottom'>
|
<Tooltip content='Hapus' position='bottom'>
|
||||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
|
|
||||||
{/* Informasi Umum */}
|
<ApprovalStepsV2
|
||||||
<div className='border-t-1 border-gray-300'>
|
approvals={projectFlockApproval}
|
||||||
<div className='p-4 flex flex-col gap-4'>
|
steps={APPROVAL_WORKFLOWS.PROJECT_FLOCKS}
|
||||||
<h2 className='text-2xl font-semibold'>Informasi Umum</h2>
|
/>
|
||||||
{/* Status Approval */}
|
|
||||||
{approvals && !approvalsLoading && (
|
|
||||||
<div className='text-sm my-3'>
|
|
||||||
<ApprovalSteps approvals={approvals} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Badge Row */}
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={
|
|
||||||
projectFlock.approval?.step_number == 1
|
|
||||||
? 'neutral'
|
|
||||||
: projectFlock.approval?.step_number == 2
|
|
||||||
? 'primary'
|
|
||||||
: projectFlock.approval?.step_number == 3
|
|
||||||
? 'success'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={
|
|
||||||
projectFlock.approval?.step_number == 1
|
|
||||||
? 'neutral'
|
|
||||||
: projectFlock.approval?.step_number == 2
|
|
||||||
? 'primary'
|
|
||||||
: projectFlock.approval?.step_number == 3
|
|
||||||
? 'success'
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>{' '}
|
|
||||||
{projectFlock.approval?.step_name}
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<Badge
|
|
||||||
color='neutral'
|
|
||||||
variant='soft'
|
|
||||||
className={{ badge: 'rounded-lg px-2' }}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:bookmark' width={12} height={12} />
|
|
||||||
{` ${formatTitleCase(projectFlock.category ?? '')}`}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{/* Information Grid */}
|
|
||||||
<div className='grid grid-cols-3 gap-4'>
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon='mdi:account' /> Submitted
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='neutral'
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon='mdi:account-circle' width={14} height={14} />{' '}
|
|
||||||
{projectFlock.created_user?.name}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* BARIS 1 */}
|
<div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
|
||||||
<div
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
Informasi Umum
|
||||||
relative
|
</h4>
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock?.area?.name}</div>
|
|
||||||
|
|
||||||
{/* BARIS 2 */}
|
<Table<ProjectFlockFormConfirmationTableType>
|
||||||
<div
|
columns={confirmationTableColumns}
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
data={confirmationTableData}
|
||||||
relative
|
withPagination={false}
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
pageSize={10000}
|
||||||
>
|
expanded={true}
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
|
getSubRows={(row) => row.subRows}
|
||||||
</div>
|
className={{
|
||||||
<div className='col-span-2'>{projectFlock?.location?.name}</div>
|
headerRowClassName: 'border-b border-base-content/10',
|
||||||
|
bodyRowClassName: 'border-none',
|
||||||
<div
|
bodySubRowClassName: () => 'border-none',
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
bodySubRowColumnClassName: () => 'first:p-0',
|
||||||
relative
|
}}
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
/>
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> FCR
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>{projectFlock?.fcr?.name}</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
|
|
||||||
relative
|
|
||||||
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
|
|
||||||
>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
|
|
||||||
Standard
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
{projectFlock?.production_standard?.name ?? '-'}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */}
|
|
||||||
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
|
|
||||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
|
|
||||||
Kategori
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
{formatTitleCase(projectFlock.category ?? '')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Kandang Aktif */}
|
<div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
|
||||||
<div className='border-t-1 border-gray-300'>
|
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||||
<div className='p-4 flex flex-col gap-4'>
|
Kandang Aktif
|
||||||
<h2 className='text-2xl font-semibold'>Kandang Aktif</h2>
|
</h4>
|
||||||
{kandangApprovals && !kandangApprovalsLoading && (
|
|
||||||
<ApprovalSteps approvals={kandangApprovals} />
|
|
||||||
)}
|
|
||||||
{/* Badge Row */}
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color={'primary'}
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg px-2',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='mdi:circle'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
color={'primary'}
|
|
||||||
/>{' '}
|
|
||||||
Kandang Aktif ({projectFlock.kandangs?.length})
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
|
||||||
<Badge
|
|
||||||
color='neutral'
|
|
||||||
variant='soft'
|
|
||||||
className={{ badge: 'rounded-lg px-2 cursor-pointer' }}
|
|
||||||
onClick={() => {
|
|
||||||
setOpenBudget(!openBudgets);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{` ${formatCurrency(
|
|
||||||
(projectFlock.project_budgets ?? []).reduce(
|
|
||||||
(acc, curr) => acc + curr.price * curr.qty,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)}`}
|
|
||||||
<Icon
|
|
||||||
icon={`mdi:${openBudgets ? 'eye' : 'eye-off'}`}
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
/>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card List Project Budgets */}
|
<div className='flex flex-row gap-2'>
|
||||||
{openBudgets &&
|
<StatusBadge
|
||||||
(projectFlock.project_budgets ?? []).map((budget) => (
|
color='info'
|
||||||
<Card
|
text={`Kandang Aktif (${projectFlock.kandangs?.length})`}
|
||||||
key={budget.id}
|
className={{ badge: 'w-fit text-nowrap' }}
|
||||||
variant='bordered'
|
/>
|
||||||
className={{
|
|
||||||
wrapper: 'w-full',
|
|
||||||
body: 'p-3',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-6'>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
|
||||||
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
|
|
||||||
<span>Jenis Produk</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-end text-gray-500'>
|
|
||||||
{budget?.nonstock?.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
|
||||||
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
|
|
||||||
<span>Nama Satuan</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-end text-gray-500'>
|
|
||||||
{budget?.nonstock?.uom?.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
|
||||||
<Icon
|
|
||||||
icon={'mdi:file-multiple'}
|
|
||||||
width={14}
|
|
||||||
height={14}
|
|
||||||
/>{' '}
|
|
||||||
<span>Jumlah Pembelian</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-end text-gray-500'>
|
|
||||||
{formatNumber(budget.qty)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
|
||||||
<Icon icon={'mdi:file'} width={14} height={14} />{' '}
|
|
||||||
<span>Harga Satuan</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-end text-gray-500'>
|
|
||||||
{formatCurrency(budget.price)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-row justify-between items-center'>
|
|
||||||
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
|
||||||
<Icon icon={'mdi:calculator'} width={14} height={14} />{' '}
|
|
||||||
<span>Total Harga</span>
|
|
||||||
</div>
|
|
||||||
<div className='text-end text-gray-500'>
|
|
||||||
{formatCurrency(budget.price * budget.qty)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Card Kandangs */}
|
<StatusBadge
|
||||||
<Card
|
color='neutral'
|
||||||
variant='bordered'
|
onClick={() => {
|
||||||
className={{
|
setOpenBudget(!openBudgets);
|
||||||
wrapper: 'w-full',
|
|
||||||
body: 'p-3',
|
|
||||||
}}
|
}}
|
||||||
>
|
text={
|
||||||
<RadioGroup
|
<>
|
||||||
name='gender'
|
{` ${formatCurrency(
|
||||||
|
(projectFlock.project_budgets ?? []).reduce(
|
||||||
|
(acc, curr) => acc + curr.price * curr.qty,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}`}
|
||||||
|
<Icon
|
||||||
|
icon={`mdi:${openBudgets ? 'eye' : 'eye-off'}`}
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
className={{ badge: 'w-fit text-nowrap cursor-pointer' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card List Project Budgets */}
|
||||||
|
{openBudgets &&
|
||||||
|
(projectFlock.project_budgets ?? []).map((budget) => (
|
||||||
|
<Card
|
||||||
|
key={budget.id}
|
||||||
|
variant='bordered'
|
||||||
className={{
|
className={{
|
||||||
radioWrapper: 'grid grid-cols-1 gap-6',
|
wrapper: 'w-full rounded-lg',
|
||||||
|
body: 'p-3',
|
||||||
}}
|
}}
|
||||||
onChange={(e) => setSelectedKamdangId(e.target.value)}
|
|
||||||
value={selectedKandangId?.toString()}
|
|
||||||
size='md'
|
|
||||||
color='neutral'
|
|
||||||
disabled={projectFlock?.approval?.step_number == 1}
|
|
||||||
>
|
>
|
||||||
{projectFlock.kandangs?.map((kandang) => (
|
<div className='flex flex-col gap-6'>
|
||||||
<div
|
<div className='flex flex-row justify-between items-center'>
|
||||||
key={kandang.id}
|
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
||||||
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
|
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
|
||||||
onClick={() =>
|
<span>Jenis Produk</span>
|
||||||
projectFlock?.approval?.step_number > 1 &&
|
</div>
|
||||||
setSelectedKamdangId(kandang?.id?.toString())
|
<div className='text-end text-gray-500'>
|
||||||
}
|
{budget?.nonstock?.name}
|
||||||
>
|
|
||||||
<RadioGroupItem
|
|
||||||
value={kandang?.id?.toString()}
|
|
||||||
label={kandang?.name}
|
|
||||||
disabled={projectFlock?.approval?.step_number == 1}
|
|
||||||
/>
|
|
||||||
<div className='text-end'>
|
|
||||||
<Badge
|
|
||||||
className={{
|
|
||||||
badge: 'rounded-lg',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Kapasitas {kandang?.capacity} Ekor
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className='flex flex-row justify-between items-center'>
|
||||||
</RadioGroup>
|
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
||||||
</Card>
|
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
|
||||||
<div className='grid grid-cols-4 gap-3'>
|
<span>Nama Satuan</span>
|
||||||
<RequirePermission permissions='lti.production.chickins.detail'>
|
</div>
|
||||||
<Link
|
<div className='text-end text-gray-500'>
|
||||||
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
|
{budget?.nonstock?.uom?.name}
|
||||||
className='m-0 p-0'
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row justify-between items-center'>
|
||||||
|
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
||||||
|
<Icon icon={'mdi:file-multiple'} width={14} height={14} />{' '}
|
||||||
|
<span>Jumlah Pembelian</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-end text-gray-500'>
|
||||||
|
{formatNumber(budget.qty)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row justify-between items-center'>
|
||||||
|
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
||||||
|
<Icon icon={'mdi:file'} width={14} height={14} />{' '}
|
||||||
|
<span>Harga Satuan</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-end text-gray-500'>
|
||||||
|
{formatCurrency(budget.price)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row justify-between items-center'>
|
||||||
|
<div className='flex flex-row gap-2 items-center text-gray-400'>
|
||||||
|
<Icon icon={'mdi:calculator'} width={14} height={14} />{' '}
|
||||||
|
<span>Total Harga</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-end text-gray-500'>
|
||||||
|
{formatCurrency(budget.price * budget.qty)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Card Kandangs */}
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full rounded-lg',
|
||||||
|
body: 'p-3',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
name='gender'
|
||||||
|
className={{
|
||||||
|
radioWrapper: 'grid grid-cols-1 gap-6',
|
||||||
|
}}
|
||||||
|
onChange={(e) => setSelectedKandangId(e.target.value)}
|
||||||
|
value={selectedKandangId?.toString()}
|
||||||
|
size='md'
|
||||||
|
color='neutral'
|
||||||
|
disabled={projectFlock?.approval?.step_number == 1}
|
||||||
|
>
|
||||||
|
{projectFlock.kandangs?.map((kandang) => (
|
||||||
|
<div
|
||||||
|
key={kandang.id}
|
||||||
|
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
|
||||||
|
onClick={() =>
|
||||||
|
projectFlock?.approval?.step_number > 1 &&
|
||||||
|
setSelectedKandangId(kandang?.id?.toString())
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<RadioGroupItem
|
||||||
className='w-full px-2 py-1 text-sm'
|
value={kandang?.id?.toString()}
|
||||||
variant='outline'
|
label={kandang?.name}
|
||||||
color='success'
|
disabled={projectFlock?.approval?.step_number == 1}
|
||||||
disabled={
|
/>
|
||||||
!selectedKandangId ||
|
<div className='text-end'>
|
||||||
projectFlock?.approval?.step_number == 1
|
<Badge
|
||||||
}
|
className={{
|
||||||
>
|
badge: 'rounded-lg',
|
||||||
Chickin <Icon icon='mdi:checkbox-marked-outline' />
|
}}
|
||||||
</Button>
|
>
|
||||||
</Link>
|
Kapasitas {kandang?.capacity} Ekor
|
||||||
</RequirePermission>
|
</Badge>
|
||||||
<RequirePermission permissions='lti.production.project_flock_kandangs.closing.detail'>
|
</div>
|
||||||
<Link
|
</div>
|
||||||
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
))}
|
||||||
className='m-0 p-0'
|
</RadioGroup>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<ApprovalStepsV2
|
||||||
|
approvals={projectFlockKandangApproval}
|
||||||
|
steps={APPROVAL_WORKFLOWS.PROJECT_FLOCK_KANDANGS}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-4 gap-3'>
|
||||||
|
<RequirePermission permissions='lti.production.chickins.detail'>
|
||||||
|
<Link
|
||||||
|
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
|
||||||
|
className='m-0 p-0'
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className='w-full px-2 py-1 text-sm'
|
||||||
|
variant='outline'
|
||||||
|
color='success'
|
||||||
|
disabled={
|
||||||
|
!selectedKandangId ||
|
||||||
|
projectFlock?.approval?.step_number == 1
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Button
|
Chickin <Icon icon='mdi:checkbox-marked-outline' />
|
||||||
className='w-full px-2 py-1 text-sm'
|
</Button>
|
||||||
variant='outline'
|
</Link>
|
||||||
color='error'
|
</RequirePermission>
|
||||||
disabled={
|
<RequirePermission permissions='lti.production.project_flock_kandangs.closing.detail'>
|
||||||
!selectedKandangId ||
|
<Link
|
||||||
projectFlock?.approval?.step_number == 1
|
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
||||||
}
|
className='m-0 p-0'
|
||||||
>
|
>
|
||||||
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
|
<Button
|
||||||
</Button>
|
className='w-full px-2 py-1 text-sm'
|
||||||
</Link>
|
variant='outline'
|
||||||
</RequirePermission>
|
color='error'
|
||||||
</div>
|
disabled={
|
||||||
|
!selectedKandangId ||
|
||||||
|
projectFlock?.approval?.step_number == 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</RequirePermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
import PillBadge from '@/components/PillBadge';
|
import PillBadge from '@/components/PillBadge';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
@@ -32,6 +33,14 @@ const ProjectFlockKandangTable = ({
|
|||||||
initialValues?: ProjectFlock;
|
initialValues?: ProjectFlock;
|
||||||
formType: 'add' | 'edit' | 'detail';
|
formType: 'add' | 'edit' | 'detail';
|
||||||
}) => {
|
}) => {
|
||||||
|
const availableKandang = listKandang.filter(
|
||||||
|
(kandang) => kandang.status == 'NON_ACTIVE'
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const unavailableKandang = listKandang.filter(
|
||||||
|
(kandang) => kandang.status != 'NON_ACTIVE'
|
||||||
|
).length;
|
||||||
|
|
||||||
// Fungsi untuk menangani perubahan checkbox
|
// Fungsi untuk menangani perubahan checkbox
|
||||||
const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => {
|
const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => {
|
||||||
// Hanya izinkan perubahan jika tidak dalam mode 'detail'
|
// Hanya izinkan perubahan jika tidak dalam mode 'detail'
|
||||||
@@ -57,48 +66,30 @@ const ProjectFlockKandangTable = ({
|
|||||||
{listKandang.length > 0 ? (
|
{listKandang.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{/* ... Bagian Badge Status ... */}
|
{/* ... Bagian Badge Status ... */}
|
||||||
<div className='flex flex-row mb-4'>
|
<div className='w-fit flex flex-row items-stretch gap-3 mb-3'>
|
||||||
<Badge
|
<StatusBadge
|
||||||
variant='soft'
|
color='info'
|
||||||
color='primary'
|
text={`Tersedia (${availableKandang})`}
|
||||||
className={{
|
className={{ badge: 'text-nowrap' }}
|
||||||
badge: 'rounded-lg px-2',
|
/>
|
||||||
}}
|
|
||||||
>
|
<div className='w-px border-none bg-base-content/10' />
|
||||||
<Icon icon='mdi:circle' width={12} height={12} />
|
|
||||||
Tersedia (
|
<StatusBadge
|
||||||
{
|
|
||||||
listKandang.filter((kandang) => kandang.status == 'NON_ACTIVE')
|
|
||||||
.length
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</Badge>
|
|
||||||
<div className='divider divider-horizontal mx-1'></div>
|
|
||||||
<Badge
|
|
||||||
variant='soft'
|
|
||||||
color='neutral'
|
color='neutral'
|
||||||
className={{
|
text={`Tidak Tersedia (${unavailableKandang})`}
|
||||||
badge: 'rounded-lg px-2',
|
className={{ badge: 'text-nowrap' }}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<Icon icon='mdi:circle' width={12} height={12} />
|
|
||||||
Tidak Tersedia (
|
|
||||||
{
|
|
||||||
listKandang.filter((kandang) => kandang.status != 'NON_ACTIVE')
|
|
||||||
.length
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
{/* --- */}
|
{/* --- */}
|
||||||
<Card
|
<Card
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
className={{
|
className={{
|
||||||
wrapper: 'w-full rounded-lg',
|
wrapper: 'w-full rounded-xl border border-base-content/5',
|
||||||
body: 'p-4',
|
body: 'p-0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-4 w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
{listKandang.map((kandang, index) => {
|
{listKandang.map((kandang, index) => {
|
||||||
const kandangIdString =
|
const kandangIdString =
|
||||||
kandang.id?.toString() ?? `temp-${index}`;
|
kandang.id?.toString() ?? `temp-${index}`;
|
||||||
@@ -112,28 +103,36 @@ const ProjectFlockKandangTable = ({
|
|||||||
formType == 'detail' || kandang.status != 'NON_ACTIVE';
|
formType == 'detail' || kandang.status != 'NON_ACTIVE';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className='flex flex-row justify-between'>
|
<div
|
||||||
|
key={index}
|
||||||
|
className='w-full p-3 flex flex-row items-center justify-between'
|
||||||
|
>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
|
name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
|
||||||
label={kandang.name}
|
label={kandang.name}
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
|
size='md'
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleCheckboxChange(kandang, e.currentTarget.checked)
|
handleCheckboxChange(kandang, e.currentTarget.checked)
|
||||||
}
|
}
|
||||||
/>
|
classNames={{
|
||||||
<Badge
|
inputWrapper: cn('gap-3 text-base-content/50', {
|
||||||
variant='soft'
|
'text-base-content/20': isDisabled,
|
||||||
color={
|
}),
|
||||||
kandang.status == 'NON_ACTIVE' ? 'primary' : 'neutral'
|
label: 'cursor-pointer',
|
||||||
}
|
checkbox: cn({
|
||||||
className={{
|
'bg-base-200 border border-base-content/10 opacity-100':
|
||||||
badge: 'rounded-lg px-2',
|
isDisabled,
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon icon='mdi:circle' width={12} height={12} />
|
|
||||||
{kandang.status != 'NON_ACTIVE' && 'Tidak'} Tersedia
|
<StatusBadge
|
||||||
</Badge>
|
color={!isDisabled ? 'info' : 'neutral'}
|
||||||
|
text={!isDisabled ? 'Tersedia' : 'Tidak Tersedia'}
|
||||||
|
className={{ badge: 'w-fit' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ interface CustomerPaymentExportPDFParams {
|
|||||||
// sales?: string;
|
// sales?: string;
|
||||||
start_date?: string;
|
start_date?: string;
|
||||||
end_date?: string;
|
end_date?: string;
|
||||||
// TODO: Uncomment when BE is ready
|
filter_by?: string;
|
||||||
// filter_by?: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import useSWR from 'swr';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import SelectInput, { useSelect } from '@/components/input/SelectInput';
|
import { useSelect } from '@/components/input/SelectInput';
|
||||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
import { CustomerApi } from '@/services/api/master-data';
|
import { CustomerApi } from '@/services/api/master-data';
|
||||||
import { FinanceApi } from '@/services/api/report/finance-report';
|
import { FinanceApi } from '@/services/api/report/finance-report';
|
||||||
import { UserApi } from '@/services/api/user';
|
// import { UserApi } from '@/services/api/user';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper';
|
||||||
import {
|
import {
|
||||||
CustomerPaymentReport,
|
CustomerPaymentReport,
|
||||||
CustomerPaymentRow,
|
|
||||||
CustomerPaymentSummary,
|
CustomerPaymentSummary,
|
||||||
} from '@/types/api/report/customer-payment';
|
} from '@/types/api/report/customer-payment';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -48,38 +48,58 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
|
||||||
// ===== FILTER STATE =====
|
// ===== FILTER STATE =====
|
||||||
|
const [appliedFilterCustomer, setAppliedFilterCustomer] = useState<
|
||||||
|
typeof customerOptions
|
||||||
|
>([]);
|
||||||
|
// TODO: Uncomment when BE is ready
|
||||||
|
// const [appliedFilterSales, setAppliedFilterSales] = useState<
|
||||||
|
// typeof salesOptions
|
||||||
|
// >([]);
|
||||||
|
const [appliedFilterByType, setAppliedFilterByType] = useState<
|
||||||
|
(typeof dataTypeOptions)[0] | null
|
||||||
|
>(null);
|
||||||
|
const [appliedFilterStartDate, setAppliedFilterStartDate] = useState('');
|
||||||
|
const [appliedFilterEndDate, setAppliedFilterEndDate] = useState('');
|
||||||
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
const [filterCustomer, setFilterCustomer] = useState<typeof customerOptions>(
|
const [filterCustomer, setFilterCustomer] = useState<typeof customerOptions>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
|
// const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
|
||||||
const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
|
|
||||||
const [filterStartDate, setFilterStartDate] = useState('');
|
const [filterStartDate, setFilterStartDate] = useState('');
|
||||||
const [filterEndDate, setFilterEndDate] = useState('');
|
const [filterEndDate, setFilterEndDate] = useState('');
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
|
const dataTypeOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: 'trans_date', label: 'Tanggal Jual/Bayar' },
|
||||||
|
{ value: 'realization_date', label: 'Tanggal Realisasi' },
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [filterByType, setFilterByType] = useState<
|
||||||
|
(typeof dataTypeOptions)[0] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: customerOptions,
|
options: customerOptions,
|
||||||
setInputValue: setCustomerInputValue,
|
setInputValue: setCustomerInputValue,
|
||||||
isLoadingOptions: isLoadingCustomers,
|
isLoadingOptions: isLoadingCustomers,
|
||||||
loadMore: loadMoreCustomers,
|
loadMore: loadMoreCustomers,
|
||||||
hasMore: hasMoreCustomers,
|
|
||||||
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
const {
|
// const {
|
||||||
options: salesOptions,
|
// options: salesOptions,
|
||||||
setInputValue: setSalesInputValue,
|
// setInputValue: setSalesInputValue,
|
||||||
isLoadingOptions: isLoadingSales,
|
// isLoadingOptions: isLoadingSales,
|
||||||
loadMore: loadMoreSales,
|
// loadMore: loadMoreSales,
|
||||||
hasMore: hasMoreSales,
|
// hasMore: hasMoreSales,
|
||||||
} = useSelect(UserApi.basePath, 'id', 'name', 'search');
|
// } = useSelect(UserApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const dataTypeOptions = useMemo(
|
|
||||||
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getPaymentStatusColor = (notes: string) => {
|
const getPaymentStatusColor = (notes: string) => {
|
||||||
const normalizedValue = notes.toLowerCase();
|
const normalizedValue = notes.toLowerCase();
|
||||||
@@ -119,49 +139,145 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
|
|
||||||
// ===== FILTER HANDLERS =====
|
// ===== FILTER HANDLERS =====
|
||||||
const handleFilterModalOpen = useCallback(() => {
|
const handleFilterModalOpen = useCallback(() => {
|
||||||
|
setFilterCustomer(appliedFilterCustomer);
|
||||||
|
// setFilterSales(appliedFilterSales);
|
||||||
|
setFilterByType(appliedFilterByType);
|
||||||
|
setFilterStartDate(appliedFilterStartDate);
|
||||||
|
setFilterEndDate(appliedFilterEndDate);
|
||||||
filterModal.openModal();
|
filterModal.openModal();
|
||||||
}, [filterModal]);
|
}, [
|
||||||
|
filterModal,
|
||||||
|
appliedFilterCustomer,
|
||||||
|
appliedFilterByType,
|
||||||
|
appliedFilterStartDate,
|
||||||
|
appliedFilterEndDate,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleResetFilters = useCallback(() => {
|
const handleResetFilters = useCallback(() => {
|
||||||
setIsSubmitted(false);
|
setIsSubmitted(false);
|
||||||
setFilterCustomer([]);
|
setFilterCustomer([]);
|
||||||
setFilterSales([]);
|
setFilterByType(null);
|
||||||
setFilterStartDate('');
|
setFilterStartDate('');
|
||||||
setFilterEndDate('');
|
setFilterEndDate('');
|
||||||
}, []);
|
setAppliedFilterCustomer([]);
|
||||||
|
setAppliedFilterByType(null);
|
||||||
|
setAppliedFilterStartDate('');
|
||||||
|
setAppliedFilterEndDate('');
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
}, [dateErrorShown]);
|
||||||
|
|
||||||
const handleApplyFilters = useCallback(() => {
|
const handleApplyFilters = useCallback(() => {
|
||||||
|
setAppliedFilterCustomer(filterCustomer);
|
||||||
|
setAppliedFilterByType(filterByType);
|
||||||
|
setAppliedFilterStartDate(filterStartDate);
|
||||||
|
setAppliedFilterEndDate(filterEndDate);
|
||||||
setIsSubmitted(true);
|
setIsSubmitted(true);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
}, [filterModal]);
|
}, [
|
||||||
|
filterModal,
|
||||||
|
filterCustomer,
|
||||||
|
filterByType,
|
||||||
|
filterStartDate,
|
||||||
|
filterEndDate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleStartDateChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterStartDate(value);
|
||||||
|
|
||||||
|
if (value && filterEndDate) {
|
||||||
|
const startDate = new Date(value);
|
||||||
|
const endDateObj = new Date(filterEndDate);
|
||||||
|
|
||||||
|
if (endDateObj < startDate) {
|
||||||
|
setHasDateError(true);
|
||||||
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHasDateError(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[filterEndDate, dateErrorShown]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEndDateChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFilterEndDate(value);
|
||||||
|
|
||||||
|
if (value && filterStartDate) {
|
||||||
|
const startDateObj = new Date(filterStartDate);
|
||||||
|
const endDate = new Date(value);
|
||||||
|
|
||||||
|
if (endDate < startDateObj) {
|
||||||
|
setHasDateError(true);
|
||||||
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[filterStartDate, dateErrorShown]
|
||||||
|
);
|
||||||
|
|
||||||
// ===== ACTIVE FILTERS COUNT =====
|
// ===== ACTIVE FILTERS COUNT =====
|
||||||
const activeFiltersCount = useMemo(() => {
|
const activeFiltersCount = useMemo(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
// Date filter (start_date + end_date = 1 filter)
|
// Date filter (start_date + end_date = 1 filter)
|
||||||
if (filterStartDate || filterEndDate) {
|
if (appliedFilterStartDate || appliedFilterEndDate) {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customer filter
|
// Customer filter
|
||||||
if (filterCustomer.length > 0) {
|
if (appliedFilterCustomer.length > 0) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by type filter (hanya dihitung jika ada nilai yang dipilih)
|
||||||
|
if (appliedFilterByType) {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// // Sales filter
|
// // Sales filter
|
||||||
// if (filterSales.length > 0) {
|
// if (appliedFilterSales.length > 0) {
|
||||||
// count += 1;
|
// count += 1;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}, [
|
}, [
|
||||||
filterStartDate,
|
appliedFilterStartDate,
|
||||||
filterEndDate,
|
appliedFilterEndDate,
|
||||||
filterCustomer,
|
appliedFilterCustomer,
|
||||||
// filterSales,
|
appliedFilterByType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasFilters = activeFiltersCount > 0;
|
const hasFilters = activeFiltersCount > 0;
|
||||||
@@ -172,17 +288,20 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
? () => {
|
? () => {
|
||||||
const params = {
|
const params = {
|
||||||
customer_ids:
|
customer_ids:
|
||||||
filterCustomer.length > 0
|
appliedFilterCustomer.length > 0
|
||||||
? filterCustomer.map((v) => String(v.value)).join(',')
|
? appliedFilterCustomer.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// sales_id:
|
// sales_id:
|
||||||
// filterSales.length > 0
|
// appliedFilterSales.length > 0
|
||||||
// ? filterSales.map((v) => String(v.value)).join(',')
|
// ? appliedFilterSales.map((v) => String(v.value)).join(',')
|
||||||
// : undefined,
|
// : undefined,
|
||||||
// filter_by: 'do_date' as const,
|
filter_by: appliedFilterByType?.value as
|
||||||
start_date: filterStartDate || undefined,
|
| 'trans_date'
|
||||||
end_date: filterEndDate || undefined,
|
| 'realization_date'
|
||||||
|
| undefined,
|
||||||
|
start_date: appliedFilterStartDate || undefined,
|
||||||
|
end_date: appliedFilterEndDate || undefined,
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
};
|
};
|
||||||
@@ -193,8 +312,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
([, params]) =>
|
([, params]) =>
|
||||||
FinanceApi.getCustomerPaymentReport(
|
FinanceApi.getCustomerPaymentReport(
|
||||||
params.customer_ids,
|
params.customer_ids,
|
||||||
undefined, // TODO: Change to params.sales_id when BE is ready
|
params.filter_by,
|
||||||
undefined, // TODO: Change to params.filter_by when BE is ready
|
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
params.page,
|
params.page,
|
||||||
@@ -216,24 +334,27 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
> => {
|
> => {
|
||||||
const params = {
|
const params = {
|
||||||
customer_ids:
|
customer_ids:
|
||||||
filterCustomer.length > 0
|
appliedFilterCustomer.length > 0
|
||||||
? filterCustomer.map((v) => String(v.value)).join(',')
|
? appliedFilterCustomer.map((v) => String(v.value)).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// sales_id:
|
// sales_id:
|
||||||
// filterSales.length > 0
|
// appliedFilterSales.length > 0
|
||||||
// ? filterSales.map((v) => String(v.value)).join(',')
|
// ? appliedFilterSales.map((v) => String(v.value)).join(',')
|
||||||
// : undefined,
|
// : undefined,
|
||||||
start_date: filterStartDate || undefined,
|
filter_by: appliedFilterByType?.value as
|
||||||
end_date: filterEndDate || undefined,
|
| 'trans_date'
|
||||||
|
| 'realization_date'
|
||||||
|
| undefined,
|
||||||
|
start_date: appliedFilterStartDate || undefined,
|
||||||
|
end_date: appliedFilterEndDate || undefined,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
page: 1,
|
page: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await FinanceApi.getCustomerPaymentReport(
|
const response = await FinanceApi.getCustomerPaymentReport(
|
||||||
params.customer_ids,
|
params.customer_ids,
|
||||||
undefined, // TODO: Change to params.sales_id when BE is ready
|
params.filter_by,
|
||||||
undefined, // TODO: Change to params.filter_by when BE is ready
|
|
||||||
params.start_date,
|
params.start_date,
|
||||||
params.end_date,
|
params.end_date,
|
||||||
params.page,
|
params.page,
|
||||||
@@ -243,7 +364,13 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
return isResponseSuccess(response)
|
return isResponseSuccess(response)
|
||||||
? (response.data as unknown as CustomerPaymentReport[])
|
? (response.data as unknown as CustomerPaymentReport[])
|
||||||
: null;
|
: null;
|
||||||
}, [filterCustomer, filterSales, filterStartDate, filterEndDate]);
|
}, [
|
||||||
|
appliedFilterCustomer,
|
||||||
|
// appliedFilterSales,
|
||||||
|
appliedFilterStartDate,
|
||||||
|
appliedFilterEndDate,
|
||||||
|
appliedFilterByType,
|
||||||
|
]);
|
||||||
|
|
||||||
// ===== EXPORT HANDLERS =====
|
// ===== EXPORT HANDLERS =====
|
||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
@@ -287,18 +414,20 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
data: allDataForExport,
|
data: allDataForExport,
|
||||||
params: {
|
params: {
|
||||||
customer_name:
|
customer_name:
|
||||||
filterCustomer.length > 0
|
appliedFilterCustomer.length > 0
|
||||||
? filterCustomer.map((c) => c.label).join(', ')
|
? appliedFilterCustomer.map((c) => c.label).join(', ')
|
||||||
: undefined,
|
: undefined,
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// sales:
|
// sales:
|
||||||
// filterSales.length > 0
|
// appliedFilterSales.length > 0
|
||||||
// ? filterSales.map((s) => s.label).join(', ')
|
// ? appliedFilterSales.map((s) => s.label).join(', ')
|
||||||
// : undefined,
|
// : undefined,
|
||||||
start_date: filterStartDate || undefined,
|
start_date: appliedFilterStartDate || undefined,
|
||||||
end_date: filterEndDate || undefined,
|
end_date: appliedFilterEndDate || undefined,
|
||||||
// TODO: Uncomment when BE is ready
|
filter_by: appliedFilterByType?.value as
|
||||||
// filter_by: 'do_date' as const,
|
| 'trans_date'
|
||||||
|
| 'realization_date'
|
||||||
|
| undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.success('PDF berhasil dibuat dan diunduh.');
|
toast.success('PDF berhasil dibuat dan diunduh.');
|
||||||
@@ -406,7 +535,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
footer: () => <div className='font-semibold text-gray-900'>Total</div>,
|
footer: () => <div className='font-semibold text-gray-900'>Total</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'do_date_or_payment_date',
|
id: 'trans_date',
|
||||||
header: 'Tanggal Jual/Bayar',
|
header: 'Tanggal Jual/Bayar',
|
||||||
accessorKey: 'trans_date',
|
accessorKey: 'trans_date',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
@@ -811,9 +940,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
<DateInput
|
<DateInput
|
||||||
name='start_date'
|
name='start_date'
|
||||||
value={filterStartDate}
|
value={filterStartDate}
|
||||||
onChange={(e) => {
|
onChange={handleStartDateChange}
|
||||||
setFilterStartDate(e.target.value);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isNestedModal
|
isNestedModal
|
||||||
/>
|
/>
|
||||||
@@ -822,9 +949,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
<DateInput
|
<DateInput
|
||||||
name='end_date'
|
name='end_date'
|
||||||
value={filterEndDate}
|
value={filterEndDate}
|
||||||
onChange={(e) => {
|
onChange={handleEndDateChange}
|
||||||
setFilterEndDate(e.target.value);
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isNestedModal
|
isNestedModal
|
||||||
/>
|
/>
|
||||||
@@ -864,17 +989,18 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
/>
|
/>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
{/* TODO: Uncomment when BE is ready */}
|
<SelectInputRadio
|
||||||
{/* <div>
|
label='Filter Berdasarkan'
|
||||||
<SelectInput
|
placeholder='Pilih Filter Berdasarkan'
|
||||||
label='Filter Berdasarkan'
|
options={dataTypeOptions}
|
||||||
placeholder='Pilih Filter Berdasarkan'
|
value={filterByType}
|
||||||
options={dataTypeOptions}
|
onChange={(val) => {
|
||||||
value={dataTypeOptions[0]}
|
if (val && !Array.isArray(val)) {
|
||||||
isDisabled={true}
|
setFilterByType(val);
|
||||||
className={{ wrapper: 'w-full' }}
|
}
|
||||||
/>
|
}}
|
||||||
</div> */}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
</div>
|
</div>
|
||||||
@@ -889,6 +1015,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
<Button
|
<Button
|
||||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||||
onClick={handleApplyFilters}
|
onClick={handleApplyFilters}
|
||||||
|
disabled={hasDateError}
|
||||||
>
|
>
|
||||||
Apply Filter
|
Apply Filter
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -446,6 +446,24 @@ export const APPROVAL_WORKFLOWS = {
|
|||||||
step_number: 2,
|
step_number: 2,
|
||||||
step_name: 'Aktif',
|
step_name: 'Aktif',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Selesai',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
PROJECT_FLOCK_KANDANGS: [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Selesai',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
RECORDINGS: [
|
RECORDINGS: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
ClosingProjectFlockKandangPayload,
|
ClosingProjectFlockKandangPayload,
|
||||||
CheckClosingResponse,
|
CheckClosingResponse,
|
||||||
} from '@/types/api/production/project-flock-kandang';
|
} from '@/types/api/production/project-flock-kandang';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { Approvals, BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { httpClient } from '@/services/http/client';
|
import { httpClient } from '@/services/http/client';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
@@ -181,6 +181,33 @@ export class ProjectFlockKandangService extends BaseApiService<
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getApprovalLineHistory(
|
||||||
|
id: number,
|
||||||
|
page: number = 1,
|
||||||
|
limit: number = 100
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const approvalHistoryRes = await httpClient<Approvals>('/approvals', {
|
||||||
|
query: {
|
||||||
|
module_name: 'PROJECT_FLOCK_KANDANGS',
|
||||||
|
module_id: id,
|
||||||
|
group_step_number: 'false',
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
order_by_date: 'ASC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return approvalHistoryRes;
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError<Approvals>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectFlockKandangApi = new ProjectFlockKandangService(
|
export const ProjectFlockKandangApi = new ProjectFlockKandangService(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from '@/types/api/production/project-flock';
|
} from '@/types/api/production/project-flock';
|
||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import {
|
import {
|
||||||
|
Approvals,
|
||||||
BaseApiResponse,
|
BaseApiResponse,
|
||||||
BaseGroupedApproval,
|
BaseGroupedApproval,
|
||||||
ErrorApiResponse,
|
ErrorApiResponse,
|
||||||
@@ -53,6 +54,33 @@ export class ProjectFlockService extends BaseApiService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getApprovalLineHistory(
|
||||||
|
id: number,
|
||||||
|
page: number = 1,
|
||||||
|
limit: number = 100
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const approvalHistoryRes = await httpClient<Approvals>('/approvals', {
|
||||||
|
query: {
|
||||||
|
module_name: 'PROJECT_FLOCKS',
|
||||||
|
module_id: id,
|
||||||
|
group_step_number: 'false',
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
order_by_date: 'ASC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return approvalHistoryRes;
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError<Approvals>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup for Project Flock Kandang
|
* Lookup for Project Flock Kandang
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ export class FinanceApiService extends BaseApiService<
|
|||||||
customer_ids?: string,
|
customer_ids?: string,
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// sales_id?: string,
|
// sales_id?: string,
|
||||||
// filter_by?: 'do_date',
|
filter_by?: 'trans_date' | 'realization_date',
|
||||||
sales_id?: string,
|
|
||||||
filter_by?: 'do_date' | undefined,
|
|
||||||
start_date?: string,
|
start_date?: string,
|
||||||
end_date?: string,
|
end_date?: string,
|
||||||
page?: number,
|
page?: number,
|
||||||
@@ -31,7 +29,7 @@ export class FinanceApiService extends BaseApiService<
|
|||||||
customer_ids: customer_ids,
|
customer_ids: customer_ids,
|
||||||
// TODO: Uncomment when BE is ready
|
// TODO: Uncomment when BE is ready
|
||||||
// sales_id: sales_id,
|
// sales_id: sales_id,
|
||||||
// filter_by: filter_by,
|
filter_by: filter_by,
|
||||||
start_date: start_date,
|
start_date: start_date,
|
||||||
end_date: end_date,
|
end_date: end_date,
|
||||||
page: page,
|
page: page,
|
||||||
|
|||||||
Reference in New Issue
Block a user