Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu

This commit is contained in:
rstubryan
2026-01-05 08:36:15 +07:00
6 changed files with 262 additions and 233 deletions
+21 -25
View File
@@ -2,6 +2,17 @@ stages:
- build - build
- deploy - deploy
# ==========================================================
# ✅ Global defaults
# ==========================================================
default:
tags:
- server-development-biznet
interruptible: true
# ==========================================================
# 🏗️ Build Template
# ==========================================================
.build_template: &build_template .build_template: &build_template
stage: build stage: build
image: node:20-alpine image: node:20-alpine
@@ -39,6 +50,9 @@ stages:
- out/ - out/
expire_in: 1 week expire_in: 1 week
# ==========================================================
# 🚀 Deploy Template
# ==========================================================
.deploy_template: &deploy_template .deploy_template: &deploy_template
stage: deploy stage: deploy
image: image:
@@ -82,11 +96,11 @@ stages:
if [ "$STATUS" = "success" ]; then if [ "$STATUS" = "success" ]; then
COLOR=3066993 COLOR=3066993
TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded" TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded"
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully." DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully."
else else
COLOR=15158332 COLOR=15158332
TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed" TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed"
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` encountered issues." DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues."
fi fi
jq -n \ jq -n \
@@ -114,7 +128,9 @@ stages:
curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL" curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL"
# ===== DEVELOPMENT (Branch development) ====== # ==========================================================
# ==== DEVELOPMENT (Branch development) ======
# ==========================================================
build:dev: build:dev:
<<: *build_template <<: *build_template
rules: rules:
@@ -140,7 +156,9 @@ deploy:dev:
name: development name: development
url: https://dev-lti-erp.mbugroup.id url: https://dev-lti-erp.mbugroup.id
# ==========================================================
# ====== STAGING (Branch staging) ====== # ====== STAGING (Branch staging) ======
# ==========================================================
build:staging: build:staging:
<<: *build_template <<: *build_template
rules: rules:
@@ -165,25 +183,3 @@ deploy:staging:
environment: environment:
name: staging name: staging
url: https://stg-lti-erp.mbugroup.id url: https://stg-lti-erp.mbugroup.id
# ====== PRODUCTION ======
# build:production:
# <<: *build_template
# rules:
# # pilih salah satu: pakai branch master ATAU pakai tags rilis
# - if: '$CI_COMMIT_BRANCH == "master"'
# # - if: '$CI_COMMIT_TAG' # kalau mau rilis via tag, uncomment ini dan hapus baris di atas
# environment:
# name: production
# deploy:production:
# <<: *deploy_template
# needs: ["build:production"]
# rules:
# - if: '$CI_COMMIT_BRANCH == "master"'
# # - if: '$CI_COMMIT_TAG' # selaras dengan rule di build:production
# variables:
# S3_BUCKET: "lti-erp.mbugroup.id"
# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
# environment:
# name: production
+13 -8
View File
@@ -29,8 +29,8 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
>('/sso/userinfo', httpClientFetcher, { >('/sso/userinfo', httpClientFetcher, {
shouldRetryOnError: false, shouldRetryOnError: false,
// refresh every 13 minutes // refresh every 12 minutes
refreshInterval: 13 * 60 * 1000, refreshInterval: 12 * 60 * 1000,
}); });
useEffect(() => { useEffect(() => {
@@ -61,12 +61,20 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
async () => { async () => {
await AuthApi.refresh(); await AuthApi.refresh();
}, },
13 * 60 * 1000 12 * 60 * 1000
); );
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
useEffect(() => {
const refreshUserSession = async () => {
await AuthApi.refresh();
};
refreshUserSession();
}, []);
if ( if (
(isLoadingUserResponse && !userResponse && !userErrorResponse) || (isLoadingUserResponse && !userResponse && !userErrorResponse) ||
(!userResponse && !userErrorResponse) (!userResponse && !userErrorResponse)
@@ -78,7 +86,7 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
); );
} }
if (userErrorResponse) { if (!isLoadingUserResponse && userErrorResponse) {
return ( return (
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'> <div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
<h2 className='text-2xl font-bold text-error'>Authentication Failed</h2> <h2 className='text-2xl font-bold text-error'>Authentication Failed</h2>
@@ -86,10 +94,7 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
Please try refreshing the page or contact support if the problem Please try refreshing the page or contact support if the problem
persists. persists.
</p> </p>
<button <button className='btn btn-primary' onClick={() => redirectToSSO()}>
className='btn btn-primary'
onClick={() => window.location.reload()}
>
Retry Retry
</button> </button>
</div> </div>
@@ -37,88 +37,88 @@ const ClosingSapronakCalculationTable = ({
): ColumnDef<RowSapronakCalculation>[] => [ ): ColumnDef<RowSapronakCalculation>[] => [
{ {
header: 'Tanggal', header: 'Tanggal',
accessorKey: 'tanggal', accessorKey: 'date',
cell: (props) => cell: (props) =>
props.row.original.tanggal props.row.original.date
? formatDate(props.row.original.tanggal, 'DD MMM YYYY') ? formatDate(props.row.original.date, 'DD MMM YYYY')
: '-', : '-',
footer: 'Total', footer: 'Total',
}, },
{ {
header: 'No. Referensi', header: 'No. Referensi',
accessorKey: 'no_referensi', accessorKey: 'reference_number',
cell: (props) => (props.row.original.no_referensi as string) || '-', cell: (props) => (props.row.original.reference_number as string) || '-',
footer: '', footer: '',
}, },
{ {
header: 'QTY Masuk', header: 'QTY Masuk',
accessorKey: 'qty_masuk', accessorKey: 'qty_in',
cell: (props) => cell: (props) =>
props.row.original.qty_masuk props.row.original.qty_in
? formatNumber(props.row.original.qty_masuk as number) ? formatNumber(props.row.original.qty_in as number)
: '-', : '-',
footer: total footer: total
? () => ( ? () => (
<div className='font-semibold text-gray-900'> <div className='font-semibold text-gray-900'>
{total?.qty_masuk ? formatNumber(total?.qty_masuk) : '-'} {total?.qty_in ? formatNumber(total?.qty_in) : '-'}
</div> </div>
) )
: '', : '',
}, },
{ {
header: 'QTY Keluar', header: 'QTY Keluar',
accessorKey: 'qty_keluar', accessorKey: 'qty_out',
cell: (props) => cell: (props) =>
props.row.original.qty_keluar props.row.original.qty_out
? formatNumber(props.row.original.qty_keluar as number) ? formatNumber(props.row.original.qty_out as number)
: '-', : '-',
footer: total footer: total
? () => ( ? () => (
<div className='font-semibold text-gray-900'> <div className='font-semibold text-gray-900'>
{total?.qty_keluar ? formatNumber(total?.qty_keluar) : '-'} {total?.qty_out ? formatNumber(total?.qty_out) : '-'}
</div> </div>
) )
: '', : '',
}, },
{ {
header: 'QTY Pakai', header: 'QTY Pakai',
accessorKey: 'qty_pakai', accessorKey: 'qty_used',
cell: (props) => cell: (props) =>
props.row.original.qty_pakai props.row.original.qty_used
? formatNumber(props.row.original.qty_pakai as number) ? formatNumber(props.row.original.qty_used as number)
: '-', : '-',
footer: total footer: total
? () => ( ? () => (
<div className='font-semibold text-gray-900'> <div className='font-semibold text-gray-900'>
{total?.qty_pakai ? formatNumber(total?.qty_pakai) : '-'} {total?.qty_used ? formatNumber(total?.qty_used) : '-'}
</div> </div>
) )
: '', : '',
}, },
{ {
header: 'Uraian', header: 'Uraian',
accessorKey: 'uraian', accessorKey: 'description',
cell: (props) => (props.row.original.uraian as string) || '-', cell: (props) => (props.row.original.description as string) || '-',
footer: '', footer: '',
}, },
{ {
header: 'Kategori Produk', header: 'Kategori Produk',
accessorKey: 'kategori_produk', accessorKey: 'product_category',
cell: (props) => (props.row.original.kategori_produk as string) || '-', cell: (props) => (props.row.original.product_category as string) || '-',
footer: '', footer: '',
}, },
{ {
header: 'Harga Beli/Qty (Rp)', header: 'Harga Beli/Qty (Rp)',
accessorKey: 'harga_beli_per_qty', accessorKey: 'unit_price',
cell: (props) => cell: (props) =>
props.row.original.harga_beli_per_qty props.row.original.unit_price
? formatCurrency(props.row.original.harga_beli_per_qty as number) ? formatCurrency(props.row.original.unit_price as number)
: '-', : '-',
footer: total footer: total
? () => ( ? () => (
<div className='font-semibold text-gray-900'> <div className='font-semibold text-gray-900'>
{total?.harga_beli_per_qty {total?.avg_unit_price
? formatCurrency(total?.harga_beli_per_qty) ? formatCurrency(total?.avg_unit_price)
: '-'} : '-'}
</div> </div>
) )
@@ -126,32 +126,32 @@ const ClosingSapronakCalculationTable = ({
}, },
{ {
header: 'Total Harga (Rp)', header: 'Total Harga (Rp)',
accessorKey: 'total_harga', accessorKey: 'total_amount',
cell: (props) => cell: (props) =>
props.row.original.total_harga props.row.original.total_amount
? formatCurrency(props.row.original.total_harga as number) ? formatCurrency(props.row.original.total_amount as number)
: '-', : '-',
footer: total footer: total
? () => ( ? () => (
<div className='font-semibold text-gray-900'> <div className='font-semibold text-gray-900'>
{total?.total_harga ? formatCurrency(total?.total_harga) : '-'} {total?.total_amount ? formatCurrency(total?.total_amount) : '-'}
</div> </div>
) )
: '', : '',
}, },
{ {
header: 'Keterangan', header: 'Keterangan',
accessorKey: 'keterangan', accessorKey: 'notes',
cell: (props) => (props.row.original.keterangan as string) || '-', cell: (props) => (props.row.original.notes as string) || '-',
footer: '', footer: '',
}, },
]; ];
// Memoize columns untuk setiap kategori // Memoize columns untuk setiap kategori
const docBroilerColumns = useMemo( const docColumns = useMemo(
() => () =>
isResponseSuccess(sapronakCalculation) isResponseSuccess(sapronakCalculation)
? createColumns(sapronakCalculation.data?.doc_broiler?.total) ? createColumns(sapronakCalculation.data?.doc?.total)
: createColumns(), : createColumns(),
[sapronakCalculation] [sapronakCalculation]
); );
@@ -172,10 +172,18 @@ const ClosingSapronakCalculationTable = ({
[sapronakCalculation] [sapronakCalculation]
); );
const pulletColumns = useMemo(
() =>
isResponseSuccess(sapronakCalculation)
? createColumns(sapronakCalculation.data?.pullet?.total)
: createColumns(),
[sapronakCalculation]
);
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<Card <Card
title='DOC Broiler' title='DOC'
collapsible collapsible
defaultCollapsed={false} defaultCollapsed={false}
className={{ className={{
@@ -186,10 +194,10 @@ const ClosingSapronakCalculationTable = ({
<Table<RowSapronakCalculation> <Table<RowSapronakCalculation>
data={ data={
isResponseSuccess(sapronakCalculation) isResponseSuccess(sapronakCalculation)
? (sapronakCalculation.data?.doc_broiler?.rows ?? []) ? (sapronakCalculation.data?.doc?.rows ?? [])
: [] : []
} }
columns={docBroilerColumns} columns={docColumns}
className={{ className={{
containerClassName: 'my-4', containerClassName: 'my-4',
}} }}
@@ -242,6 +250,29 @@ const ClosingSapronakCalculationTable = ({
renderFooter={isResponseSuccess(sapronakCalculation)} renderFooter={isResponseSuccess(sapronakCalculation)}
/> />
</Card> </Card>
<Card
title='Pullet'
variant='bordered'
collapsible
defaultCollapsed={true}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={
isResponseSuccess(sapronakCalculation)
? (sapronakCalculation.data?.pullet?.rows ?? [])
: []
}
columns={pulletColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={isResponseSuccess(sapronakCalculation)}
/>
</Card>
</div> </div>
); );
}; };
+132 -109
View File
@@ -2,7 +2,10 @@
import Button from '@/components/Button'; import Button from '@/components/Button';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import Modal, { useModal } from '@/components/Modal'; import Modal, { 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';
@@ -28,6 +31,8 @@ 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 { useAuth } from '@/services/hooks/useAuth'; import { useAuth } from '@/services/hooks/useAuth';
import { CustomerApi, ProductApi } from '@/services/api/master-data';
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
const RowsOptionsMenu = ({ const RowsOptionsMenu = ({
type = 'dropdown', type = 'dropdown',
@@ -52,7 +57,7 @@ const RowsOptionsMenu = ({
)} )}
> >
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
{/* <RequirePermission permissions='lti.marketing.delivery_order.detail'> <RequirePermission permissions='lti.marketing.delivery_order.detail'>
<Button <Button
href={`/marketing/detail?marketingId=${props.row.original.id}`} href={`/marketing/detail?marketingId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -62,19 +67,10 @@ const RowsOptionsMenu = ({
<Icon icon='mdi:eye-outline' width={16} height={16} /> <Icon icon='mdi:eye-outline' width={16} height={16} />
Detail Detail
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
href={`/marketing/detail?marketingId=${props.row.original.id}`}
variant='ghost'
color='primary'
className='justify-start text-sm'
>
<Icon icon='mdi:eye-outline' width={16} height={16} />
Detail
</Button>
{props.row.original.latest_approval.step_number != 1 && ( {props.row.original.latest_approval.step_number != 1 && (
<> <>
{/* <RequirePermission <RequirePermission
permissions={ permissions={
props.row.original.latest_approval.step_number == 3 props.row.original.latest_approval.step_number == 3
? 'lti.marketing.delivery_order.update' ? 'lti.marketing.delivery_order.update'
@@ -101,32 +97,12 @@ const RowsOptionsMenu = ({
<Icon icon='mdi:truck' width={16} height={16} /> <Icon icon='mdi:truck' width={16} height={16} />
Deliver Deliver
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
href={
props.row.original.latest_approval.step_number == 3
? `/marketing/detail/delivery-orders/edit?marketingId=${props.row.original.id}`
: props.row.original.latest_approval.step_number == 2
? `/marketing/add/delivery-orders?marketingId=${props.row.original.id}`
: undefined
}
onClick={() => {
if (props.row.original.latest_approval.step_number == 2) {
deliveryClickHandler?.();
}
}}
variant='ghost'
color='success'
className='justify-start text-sm'
>
<Icon icon='mdi:truck' width={16} height={16} />
Deliver
</Button>
</> </>
)} )}
{props.row.original.latest_approval.step_number != 3 && ( {props.row.original.latest_approval.step_number != 3 && (
<> <>
{/* <RequirePermission permissions='lti.marketing.sales_order.update'> <RequirePermission permissions='lti.marketing.sales_order.update'>
<Button <Button
href={`/marketing/detail/sales-orders/edit?marketingId=${props.row.original.id}`} href={`/marketing/detail/sales-orders/edit?marketingId=${props.row.original.id}`}
variant='ghost' variant='ghost'
@@ -136,19 +112,10 @@ const RowsOptionsMenu = ({
<Icon icon='mdi:pencil-outline' width={16} height={16} /> <Icon icon='mdi:pencil-outline' width={16} height={16} />
Edit Edit
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
href={`/marketing/detail/sales-orders/edit?marketingId=${props.row.original.id}`}
variant='ghost'
color='warning'
className='justify-start text-sm'
>
<Icon icon='mdi:pencil-outline' width={16} height={16} />
Edit
</Button>
</> </>
)} )}
{/* <RequirePermission permissions='lti.marketing.sales_order.delete'> <RequirePermission permissions='lti.marketing.sales_order.delete'>
<Button <Button
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
@@ -158,16 +125,7 @@ const RowsOptionsMenu = ({
<Icon icon='mdi:delete-outline' width={16} height={16} /> <Icon icon='mdi:delete-outline' width={16} height={16} />
Delete Delete
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
onClick={deleteClickHandler}
variant='ghost'
color='error'
className='text-error hover:text-inherit justify-start text-sm'
>
<Icon icon='mdi:delete-outline' width={16} height={16} />
Delete
</Button>
</div> </div>
</div> </div>
); );
@@ -175,8 +133,6 @@ const RowsOptionsMenu = ({
const MarketingTable = () => { const MarketingTable = () => {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>( const [approveAction, setApproveAction] = useState<'APPROVED' | 'REJECTED'>(
'APPROVED' 'APPROVED'
@@ -186,22 +142,68 @@ const MarketingTable = () => {
const { permissionCheck } = useAuth(); const { permissionCheck } = useAuth();
const router = useRouter(); const router = useRouter();
const {
data: marketing,
isLoading: isLoadingMarketing,
mutate: refreshMarketing,
} = useSWR(MarketingApi.basePath, MarketingApi.getAllFetcher);
const deleteModal = useModal(); const deleteModal = useModal();
const confirmationModal = useModal(); const confirmationModal = useModal();
const productsModal = useModal(); const productsModal = useModal();
const deliveryModal = useModal(); const deliveryModal = useModal();
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterToQueryString,
} = useTableFilter({
initial: {
search: '',
product_ids: '',
status: '',
customer_id: '',
page: 1,
limit: 10,
},
paramMap: {
page: 'page',
pageSize: 'limit',
product_ids: 'product_ids',
status: 'status',
customer_id: 'customer_id',
},
});
// ===== FETCH DATA =====
const {
data: marketing,
isLoading: isLoadingMarketing,
mutate: refreshMarketing,
} = useSWR(
`${MarketingApi.basePath}${getTableFilterToQueryString()}`,
MarketingApi.getAllFetcher
);
// ===== OPTIONS =====
const {
options: productsOptions,
isLoadingOptions: isLoadingProductsOptions,
} = useSelect(ProductApi.basePath, 'id', 'name', '', {
limit: 'limit',
});
const {
options: customersOptions,
isLoadingOptions: isLoadingCustomersOptions,
} = useSelect(CustomerApi.basePath, 'id', 'name', '', {
limit: 'limit',
});
const statusOptions = MARKETING_APPROVAL_LINE.map((item) => ({
value: item.step_number,
label: item.step_name,
}));
// ===== HANDLER =====
const searchChangeHandler = useCallback( const searchChangeHandler = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); setSearch(e.target.value);
setPage(1); updateFilter('page', 1);
updateFilter('search', e.target.value);
}, },
[] []
); );
@@ -209,7 +211,8 @@ const MarketingTable = () => {
(val: OptionType | OptionType[] | null) => { (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType; const newVal = val as OptionType;
setPageSize(newVal.value as number); setPageSize(newVal.value as number);
setPage(1); updateFilter('page', 1);
updateFilter('limit', newVal.value as number);
}, },
[] []
); );
@@ -314,20 +317,6 @@ const MarketingTable = () => {
); );
}; };
const {
state: tableFilterState,
updateFilter,
toQueryString: getTableFilterToQueryString,
} = useTableFilter({
initial: {
search: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
},
});
const getRowCanSelect = (row: Row<Marketing>): boolean => { const getRowCanSelect = (row: Row<Marketing>): boolean => {
const approval = row.original.latest_approval; const approval = row.original.latest_approval;
return approval?.step_number === 1 && approval?.action !== 'REJECTED'; return approval?.step_number === 1 && approval?.action !== 'REJECTED';
@@ -353,7 +342,7 @@ const MarketingTable = () => {
}} }}
/> />
<div className='flex flex-row gap-2'> <div className='flex flex-row gap-2'>
{/* <RequirePermission permissions='lti.marketing.sales_order.approve'> <RequirePermission permissions='lti.marketing.sales_order.approve'>
<Button <Button
color='success' color='success'
onClick={approveClickHandler} onClick={approveClickHandler}
@@ -363,18 +352,9 @@ const MarketingTable = () => {
<Icon icon='material-symbols:check' width={24} height={24} /> <Icon icon='material-symbols:check' width={24} height={24} />
Approve Approve
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
color='success'
onClick={approveClickHandler}
className='justify-start text-sm'
disabled={disableApprove}
>
<Icon icon='material-symbols:check' width={24} height={24} />
Approve
</Button>
{/* <RequirePermission permissions='lti.marketing.sales_order.approve'> <RequirePermission permissions='lti.marketing.sales_order.approve'>
<Button <Button
color='error' color='error'
onClick={rejectClickHandler} onClick={rejectClickHandler}
@@ -384,19 +364,10 @@ const MarketingTable = () => {
<Icon icon='material-symbols:close' width={24} height={24} /> <Icon icon='material-symbols:close' width={24} height={24} />
Reject Reject
</Button> </Button>
</RequirePermission> */} </RequirePermission>
<Button
color='error'
onClick={rejectClickHandler}
className='justify-start text-sm'
disabled={disableReject}
>
<Icon icon='material-symbols:close' width={24} height={24} />
Reject
</Button>
</div> </div>
<TableRowSizeSelector <TableRowSizeSelector
value={pageSize} value={tableFilterState.pageSize}
onChange={pageSizeChangeHandler} onChange={pageSizeChangeHandler}
options={ROWS_OPTIONS} options={ROWS_OPTIONS}
className='flex sm:flex-row flex-col gap-3 items-end justify-end' className='flex sm:flex-row flex-col gap-3 items-end justify-end'
@@ -406,7 +377,29 @@ const MarketingTable = () => {
label='Product' label='Product'
isClearable isClearable
placeholder='Pilih product' placeholder='Pilih product'
options={[]} options={productsOptions}
isLoading={isLoadingProductsOptions}
value={
tableFilterState.product_ids
?.split(',')
.map((id) =>
productsOptions.find(
(option) => option.value === Number(id)
)
)
.filter(
(option): option is { value: number; label: string } =>
option !== undefined
) ?? null
}
onChange={(value: OptionType | OptionType[] | null) =>
updateFilter(
'product_ids',
(value as OptionType[])
?.map((item: OptionType) => item.value.toString())
.join(',') || ''
)
}
isMulti isMulti
/> />
{/* select status */} {/* select status */}
@@ -414,14 +407,43 @@ const MarketingTable = () => {
label='Status' label='Status'
isClearable isClearable
placeholder='Pilih status' placeholder='Pilih status'
options={[]} options={statusOptions}
value={
tableFilterState.status
? statusOptions.find(
(option) =>
option.value === Number(tableFilterState.status)
)
: null
}
onChange={(value: OptionType | OptionType[] | null) =>
updateFilter(
'status',
(value as OptionType)?.value.toString() || ''
)
}
/> />
{/* select customer */} {/* select customer */}
<SelectInput <SelectInput
label='Customer' label='Customer'
isClearable isClearable
placeholder='Pilih customer' placeholder='Pilih customer'
options={[]} options={customersOptions}
isLoading={isLoadingCustomersOptions}
value={
tableFilterState.customer_id
? customersOptions.find(
(option) =>
option.value === Number(tableFilterState.customer_id)
)
: null
}
onChange={(value: OptionType | OptionType[] | null) =>
updateFilter(
'customer_id',
(value as OptionType)?.value.toString() || ''
)
}
/> />
</TableRowSizeSelector> </TableRowSizeSelector>
</div> </div>
@@ -587,8 +609,8 @@ const MarketingTable = () => {
}, },
}, },
]} ]}
pageSize={pageSize} pageSize={tableFilterState.pageSize}
page={page} page={tableFilterState.page}
onPageChange={setPage} onPageChange={setPage}
className={{ className={{
tableWrapperClassName: 'overflow-x-auto min-h-full!', tableWrapperClassName: 'overflow-x-auto min-h-full!',
@@ -712,6 +734,7 @@ const MarketingTable = () => {
'px-6 py-3 last:flex last:flex-row last:justify-end', 'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden', paginationClassName: 'hidden',
}} }}
isLoading={isLoadingMarketing}
/> />
</Modal> </Modal>
</> </>
+11 -38
View File
@@ -3,7 +3,6 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
// Dashboard // Dashboard
'/dashboard/': ['lti.dashboard.list'], '/dashboard/': ['lti.dashboard.list'],
'/dashboard': ['lti.dashboard.list'],
// Production // Production
// Production - Project Flock // Production - Project Flock
@@ -58,27 +57,14 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/purchase/detail/edit/': ['lti.purchase.update'], '/purchase/detail/edit/': ['lti.purchase.update'],
// Marketing // Marketing
'/marketing/': ['lti.dashboard.list', 'lti.marketing.delivery_order.list'], '/marketing/': ['lti.marketing.delivery_order.list'],
'/marketing/add/delivery-orders/': [ '/marketing/add/delivery-orders/': ['lti.marketing.delivery_order.create'],
'lti.dashboard.list', '/marketing/add/sales-orders/': ['lti.marketing.sales_order.create'],
'lti.marketing.delivery_order.create', '/marketing/detail/': ['lti.marketing.delivery_order.detail'],
],
'/marketing/add/sales-orders/': [
'lti.dashboard.list',
'lti.marketing.sales_order.create',
],
'/marketing/detail/': [
'lti.dashboard.list',
'lti.marketing.delivery_order.detail',
],
'/marketing/detail/delivery-orders/edit/': [ '/marketing/detail/delivery-orders/edit/': [
'lti.dashboard.list',
'lti.marketing.delivery_order.update', 'lti.marketing.delivery_order.update',
], ],
'/marketing/detail/sales-orders/edit/': [ '/marketing/detail/sales-orders/edit/': ['lti.marketing.sales_order.update'],
'lti.dashboard.list',
'lti.marketing.sales_order.update',
],
// Expense // Expense
'/expense/': ['lti.expense.list'], '/expense/': ['lti.expense.list'],
@@ -89,19 +75,12 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/expense/realization/edit/': ['lti.expense.update.realization'], '/expense/realization/edit/': ['lti.expense.update.realization'],
// Finance // Finance
'/finance/': ['lti.dashboard.list', 'lti.finance.transaction.list'], '/finance/': ['lti.finance.transaction.list'],
'/finance/detail/': ['lti.dashboard.list', 'lti.finance.transaction.detail'], '/finance/detail/': ['lti.finance.transaction.detail'],
'/finance/add/': ['lti.dashboard.list', 'lti.finance.payments.create'], '/finance/add/': ['lti.finance.payments.create'],
'/finance/detail/edit/': [ '/finance/detail/edit/': ['lti.finance.payments.update'],
'lti.dashboard.list', '/finance/add/initial-balance/': ['lti.finance.initial_balances.create'],
'lti.finance.payments.update',
],
'/finance/add/initial-balance/': [
'lti.dashboard.list',
'lti.finance.initial_balances.create',
],
'/finance/detail/edit/initial-balance/': [ '/finance/detail/edit/initial-balance/': [
'lti.dashboard.list',
'lti.finance.initial_balances.update', 'lti.finance.initial_balances.update',
], ],
'/finance/add/injection/': ['lti.finance.injections.create'], '/finance/add/injection/': ['lti.finance.injections.create'],
@@ -203,20 +182,14 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/master-data/flock/detail/': ['lti.master.flocks.detail'], '/master-data/flock/detail/': ['lti.master.flocks.detail'],
'/master-data/flock/detail/edit/': ['lti.master.flocks.update'], '/master-data/flock/detail/edit/': ['lti.master.flocks.update'],
'/master-data/production-standard/': [ '/master-data/production-standard/': ['lti.master.production_standards.list'],
'lti.dashboard.list',
'lti.master.production_standards.list',
],
'/master-data/production-standard/add/': [ '/master-data/production-standard/add/': [
'lti.dashboard.list',
'lti.master.production_standards.create', 'lti.master.production_standards.create',
], ],
'/master-data/production-standard/detail/': [ '/master-data/production-standard/detail/': [
'lti.dashboard.list',
'lti.master.production_standards.detail', 'lti.master.production_standards.detail',
], ],
'/master-data/production-standard/detail/edit/': [ '/master-data/production-standard/detail/edit/': [
'lti.dashboard.list',
'lti.master.production_standards.update', 'lti.master.production_standards.update',
], ],
}; };
+17 -16
View File
@@ -147,25 +147,25 @@ export type ClosingProductionData = {
export type RowSapronakCalculation = { export type RowSapronakCalculation = {
id: number; id: number;
tanggal: string; date: string;
no_referensi: string; reference_number: string;
qty_masuk: number; qty_in: number;
qty_keluar: number; qty_out: number;
qty_pakai: number; qty_used: number;
uraian: string; description: string;
kategori_produk: string; product_category: string;
harga_beli_per_qty: number; unit_price: number;
total_harga: number; total_amount: number;
keterangan: string; notes: string;
}; };
export type TotalSapronakCalculation = { export type TotalSapronakCalculation = {
label: string; label: string;
qty_masuk: number; qty_in: number;
qty_keluar: number; qty_out: number;
qty_pakai: number; qty_used: number;
harga_beli_per_qty: number; avg_unit_price: number;
total_harga: number; total_amount: number;
}; };
export type ClosingSapronakCalculationItem = { export type ClosingSapronakCalculationItem = {
@@ -174,9 +174,10 @@ export type ClosingSapronakCalculationItem = {
}; };
export type ClosingSapronakCalculation = { export type ClosingSapronakCalculation = {
doc_broiler: ClosingSapronakCalculationItem; doc: ClosingSapronakCalculationItem;
ovk: ClosingSapronakCalculationItem; ovk: ClosingSapronakCalculationItem;
pakan: ClosingSapronakCalculationItem; pakan: ClosingSapronakCalculationItem;
pullet: ClosingSapronakCalculationItem;
}; };
// ====== OVERHEAD ====== // ====== OVERHEAD ======