mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
Merge branch 'feat/share-daily-checklist-to-wa' into 'development'
[FEAT/FE] Share Daily Checklist See merge request mbugroup/lti-web-client!428
This commit is contained in:
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
npm run format
|
#npm run format
|
||||||
npm run lint
|
#npm run lint
|
||||||
npm run typecheck
|
#npm run typecheck
|
||||||
git add .
|
#git add .
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@@ -10,16 +10,14 @@ import ExpenseRequestContent from '@/components/pages/expense/ExpenseRequestCont
|
|||||||
import ExpenseRealizationContent from '@/components/pages/expense/ExpenseRealizationContent';
|
import ExpenseRealizationContent from '@/components/pages/expense/ExpenseRealizationContent';
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { getExpenseListReturnTo } from '@/lib/expense-list-navigation';
|
|
||||||
|
|
||||||
interface ExpenseDetailProps {
|
interface ExpenseDetailProps {
|
||||||
initialValues?: Expense;
|
initialValues?: Expense;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpenseDetail: React.FC<ExpenseDetailProps> = ({ initialValues }) => {
|
const ExpenseDetail: React.FC<ExpenseDetailProps> = ({ initialValues }) => {
|
||||||
|
const router = useRouter();
|
||||||
const [activeTab, setActiveTab] = useState<string>('request');
|
const [activeTab, setActiveTab] = useState<string>('request');
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const returnTo = getExpenseListReturnTo(searchParams);
|
|
||||||
|
|
||||||
const expenseDetailTabs = useMemo(() => {
|
const expenseDetailTabs = useMemo(() => {
|
||||||
const validTabs = [
|
const validTabs = [
|
||||||
@@ -50,8 +48,8 @@ const ExpenseDetail: React.FC<ExpenseDetailProps> = ({ initialValues }) => {
|
|||||||
<section className='w-full max-w-full pb-16'>
|
<section className='w-full max-w-full pb-16'>
|
||||||
<header className='flex flex-col gap-4'>
|
<header className='flex flex-col gap-4'>
|
||||||
<Button
|
<Button
|
||||||
href={returnTo}
|
|
||||||
variant='link'
|
variant='link'
|
||||||
|
onClick={router.back}
|
||||||
className='w-fit p-0 text-primary'
|
className='w-fit p-0 text-primary'
|
||||||
>
|
>
|
||||||
<Icon icon='uil:arrow-left' width={24} height={24} />
|
<Icon icon='uil:arrow-left' width={24} height={24} />
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ const ExpensesTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.supplier.name ?? '-',
|
accessorFn: (row) => row.supplier.name ?? '-',
|
||||||
header: 'Vendor',
|
header: 'Uraian',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'grand_total',
|
accessorKey: 'grand_total',
|
||||||
|
|||||||
@@ -735,7 +735,7 @@ const MarketingTable = () => {
|
|||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
{idsToProcess.length > 0 && (
|
{idsToProcess.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className='divider divider-horizontal w-px p-0 m-0 bg-base-content/10 text-base-content/10 before:bg-base-content/10 before:w-px after:bg-base-content/10 after:w-px'></div>
|
<div className='divider divider-horizontal w-px p-0 m-0 bg-base-content/10 text-base-content/10 before:bg-base-content/10 before:w-px after:bg-base-content/10 after:w-px' />
|
||||||
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
<Button
|
<Button
|
||||||
color='error'
|
color='error'
|
||||||
@@ -749,7 +749,7 @@ const MarketingTable = () => {
|
|||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
/>
|
/>
|
||||||
Reject
|
Reject ({idsToProcess.length} Item)
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
<RequirePermission permissions='lti.marketing.sales_order.approve'>
|
||||||
@@ -765,7 +765,7 @@ const MarketingTable = () => {
|
|||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
/>
|
/>
|
||||||
Approve
|
Approve ({idsToProcess.length} Item)
|
||||||
</Button>
|
</Button>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { RefObject, useState, useEffect, useMemo } from 'react';
|
import { RefObject, useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
@@ -26,22 +26,32 @@ import { isResponseSuccess } from '@/lib/api-helper';
|
|||||||
|
|
||||||
interface PurchaseFilterModalProps {
|
interface PurchaseFilterModalProps {
|
||||||
ref: RefObject<HTMLDialogElement | null>;
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
initialValues?: {
|
||||||
|
poDate: string;
|
||||||
|
category: OptionType<number>[];
|
||||||
|
status: OptionType<string>[];
|
||||||
|
supplier: OptionType<number> | null;
|
||||||
|
area: OptionType<number> | null;
|
||||||
|
location: OptionType<number> | null;
|
||||||
|
project_flock: OptionType<number> | null;
|
||||||
|
project_flock_kandang: OptionType<number> | null;
|
||||||
|
};
|
||||||
onSubmit?: (values: PurchaseFilter) => void;
|
onSubmit?: (values: PurchaseFilter) => void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PurchaseFilterModal = ({
|
const PurchaseFilterModal = ({
|
||||||
ref,
|
ref,
|
||||||
|
initialValues,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onReset,
|
onReset,
|
||||||
}: PurchaseFilterModalProps) => {
|
}: PurchaseFilterModalProps) => {
|
||||||
const closeModalHandler = () => {
|
const closeModalHandler = useCallback(() => {
|
||||||
ref.current?.close();
|
ref.current?.close();
|
||||||
};
|
}, [ref]);
|
||||||
|
|
||||||
// ===== DATE ERROR STATE =====
|
// ===== DATE ERROR STATE =====
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
|
||||||
|
|
||||||
// ===== CLEANUP TOAST ON UNMOUNT =====
|
// ===== CLEANUP TOAST ON UNMOUNT =====
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -81,8 +91,12 @@ const PurchaseFilterModal = ({
|
|||||||
'search'
|
'search'
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedAreaId, setSelectedAreaId] = useState('');
|
const [selectedAreaId, setSelectedAreaId] = useState(
|
||||||
const [selectedLocationId, setSelectedLocationId] = useState('');
|
initialValues?.area?.value ? String(initialValues.area.value) : ''
|
||||||
|
);
|
||||||
|
const [selectedLocationId, setSelectedLocationId] = useState(
|
||||||
|
initialValues?.location?.value ? String(initialValues.location.value) : ''
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setSupplierInputValue,
|
setInputValue: setSupplierInputValue,
|
||||||
@@ -133,7 +147,8 @@ const PurchaseFilterModal = ({
|
|||||||
project_flock: OptionType<number> | null;
|
project_flock: OptionType<number> | null;
|
||||||
project_flock_kandang: OptionType<number> | null;
|
project_flock_kandang: OptionType<number> | null;
|
||||||
}>({
|
}>({
|
||||||
initialValues: {
|
// enableReinitialize: true,
|
||||||
|
initialValues: initialValues || {
|
||||||
poDate: '',
|
poDate: '',
|
||||||
category: [],
|
category: [],
|
||||||
status: [],
|
status: [],
|
||||||
@@ -147,12 +162,18 @@ const PurchaseFilterModal = ({
|
|||||||
const formattedValues = {
|
const formattedValues = {
|
||||||
...values,
|
...values,
|
||||||
category: values.category.map((item) => String(item.value)),
|
category: values.category.map((item) => String(item.value)),
|
||||||
|
category_labels: values.category,
|
||||||
status: values.status.map((item) => String(item.value)),
|
status: values.status.map((item) => String(item.value)),
|
||||||
supplier_id: values.supplier?.value,
|
supplier_id: values.supplier?.value,
|
||||||
|
supplier_label: values.supplier?.label,
|
||||||
area_id: values.area?.value,
|
area_id: values.area?.value,
|
||||||
|
area_label: values.area?.label,
|
||||||
location_id: values.location?.value,
|
location_id: values.location?.value,
|
||||||
|
location_label: values.location?.label,
|
||||||
project_flock_id: values.project_flock?.value,
|
project_flock_id: values.project_flock?.value,
|
||||||
|
project_flock_label: values.project_flock?.label,
|
||||||
project_flock_kandang_id: values.project_flock_kandang?.value,
|
project_flock_kandang_id: values.project_flock_kandang?.value,
|
||||||
|
project_flock_kandang_label: values.project_flock_kandang?.label,
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit?.(formattedValues);
|
onSubmit?.(formattedValues);
|
||||||
@@ -166,6 +187,17 @@ const PurchaseFilterModal = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { resetForm, submitForm } = formik;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedAreaId(
|
||||||
|
initialValues?.area?.value ? String(initialValues.area.value) : ''
|
||||||
|
);
|
||||||
|
setSelectedLocationId(
|
||||||
|
initialValues?.location?.value ? String(initialValues.location.value) : ''
|
||||||
|
);
|
||||||
|
}, [initialValues?.area, initialValues?.location]);
|
||||||
|
|
||||||
const projectFlockKandangOptions = useMemo(() => {
|
const projectFlockKandangOptions = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
!formik.values.project_flock ||
|
!formik.values.project_flock ||
|
||||||
@@ -197,6 +229,29 @@ const PurchaseFilterModal = ({
|
|||||||
formik.setFieldValue('status', val);
|
formik.setFieldValue('status', val);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formikResetHandler = useCallback(() => {
|
||||||
|
resetForm({
|
||||||
|
values: {
|
||||||
|
poDate: '',
|
||||||
|
category: [],
|
||||||
|
status: [],
|
||||||
|
supplier: null,
|
||||||
|
area: null,
|
||||||
|
location: null,
|
||||||
|
project_flock: null,
|
||||||
|
project_flock_kandang: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setSelectedAreaId('');
|
||||||
|
setSelectedLocationId('');
|
||||||
|
onReset?.();
|
||||||
|
closeModalHandler();
|
||||||
|
}, [resetForm, onReset, closeModalHandler]);
|
||||||
|
|
||||||
|
const formikSubmitHandler = useCallback(async () => {
|
||||||
|
await submitForm();
|
||||||
|
}, [submitForm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -206,7 +261,7 @@ const PurchaseFilterModal = ({
|
|||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
onReset={formik.handleReset}
|
onReset={formikResetHandler}
|
||||||
className='w-full flex flex-col'
|
className='w-full flex flex-col'
|
||||||
>
|
>
|
||||||
{/* Modal Header */}
|
{/* Modal Header */}
|
||||||
@@ -220,7 +275,9 @@ const PurchaseFilterModal = ({
|
|||||||
type='button'
|
type='button'
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='none'
|
color='none'
|
||||||
onClick={closeModalHandler}
|
onClick={() => {
|
||||||
|
closeModalHandler();
|
||||||
|
}}
|
||||||
className='p-0 text-base-content/50 hover:text-base-content'
|
className='p-0 text-base-content/50 hover:text-base-content'
|
||||||
>
|
>
|
||||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
@@ -377,7 +434,8 @@ const PurchaseFilterModal = ({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='button'
|
||||||
|
onClick={formikSubmitHandler}
|
||||||
className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm'
|
className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm'
|
||||||
>
|
>
|
||||||
Apply Filter
|
Apply Filter
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import axios from 'axios';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import { useUiStore } from '@/stores/ui/ui.store';
|
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import useSWRInfinite from 'swr/infinite';
|
|
||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
@@ -31,17 +21,34 @@ import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/Purchase
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
|
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
|
||||||
import Dropdown from '@/components/dropdown/Dropdown';
|
import Dropdown from '@/components/dropdown/Dropdown';
|
||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
|
||||||
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { Purchase, PurchaseFilter } from '@/types/api/purchase/purchase';
|
import { Purchase, PurchaseFilter } from '@/types/api/purchase/purchase';
|
||||||
import { PurchaseApi } from '@/services/api/purchase';
|
import { PurchaseApi } from '@/services/api/purchase';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
|
||||||
import { Expense } from '@/types/api/expense';
|
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
|
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
|
||||||
|
type PurchaseTableFilters = {
|
||||||
|
search: string;
|
||||||
|
po_date: string;
|
||||||
|
approval_status: string;
|
||||||
|
product_category_id: string;
|
||||||
|
product_category_name: string;
|
||||||
|
supplier_id: string;
|
||||||
|
supplier_name: string;
|
||||||
|
area_id: string;
|
||||||
|
area_name: string;
|
||||||
|
location_id: string;
|
||||||
|
location_name: string;
|
||||||
|
project_flock_id: string;
|
||||||
|
project_flock_name: string;
|
||||||
|
project_flock_kandang_id: string;
|
||||||
|
project_flock_kandang_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
@@ -150,9 +157,6 @@ const RowOptionsMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PurchaseTable = () => {
|
const PurchaseTable = () => {
|
||||||
const { searchValue, setSearchValue, setTableState } = useUiStore();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
@@ -168,21 +172,28 @@ const PurchaseTable = () => {
|
|||||||
// ===== TABLE FILTER STATE =====
|
// ===== TABLE FILTER STATE =====
|
||||||
const {
|
const {
|
||||||
state: tableFilterState,
|
state: tableFilterState,
|
||||||
|
setFilters,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
setPage,
|
setPage,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
toQueryString: getTableFilterQueryString,
|
toQueryString: getTableFilterQueryString,
|
||||||
} = useTableFilter({
|
} = useTableFilter<PurchaseTableFilters>({
|
||||||
initial: {
|
initial: {
|
||||||
search: '',
|
search: '',
|
||||||
po_date: '',
|
po_date: '',
|
||||||
approval_status: '',
|
approval_status: '',
|
||||||
product_category_id: '',
|
product_category_id: '',
|
||||||
|
product_category_name: '',
|
||||||
supplier_id: '',
|
supplier_id: '',
|
||||||
|
supplier_name: '',
|
||||||
area_id: '',
|
area_id: '',
|
||||||
|
area_name: '',
|
||||||
location_id: '',
|
location_id: '',
|
||||||
|
location_name: '',
|
||||||
project_flock_id: '',
|
project_flock_id: '',
|
||||||
|
project_flock_name: '',
|
||||||
project_flock_kandang_id: '',
|
project_flock_kandang_id: '',
|
||||||
|
project_flock_kandang_name: '',
|
||||||
},
|
},
|
||||||
paramMap: {
|
paramMap: {
|
||||||
page: 'page',
|
page: 'page',
|
||||||
@@ -196,6 +207,16 @@ const PurchaseTable = () => {
|
|||||||
project_flock_id: 'project_flock_id',
|
project_flock_id: 'project_flock_id',
|
||||||
project_flock_kandang_id: 'project_flock_kandang_id',
|
project_flock_kandang_id: 'project_flock_kandang_id',
|
||||||
},
|
},
|
||||||
|
excludeKeysFromUrl: [
|
||||||
|
'product_category_name',
|
||||||
|
'supplier_name',
|
||||||
|
'area_name',
|
||||||
|
'location_name',
|
||||||
|
'project_flock_name',
|
||||||
|
'project_flock_kandang_name',
|
||||||
|
],
|
||||||
|
persist: true,
|
||||||
|
storeName: 'purchase-table',
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== MODAL HOOKS =====
|
// ===== MODAL HOOKS =====
|
||||||
@@ -213,33 +234,6 @@ const PurchaseTable = () => {
|
|||||||
PurchaseApi.getAllFetcher
|
PurchaseApi.getAllFetcher
|
||||||
);
|
);
|
||||||
|
|
||||||
const getKey = (
|
|
||||||
pageIndex: number,
|
|
||||||
previousPageData: BaseApiResponse<Expense>[] | null
|
|
||||||
) => {
|
|
||||||
if (pageIndex > 0 && !previousPageData) return null;
|
|
||||||
return `${ExpenseApi.basePath}?page=${pageIndex + 1}&limit=100`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: expensesPages } = useSWRInfinite(
|
|
||||||
getKey,
|
|
||||||
ExpenseApi.getAllFetcher
|
|
||||||
);
|
|
||||||
|
|
||||||
const expenseMap = useMemo(() => {
|
|
||||||
const map = new Map<string, number>();
|
|
||||||
if (!expensesPages) return map;
|
|
||||||
|
|
||||||
expensesPages.forEach((page) => {
|
|
||||||
if (isResponseSuccess(page)) {
|
|
||||||
page.data.forEach((expense: Expense) => {
|
|
||||||
map.set(expense.reference_number, expense.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [expensesPages]);
|
|
||||||
|
|
||||||
// ===== TABLE COLUMNS DEFINITION =====
|
// ===== TABLE COLUMNS DEFINITION =====
|
||||||
const purchaseColumns: ColumnDef<Purchase>[] = [
|
const purchaseColumns: ColumnDef<Purchase>[] = [
|
||||||
{
|
{
|
||||||
@@ -258,20 +252,16 @@ const PurchaseTable = () => {
|
|||||||
return (
|
return (
|
||||||
<ul className='list-disc pl-4'>
|
<ul className='list-disc pl-4'>
|
||||||
{poExpedition.map((exp, index) => {
|
{poExpedition.map((exp, index) => {
|
||||||
const expenseId = expenseMap.get(exp.refrence);
|
|
||||||
if (expenseId) {
|
|
||||||
return (
|
return (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<Link
|
<Link
|
||||||
href={`/expense/detail/?expenseId=${expenseId}`}
|
href={`/expense/detail/?expenseId=${exp.id}`}
|
||||||
className='p-0 h-auto text-primary underline'
|
className='p-0 h-auto text-primary underline'
|
||||||
>
|
>
|
||||||
{exp.refrence}
|
{exp.refrence}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return <li key={index}>{exp.refrence}</li>;
|
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
@@ -422,58 +412,127 @@ const PurchaseTable = () => {
|
|||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
}, [selectedPurchase?.id, refreshPurchaseRequests, deleteModal]);
|
}, [selectedPurchase?.id, refreshPurchaseRequests, deleteModal]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateFilter('search', searchValue);
|
|
||||||
}, [searchValue, updateFilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTableState('purchase-table', pathname);
|
|
||||||
}, [pathname, setTableState]);
|
|
||||||
|
|
||||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = useCallback(
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
setSearchValue(e.target.value);
|
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
},
|
},
|
||||||
[updateFilter, setSearchValue]
|
[updateFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterSubmitHandler = (values: PurchaseFilter) => {
|
const filterSubmitHandler = (values: PurchaseFilter) => {
|
||||||
updateFilter('po_date', values.poDate);
|
setFilters({
|
||||||
updateFilter('product_category_id', values.category.join(','));
|
po_date: values.poDate,
|
||||||
updateFilter('approval_status', values.status.join(','));
|
product_category_id: values.category.join(','),
|
||||||
updateFilter(
|
product_category_name:
|
||||||
'supplier_id',
|
values.category_labels?.map((item) => item.label).join(',') || '',
|
||||||
values.supplier_id ? String(values.supplier_id) : ''
|
approval_status: values.status.join(','),
|
||||||
);
|
supplier_id: values.supplier_id ? String(values.supplier_id) : '',
|
||||||
updateFilter('area_id', values.area_id ? String(values.area_id) : '');
|
supplier_name: values.supplier_label || '',
|
||||||
updateFilter(
|
area_id: values.area_id ? String(values.area_id) : '',
|
||||||
'location_id',
|
area_name: values.area_label || '',
|
||||||
values.location_id ? String(values.location_id) : ''
|
location_id: values.location_id ? String(values.location_id) : '',
|
||||||
);
|
location_name: values.location_label || '',
|
||||||
updateFilter(
|
project_flock_id: values.project_flock_id
|
||||||
'project_flock_id',
|
? String(values.project_flock_id)
|
||||||
values.project_flock_id ? String(values.project_flock_id) : ''
|
: '',
|
||||||
);
|
project_flock_name: values.project_flock_label || '',
|
||||||
updateFilter(
|
project_flock_kandang_id: values.project_flock_kandang_id
|
||||||
'project_flock_kandang_id',
|
|
||||||
values.project_flock_kandang_id
|
|
||||||
? String(values.project_flock_kandang_id)
|
? String(values.project_flock_kandang_id)
|
||||||
: ''
|
: '',
|
||||||
);
|
project_flock_kandang_name: values.project_flock_kandang_label || '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterResetHandler = () => {
|
const filterResetHandler = () => {
|
||||||
updateFilter('po_date', '');
|
setFilters({
|
||||||
updateFilter('product_category_id', '');
|
po_date: '',
|
||||||
updateFilter('approval_status', '');
|
product_category_id: '',
|
||||||
updateFilter('supplier_id', '');
|
product_category_name: '',
|
||||||
updateFilter('area_id', '');
|
approval_status: '',
|
||||||
updateFilter('location_id', '');
|
supplier_id: '',
|
||||||
updateFilter('project_flock_id', '');
|
supplier_name: '',
|
||||||
updateFilter('project_flock_kandang_id', '');
|
area_id: '',
|
||||||
|
area_name: '',
|
||||||
|
location_id: '',
|
||||||
|
location_name: '',
|
||||||
|
project_flock_id: '',
|
||||||
|
project_flock_name: '',
|
||||||
|
project_flock_kandang_id: '',
|
||||||
|
project_flock_kandang_name: '',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const purchaseFilterInitialValues = useMemo(() => {
|
||||||
|
const categoryIds = tableFilterState.product_category_id
|
||||||
|
? tableFilterState.product_category_id
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const categoryLabels = tableFilterState.product_category_name
|
||||||
|
? tableFilterState.product_category_name
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const approvalStatuses = tableFilterState.approval_status
|
||||||
|
? tableFilterState.approval_status
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
poDate: tableFilterState.po_date,
|
||||||
|
category: categoryIds.map((value, index) => ({
|
||||||
|
value: Number(value),
|
||||||
|
label: categoryLabels[index] || value,
|
||||||
|
})),
|
||||||
|
status: approvalStatuses.map((value) => ({
|
||||||
|
value,
|
||||||
|
label:
|
||||||
|
PURCHASE_ORDER_APPROVAL_LINE.find((item) => item.step_name === value)
|
||||||
|
?.step_name || value,
|
||||||
|
})),
|
||||||
|
supplier: tableFilterState.supplier_id
|
||||||
|
? ({
|
||||||
|
value: Number(tableFilterState.supplier_id),
|
||||||
|
label:
|
||||||
|
tableFilterState.supplier_name || tableFilterState.supplier_id,
|
||||||
|
} as OptionType<number>)
|
||||||
|
: null,
|
||||||
|
area: tableFilterState.area_id
|
||||||
|
? ({
|
||||||
|
value: Number(tableFilterState.area_id),
|
||||||
|
label: tableFilterState.area_name || tableFilterState.area_id,
|
||||||
|
} as OptionType<number>)
|
||||||
|
: null,
|
||||||
|
location: tableFilterState.location_id
|
||||||
|
? ({
|
||||||
|
value: Number(tableFilterState.location_id),
|
||||||
|
label:
|
||||||
|
tableFilterState.location_name || tableFilterState.location_id,
|
||||||
|
} as OptionType<number>)
|
||||||
|
: null,
|
||||||
|
project_flock: tableFilterState.project_flock_id
|
||||||
|
? ({
|
||||||
|
value: Number(tableFilterState.project_flock_id),
|
||||||
|
label:
|
||||||
|
tableFilterState.project_flock_name ||
|
||||||
|
tableFilterState.project_flock_id,
|
||||||
|
} as OptionType<number>)
|
||||||
|
: null,
|
||||||
|
project_flock_kandang: tableFilterState.project_flock_kandang_id
|
||||||
|
? ({
|
||||||
|
value: Number(tableFilterState.project_flock_kandang_id),
|
||||||
|
label:
|
||||||
|
tableFilterState.project_flock_kandang_name ||
|
||||||
|
tableFilterState.project_flock_kandang_id,
|
||||||
|
} as OptionType<number>)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}, [tableFilterState]);
|
||||||
|
|
||||||
const exportToExcel = useCallback(async () => {
|
const exportToExcel = useCallback(async () => {
|
||||||
setIsLoadingExportingToExcel(true);
|
setIsLoadingExportingToExcel(true);
|
||||||
|
|
||||||
@@ -705,6 +764,7 @@ const PurchaseTable = () => {
|
|||||||
|
|
||||||
<PurchaseFilterModal
|
<PurchaseFilterModal
|
||||||
ref={filterModal.ref}
|
ref={filterModal.ref}
|
||||||
|
initialValues={purchaseFilterInitialValues}
|
||||||
onSubmit={filterSubmitHandler}
|
onSubmit={filterSubmitHandler}
|
||||||
onReset={filterResetHandler}
|
onReset={filterResetHandler}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ export function DailyChecklistContent() {
|
|||||||
const [emptyKandang, setEmptyKandang] = useState(false);
|
const [emptyKandang, setEmptyKandang] = useState(false);
|
||||||
const [emptyKandangEndDate, setEmptyKandangEndDate] = useState('');
|
const [emptyKandangEndDate, setEmptyKandangEndDate] = useState('');
|
||||||
|
|
||||||
|
const isKandangEmpty = selectedCategory === 'empty_kandang';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: kandangOptions,
|
options: kandangOptions,
|
||||||
isLoadingMore: isLoadingMoreKandang,
|
isLoadingMore: isLoadingMoreKandang,
|
||||||
@@ -298,7 +300,7 @@ export function DailyChecklistContent() {
|
|||||||
|
|
||||||
if (isResponseError(checklist)) {
|
if (isResponseError(checklist)) {
|
||||||
console.error('Error upserting checklist:', checklist.message);
|
console.error('Error upserting checklist:', checklist.message);
|
||||||
toast.error('Gagal memuat checklist');
|
toast.error('Gagal memuat checklist: ' + checklist.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +313,12 @@ export function DailyChecklistContent() {
|
|||||||
|
|
||||||
if (isResponseError(existingPhases)) {
|
if (isResponseError(existingPhases)) {
|
||||||
console.error('Error loading phases:', existingPhases.message);
|
console.error('Error loading phases:', existingPhases.message);
|
||||||
|
} else if (
|
||||||
|
existingPhases &&
|
||||||
|
existingPhases.data &&
|
||||||
|
existingPhases.data.phases.length === 0
|
||||||
|
) {
|
||||||
|
toast.success('Berhasil membuat daily checklist!');
|
||||||
} else if (
|
} else if (
|
||||||
existingPhases &&
|
existingPhases &&
|
||||||
existingPhases.data &&
|
existingPhases.data &&
|
||||||
@@ -1118,7 +1126,7 @@ export function DailyChecklistContent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase Selection Section */}
|
{/* Phase Selection Section */}
|
||||||
{dailyChecklistId && (
|
{!isKandangEmpty && dailyChecklistId && (
|
||||||
<div className='mb-6 pb-6 border-b border-gray-200'>
|
<div className='mb-6 pb-6 border-b border-gray-200'>
|
||||||
{isChecklistStatusDraft && (
|
{isChecklistStatusDraft && (
|
||||||
<div className='flex items-center justify-between mb-3'>
|
<div className='flex items-center justify-between mb-3'>
|
||||||
@@ -1159,7 +1167,9 @@ export function DailyChecklistContent() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ABK Assignment Section */}
|
{/* ABK Assignment Section */}
|
||||||
{dailyChecklistId && selectedPhaseIds.length > 0 && (
|
{!isKandangEmpty &&
|
||||||
|
dailyChecklistId &&
|
||||||
|
selectedPhaseIds.length > 0 && (
|
||||||
<div className='mb-6 pb-6 border-b border-gray-200'>
|
<div className='mb-6 pb-6 border-b border-gray-200'>
|
||||||
{isChecklistStatusDraft && (
|
{isChecklistStatusDraft && (
|
||||||
<div className='flex items-center justify-between mb-3'>
|
<div className='flex items-center justify-between mb-3'>
|
||||||
@@ -1198,12 +1208,16 @@ export function DailyChecklistContent() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className='text-sm text-gray-500'>Belum ada ABK dipilih</p>
|
<p className='text-sm text-gray-500'>
|
||||||
|
Belum ada ABK dipilih
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Activity Checklist Table */}
|
{/* Activity Checklist Table */}
|
||||||
|
{!isKandangEmpty && (
|
||||||
|
<>
|
||||||
{dailyChecklistId &&
|
{dailyChecklistId &&
|
||||||
selectedPhaseIds.length > 0 &&
|
selectedPhaseIds.length > 0 &&
|
||||||
selectedEmployees.length > 0 ? (
|
selectedEmployees.length > 0 ? (
|
||||||
@@ -1235,7 +1249,8 @@ export function DailyChecklistContent() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{Object.keys(groupActivitiesByPhase()).flatMap(
|
{Object.keys(groupActivitiesByPhase()).flatMap(
|
||||||
(phaseId) => {
|
(phaseId) => {
|
||||||
const phaseData = groupActivitiesByPhase()[phaseId];
|
const phaseData =
|
||||||
|
groupActivitiesByPhase()[phaseId];
|
||||||
const { phase, timeGroups } = phaseData;
|
const { phase, timeGroups } = phaseData;
|
||||||
|
|
||||||
const timeTypes = Object.keys(timeGroups).sort(
|
const timeTypes = Object.keys(timeGroups).sort(
|
||||||
@@ -1282,7 +1297,8 @@ export function DailyChecklistContent() {
|
|||||||
// TIME_TYPE sub-headers and activities
|
// TIME_TYPE sub-headers and activities
|
||||||
timeTypes.forEach((timeType) => {
|
timeTypes.forEach((timeType) => {
|
||||||
const activities = timeGroups[timeType];
|
const activities = timeGroups[timeType];
|
||||||
const hasMultipleTimeTypes = timeTypes.length > 1;
|
const hasMultipleTimeTypes =
|
||||||
|
timeTypes.length > 1;
|
||||||
|
|
||||||
// TIME Header (optional, only if phase has multiple time types) - GRAY SOFT
|
// TIME Header (optional, only if phase has multiple time types) - GRAY SOFT
|
||||||
if (hasMultipleTimeTypes) {
|
if (hasMultipleTimeTypes) {
|
||||||
@@ -1369,17 +1385,22 @@ export function DailyChecklistContent() {
|
|||||||
rows={1}
|
rows={1}
|
||||||
placeholder='Catatan (opsional)'
|
placeholder='Catatan (opsional)'
|
||||||
value={
|
value={
|
||||||
taskId && selectedEmployees.length > 0
|
taskId &&
|
||||||
|
selectedEmployees.length > 0
|
||||||
? assignments[taskId]?.[
|
? assignments[taskId]?.[
|
||||||
selectedEmployees[0].id
|
selectedEmployees[0].id
|
||||||
]?.note || ''
|
]?.note || ''
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (selectedEmployees.length > 0) {
|
if (
|
||||||
|
selectedEmployees.length > 0
|
||||||
|
) {
|
||||||
handleNoteChange(
|
handleNoteChange(
|
||||||
String(activity.id),
|
String(activity.id),
|
||||||
String(selectedEmployees[0].id),
|
String(
|
||||||
|
selectedEmployees[0].id
|
||||||
|
),
|
||||||
e.target.value
|
e.target.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1449,8 +1470,11 @@ export function DailyChecklistContent() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{dailyChecklistId &&
|
{!isKandangEmpty &&
|
||||||
|
dailyChecklistId &&
|
||||||
selectedPhaseIds.length > 0 &&
|
selectedPhaseIds.length > 0 &&
|
||||||
selectedEmployees.length > 0 && (
|
selectedEmployees.length > 0 && (
|
||||||
<>
|
<>
|
||||||
@@ -1548,7 +1572,8 @@ export function DailyChecklistContent() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
{dailyChecklistId &&
|
{!isKandangEmpty &&
|
||||||
|
dailyChecklistId &&
|
||||||
selectedPhaseIds.length > 0 &&
|
selectedPhaseIds.length > 0 &&
|
||||||
selectedEmployees.length > 0 &&
|
selectedEmployees.length > 0 &&
|
||||||
isChecklistStatusDraft && (
|
isChecklistStatusDraft && (
|
||||||
|
|||||||
+61
-1
@@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ArrowLeft, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
CheckCircle,
|
||||||
|
XCircle,
|
||||||
|
AlertCircle,
|
||||||
|
Share2,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import * as htmlToImage from 'html-to-image';
|
||||||
import { Card, CardContent } from '@/figma-make/components/base/card';
|
import { Card, CardContent } from '@/figma-make/components/base/card';
|
||||||
import { Button } from '@/figma-make/components/base/button';
|
import { Button } from '@/figma-make/components/base/button';
|
||||||
import { Badge } from '@/figma-make/components/base/badge';
|
import { Badge } from '@/figma-make/components/base/badge';
|
||||||
@@ -137,6 +144,8 @@ export function DetailDailyChecklistContent() {
|
|||||||
const [rejectReason, setRejectReason] = useState('');
|
const [rejectReason, setRejectReason] = useState('');
|
||||||
const [actionLoading, setActionLoading] = useState(false);
|
const [actionLoading, setActionLoading] = useState(false);
|
||||||
|
|
||||||
|
const [isGeneratingImage, setIsGeneratingImage] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checklistId) {
|
if (checklistId) {
|
||||||
fetchChecklistDetail();
|
fetchChecklistDetail();
|
||||||
@@ -547,6 +556,42 @@ export function DetailDailyChecklistContent() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shareHandler = async () => {
|
||||||
|
setIsGeneratingImage(true);
|
||||||
|
|
||||||
|
const htmlBlob = await htmlToImage.toBlob(document.body);
|
||||||
|
const imgFile = new File(
|
||||||
|
[htmlBlob!],
|
||||||
|
`daily-checklist-${header?.date}-${header?.kandang_name}-${header?.category}.png`,
|
||||||
|
{
|
||||||
|
type: 'image/png',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsGeneratingImage(false);
|
||||||
|
|
||||||
|
const shareData = {
|
||||||
|
files: [imgFile],
|
||||||
|
title: `Daily Checklist - ${formatDate(header?.date || '')} - ${header?.kandang_name} - ${header?.category}`,
|
||||||
|
text: `Daily Checklist - ${formatDate(header?.date || '')} - ${header?.kandang_name} - ${header?.category}`,
|
||||||
|
url: window.location.href,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!navigator.canShare(shareData)) {
|
||||||
|
toast.error(
|
||||||
|
'Gagal membagikan checklist, coba dengan perangkat yang berbeda'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigator.share(shareData);
|
||||||
|
toast.success('Checklist berhasil dibagikan');
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Gagal membagikan checklist');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen'>
|
<div className='min-h-screen'>
|
||||||
@@ -584,6 +629,7 @@ export function DetailDailyChecklistContent() {
|
|||||||
<ArrowLeft className='w-4 h-4 mr-1' />
|
<ArrowLeft className='w-4 h-4 mr-1' />
|
||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<h1 className='text-2xl font-semibold text-gray-900'>
|
<h1 className='text-2xl font-semibold text-gray-900'>
|
||||||
Detail Daily Checklist
|
Detail Daily Checklist
|
||||||
@@ -592,6 +638,7 @@ export function DetailDailyChecklistContent() {
|
|||||||
Lihat detail checklist harian
|
Lihat detail checklist harian
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{header.status === 'SUBMITTED' && (
|
{header.status === 'SUBMITTED' && (
|
||||||
<RequirePermission permissions='lti.daily_checklist.create'>
|
<RequirePermission permissions='lti.daily_checklist.create'>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
@@ -615,6 +662,19 @@ export function DetailDailyChecklistContent() {
|
|||||||
</div>
|
</div>
|
||||||
</RequirePermission>
|
</RequirePermission>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
size='sm'
|
||||||
|
onClick={shareHandler}
|
||||||
|
disabled={isGeneratingImage}
|
||||||
|
className='border-gray-200'
|
||||||
|
>
|
||||||
|
<Share2 className='w-4 h-4 mr-1' />
|
||||||
|
{!isGeneratingImage && 'Bagikan'}
|
||||||
|
|
||||||
|
{isGeneratingImage && 'Memuat...'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Header Info Card */}
|
{/* Header Info Card */}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export type UseTableFilterOptions<TExtra extends Record<string, unknown>> = {
|
|||||||
paramMap?: Partial<Record<keyof TableFilterState<TExtra>, string>>;
|
paramMap?: Partial<Record<keyof TableFilterState<TExtra>, string>>;
|
||||||
/** If true, `toSearchParams`/`toQueryString` will omit values equal to defaults */
|
/** If true, `toSearchParams`/`toQueryString` will omit values equal to defaults */
|
||||||
omitDefaultsInUrl?: boolean;
|
omitDefaultsInUrl?: boolean;
|
||||||
|
/** Optional list of state keys that should never be serialized into the URL/query string */
|
||||||
|
excludeKeysFromUrl?: Partial<(keyof TableFilterState<TExtra>)[]>;
|
||||||
|
|
||||||
persist?: boolean;
|
persist?: boolean;
|
||||||
storeName?: string;
|
storeName?: string;
|
||||||
@@ -218,9 +220,12 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const extras = useMemo(() => {
|
const extras = useMemo(() => {
|
||||||
const { page, pageSize, ...rest } = state as TableFilterState<
|
const stateWithExtras = state as TableFilterState<Record<string, unknown>>;
|
||||||
Record<string, unknown>
|
const rest = Object.fromEntries(
|
||||||
>;
|
Object.entries(stateWithExtras).filter(
|
||||||
|
([key]) => key !== 'page' && key !== 'pageSize'
|
||||||
|
)
|
||||||
|
);
|
||||||
return rest as TExtra;
|
return rest as TExtra;
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
@@ -240,8 +245,13 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
|
|||||||
const baseline = options?.omitDefaultsInUrl
|
const baseline = options?.omitDefaultsInUrl
|
||||||
? (defaults as Record<string, unknown>)
|
? (defaults as Record<string, unknown>)
|
||||||
: null;
|
: null;
|
||||||
|
const excludedKeys = new Set<string>(
|
||||||
|
(options?.excludeKeysFromUrl as string[] | undefined) ?? []
|
||||||
|
);
|
||||||
|
|
||||||
for (const key of Object.keys(source)) {
|
for (const key of Object.keys(source)) {
|
||||||
|
if (excludedKeys.has(key)) continue;
|
||||||
|
|
||||||
const value = source[key];
|
const value = source[key];
|
||||||
if (value === undefined || value === null) continue;
|
if (value === undefined || value === null) continue;
|
||||||
|
|
||||||
@@ -260,7 +270,13 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
|
|||||||
if (serialized !== null) params.set(mapped, serialized);
|
if (serialized !== null) params.set(mapped, serialized);
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}, [state, defaults, options?.omitDefaultsInUrl, mapKey]);
|
}, [
|
||||||
|
state,
|
||||||
|
defaults,
|
||||||
|
options?.omitDefaultsInUrl,
|
||||||
|
options?.excludeKeysFromUrl,
|
||||||
|
mapKey,
|
||||||
|
]);
|
||||||
|
|
||||||
/** Build query string (prefixed with '?', or empty string if none) */
|
/** Build query string (prefixed with '?', or empty string if none) */
|
||||||
const toQueryString = useCallback(() => {
|
const toQueryString = useCallback(() => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RequestOptions } from '@/services/http/base';
|
|||||||
import { redirectToSSO } from '@/lib/auth-helper';
|
import { redirectToSSO } from '@/lib/auth-helper';
|
||||||
|
|
||||||
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
|
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
|
||||||
const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 10_000 });
|
const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 30_000 });
|
||||||
|
|
||||||
axiosClient.interceptors.response.use(
|
axiosClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
|
|||||||
Vendored
+6
@@ -148,10 +148,16 @@ export type UpdatePurchaseRequestPayload = CreatePurchaseRequestPayload;
|
|||||||
export type PurchaseFilter = {
|
export type PurchaseFilter = {
|
||||||
poDate: string;
|
poDate: string;
|
||||||
category: string[];
|
category: string[];
|
||||||
|
category_labels?: { label: string; value: number }[];
|
||||||
status: string[];
|
status: string[];
|
||||||
supplier_id?: number;
|
supplier_id?: number;
|
||||||
|
supplier_label?: string;
|
||||||
area_id?: number;
|
area_id?: number;
|
||||||
|
area_label?: string;
|
||||||
location_id?: number;
|
location_id?: number;
|
||||||
|
location_label?: string;
|
||||||
project_flock_id?: number;
|
project_flock_id?: number;
|
||||||
|
project_flock_label?: string;
|
||||||
project_flock_kandang_id?: number;
|
project_flock_kandang_id?: number;
|
||||||
|
project_flock_kandang_label?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user