mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
feat(FE-350): add filtering table
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
||||
npm run format
|
||||
npm run lint
|
||||
npm run build
|
||||
npx tsc --noEmit
|
||||
@@ -11,7 +11,7 @@ const FinanceDetailPage = () => {
|
||||
const financeId = useSearchParams().get('financeId');
|
||||
|
||||
const { data: finance } = useSWR(financeId, () =>
|
||||
FinanceApi.getSingleFetcher(financeId as string)
|
||||
FinanceApi.getSingle(Number(financeId))
|
||||
);
|
||||
|
||||
if (!financeId) {
|
||||
|
||||
@@ -1,30 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import FinanceTable from '@/components/pages/finance/FinanceTable';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { FinanceApi } from '@/services/api/finance';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const Finance = () => {
|
||||
const { data: finances, isLoading: isLoadingFinances } = useSWR(
|
||||
`${FinanceApi.basePath}`,
|
||||
() => FinanceApi.getAllFetcher()
|
||||
);
|
||||
|
||||
if (isLoadingFinances) {
|
||||
return (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className='size-full p-4'>
|
||||
<h1>Finance</h1>
|
||||
<FinanceTable
|
||||
finances={isResponseSuccess(finances) ? finances.data : []}
|
||||
/>
|
||||
<section className='size-full p-6'>
|
||||
<div className='flex flex-row gap-4'></div>
|
||||
<FinanceTable />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,87 +1,197 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import useSWRImmutable from 'swr/immutable';
|
||||
|
||||
import { useAuth } from '@/services/hooks/useAuth';
|
||||
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
|
||||
import { AxiosError } from 'axios';
|
||||
import { redirectToSSO } from '@/lib/auth-helper';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { GetMeResponse } from '@/types/api/api-general';
|
||||
|
||||
// TODO: delete this later, DONT HARDCODE USER DATA
|
||||
const DUMMY_USER = {
|
||||
id: 1,
|
||||
email: 'admin@mbugroup.id',
|
||||
npk: '0001',
|
||||
name: 'Super Admin',
|
||||
image: null,
|
||||
created_at: '2025-09-30T03:24:20.899229Z',
|
||||
updated_at: '2025-09-30T03:24:20.899229Z',
|
||||
roles: [
|
||||
{
|
||||
id: 1,
|
||||
key: 'mbu.super_admin',
|
||||
name: 'MBU Administrator',
|
||||
client: {
|
||||
id: 1,
|
||||
name: 'PT Mitra Berlian Unggas',
|
||||
alias: 'MBU',
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'mbu:purchase:read',
|
||||
action: 'read',
|
||||
client: {
|
||||
id: 1,
|
||||
name: 'PT Mitra Berlian Unggas',
|
||||
alias: 'MBU',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'mbu:purchase:create',
|
||||
action: 'create',
|
||||
client: {
|
||||
id: 1,
|
||||
name: 'PT Mitra Berlian Unggas',
|
||||
alias: 'MBU',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'mbu:purchase:approve',
|
||||
action: 'approve',
|
||||
client: {
|
||||
id: 1,
|
||||
name: 'PT Mitra Berlian Unggas',
|
||||
alias: 'MBU',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
key: 'lti.super_admin',
|
||||
name: 'LTI Administrator',
|
||||
client: {
|
||||
id: 2,
|
||||
name: 'PT Lumbung Telur Indonesia',
|
||||
alias: 'LTI',
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
id: 4,
|
||||
name: 'lti:purchase:read',
|
||||
action: 'read',
|
||||
client: {
|
||||
id: 2,
|
||||
name: 'PT Lumbung Telur Indonesia',
|
||||
alias: 'LTI',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'lti:purchase:create',
|
||||
action: 'create',
|
||||
client: {
|
||||
id: 2,
|
||||
name: 'PT Lumbung Telur Indonesia',
|
||||
alias: 'LTI',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'lti:purchase:approve',
|
||||
action: 'approve',
|
||||
client: {
|
||||
id: 2,
|
||||
name: 'PT Lumbung Telur Indonesia',
|
||||
alias: 'LTI',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
key: 'manbu.super_admin',
|
||||
name: 'MANBU Administrator',
|
||||
client: {
|
||||
id: 3,
|
||||
name: 'PT Mandiri Berlian Unggas',
|
||||
alias: 'MANBU',
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
id: 7,
|
||||
name: 'manbu:purchase:read',
|
||||
action: 'read',
|
||||
client: {
|
||||
id: 3,
|
||||
name: 'PT Mandiri Berlian Unggas',
|
||||
alias: 'MANBU',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'manbu:purchase:create',
|
||||
action: 'create',
|
||||
client: {
|
||||
id: 3,
|
||||
name: 'PT Mandiri Berlian Unggas',
|
||||
alias: 'MANBU',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'manbu:purchase:approve',
|
||||
action: 'approve',
|
||||
client: {
|
||||
id: 3,
|
||||
name: 'PT Mandiri Berlian Unggas',
|
||||
alias: 'MANBU',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
interface RequireAuthProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||
const { user, setUser, setIsLoadingUser } = useAuth();
|
||||
const router = useRouter();
|
||||
const { setUser, setIsLoadingUser } = useAuth();
|
||||
|
||||
const {
|
||||
data: userResponse,
|
||||
isLoading: isLoadingUserResponse,
|
||||
error: userErrorResponse,
|
||||
} = useSWR<
|
||||
GetMeResponse & { ok?: boolean },
|
||||
AxiosError<BaseApiResponse>,
|
||||
SWRHttpKey
|
||||
>('/sso/userinfo', httpClientFetcher, {
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
const { data: userResponse, isLoading: isLoadingUserResponse } =
|
||||
useSWRImmutable<GetMeResponse & { ok?: boolean }, unknown, SWRHttpKey>(
|
||||
'/auth/sso/userinfo',
|
||||
httpClientFetcher,
|
||||
{
|
||||
shouldRetryOnError: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshInterval: 0,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoadingUser(isLoadingUserResponse);
|
||||
}, [isLoadingUserResponse, setIsLoadingUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isResponseSuccess(userResponse)) {
|
||||
setUser(userResponse.data);
|
||||
} else {
|
||||
// router.replace(process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string);
|
||||
// TODO: remove this later, DONT HARDCODE USER DATA
|
||||
setUser(DUMMY_USER);
|
||||
}
|
||||
}, [userResponse, setUser]);
|
||||
}, [userResponse, setIsLoadingUser, setUser]);
|
||||
|
||||
// Explicitly handle 401 redirect from the component level
|
||||
useEffect(() => {
|
||||
if (
|
||||
isResponseError(userResponse) &&
|
||||
userErrorResponse?.response?.status === 401
|
||||
) {
|
||||
// Clear cache to prevent stale data from rendering children
|
||||
// mutate('/sso/userinfo', undefined, { revalidate: false }); // Optional: if using global mutate
|
||||
setUser(undefined);
|
||||
redirectToSSO();
|
||||
}
|
||||
}, [userErrorResponse, setUser, userResponse]);
|
||||
// TODO: uncomment this later
|
||||
// if (isLoadingUserResponse && !userResponse) {
|
||||
// return (
|
||||
// <div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
// <span className='loading loading-spinner loading-xl' />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoadingUser(isLoadingUserResponse);
|
||||
}, [isLoadingUserResponse]);
|
||||
|
||||
if (
|
||||
(isLoadingUserResponse && !userResponse && !userErrorResponse) ||
|
||||
(!userResponse && !userErrorResponse)
|
||||
) {
|
||||
return (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (userErrorResponse) {
|
||||
return (
|
||||
<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>
|
||||
<p className='text-gray-600'>
|
||||
Please try refreshing the page or contact support if the problem
|
||||
persists.
|
||||
</p>
|
||||
<button
|
||||
className='btn btn-primary'
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{isResponseSuccess(userResponse) && user && children}</>;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default RequireAuth;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import Card from '@/components/Card';
|
||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import Table from '@/components/Table';
|
||||
import { formatCurrency } from '@/lib/helper';
|
||||
import { Finance, FinanceReferences } from '@/types/api/finance/finance';
|
||||
import { formatCurrency, formatTitleCase } from '@/lib/helper';
|
||||
import { Finance } from '@/types/api/finance/finance';
|
||||
|
||||
const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
const informasiUmum = [
|
||||
{
|
||||
label: 'ID',
|
||||
value: finance.id,
|
||||
value: finance.payment_code,
|
||||
},
|
||||
{
|
||||
label: 'Jenis Transaksi',
|
||||
@@ -16,41 +17,47 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
},
|
||||
{
|
||||
label: 'Pihak',
|
||||
value: finance.transaction_owner.name,
|
||||
value: finance.party.name,
|
||||
},
|
||||
{
|
||||
label: 'Tanggal',
|
||||
value: finance.transaction_date,
|
||||
value: finance.payment_date,
|
||||
},
|
||||
{
|
||||
label: 'Metode Pembayaran',
|
||||
value: finance.payment_method,
|
||||
},
|
||||
{
|
||||
label: 'Catatan',
|
||||
value: finance.notes || '-',
|
||||
},
|
||||
];
|
||||
const informasiTransfer = [
|
||||
{
|
||||
label: 'No. Referensi',
|
||||
value: finance.references_number,
|
||||
value: finance.reference_number,
|
||||
},
|
||||
{
|
||||
label: 'Nomor Rekening',
|
||||
value: `${finance.bank_account.alias} - ${finance.bank_account.account_number} - ${finance.bank_account.owner}`,
|
||||
value: `${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`,
|
||||
},
|
||||
{
|
||||
label: 'Rekening Customer',
|
||||
value: finance.transaction_account_number,
|
||||
label: `Rekening ${formatTitleCase(finance.party.type)}`,
|
||||
value: finance.party.account_number,
|
||||
},
|
||||
{
|
||||
label: 'Nominal',
|
||||
value: formatCurrency(finance.transaction_amount),
|
||||
value: formatCurrency(finance.expense_amount),
|
||||
},
|
||||
{
|
||||
label: 'Sisa',
|
||||
value: formatCurrency(finance.balance_amount),
|
||||
value: formatCurrency(finance.income_amount),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='flex flex-col gap-6 p-6'>
|
||||
<FormHeader title='' backUrl='/finance' />
|
||||
|
||||
<Card
|
||||
title='Detail Keuangan'
|
||||
className={{
|
||||
@@ -100,41 +107,6 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='flex flex-row gap-4'>
|
||||
<DebouncedTextInput
|
||||
className={{
|
||||
wrapper: 'max-w-1/4 ml-auto',
|
||||
}}
|
||||
name='cari'
|
||||
placeholder='Cari'
|
||||
/>
|
||||
</div>
|
||||
<Table<FinanceReferences>
|
||||
data={finance.references}
|
||||
columns={[
|
||||
{
|
||||
header: 'No.',
|
||||
id: 'index',
|
||||
accessorFn: (row, index) => index + 1,
|
||||
},
|
||||
{
|
||||
header: 'No. Referensi',
|
||||
id: 'references_number',
|
||||
accessorKey: 'references_number',
|
||||
},
|
||||
{
|
||||
header: 'Nominal',
|
||||
id: 'nominal',
|
||||
accessorFn: (row) =>
|
||||
formatCurrency(Number(row.total_allocation)),
|
||||
},
|
||||
]}
|
||||
className={{
|
||||
containerClassName: 'mb-6',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,39 +1,208 @@
|
||||
import { ChangeEventHandler, useMemo, useState } from 'react';
|
||||
import { Row } from '@tanstack/react-table';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import Dropdown from '@/components/dropdown/Dropdown';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import Table from '@/components/Table';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { Finance } from '@/types/api/finance/finance';
|
||||
import { Row } from '@tanstack/react-table';
|
||||
import { useMemo } from 'react';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { FinanceApi } from '@/services/api/finance';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
|
||||
import { Bank } from '@/types/api/master-data/bank';
|
||||
|
||||
const FinanceTable = () => {
|
||||
const {
|
||||
state: tableFilterState,
|
||||
updateFilter,
|
||||
setPage,
|
||||
setPageSize,
|
||||
toQueryString: getTableFilterQueryString,
|
||||
} = useTableFilter({
|
||||
initial: {
|
||||
search: '',
|
||||
transactionType: '',
|
||||
bankId: '',
|
||||
partyType: '',
|
||||
sortBy: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
},
|
||||
paramMap: {
|
||||
page: 'page',
|
||||
pageSize: 'limit',
|
||||
transactionType: 'transaction_type',
|
||||
bankId: 'bank_id',
|
||||
partyType: 'party_type',
|
||||
sortBy: 'sort_date',
|
||||
startDate: 'start_date',
|
||||
endDate: 'end_date',
|
||||
},
|
||||
});
|
||||
|
||||
// ===== State =====
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [pendingFilters, setPendingFilters] = useState({
|
||||
search: '',
|
||||
transactionType: '',
|
||||
bankId: '',
|
||||
partyType: '',
|
||||
sortBy: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
});
|
||||
const [selectedTransactionType, setSelectedTransactionType] =
|
||||
useState<OptionType | null>(null);
|
||||
const [selectedBank, setSelectedBank] = useState<OptionType | null>(null);
|
||||
const [selectedPartyType, setSelectedPartyType] = useState<OptionType | null>(
|
||||
null
|
||||
);
|
||||
const [selectedSortBy, setSelectedSortBy] = useState<OptionType | null>(null);
|
||||
|
||||
// ===== Fetch Data =====
|
||||
const {
|
||||
data: finances,
|
||||
isLoading,
|
||||
mutate: refreshFinances,
|
||||
} = useSWR(
|
||||
`${FinanceApi.basePath}${getTableFilterQueryString()}`,
|
||||
FinanceApi.getAllFetcher
|
||||
);
|
||||
|
||||
// ===== Options =====
|
||||
const transactionTypeOptions = useMemo(() => {
|
||||
return [
|
||||
{ label: 'Transfer', value: 'TRANSFER' },
|
||||
{ label: 'Cash', value: 'CASH' },
|
||||
{ label: 'Card', value: 'CARD' },
|
||||
{ label: 'Cheque', value: 'CHEQUE' },
|
||||
{ label: 'Saldo', value: 'SALDO' },
|
||||
];
|
||||
}, []);
|
||||
const partyTypeOptions = useMemo(() => {
|
||||
return [
|
||||
{ label: 'Customer', value: 'CUSTOMER' },
|
||||
{ label: 'Supplier', value: 'SUPPLIER' },
|
||||
];
|
||||
}, []);
|
||||
const sortByOptions = useMemo(() => {
|
||||
return [
|
||||
{ label: 'Tanggal Pembayaran', value: 'payment_date' },
|
||||
{ label: 'Tanggal Dibuat', value: 'created_at' },
|
||||
];
|
||||
}, []);
|
||||
const { options: bankOptions, rawData: bankRawData } = useSelect<Bank>(
|
||||
BankApi.basePath,
|
||||
'id',
|
||||
'alias',
|
||||
'',
|
||||
{
|
||||
limit: 'limit',
|
||||
}
|
||||
);
|
||||
|
||||
// ===== Handler =====
|
||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setPendingFilters((prev) => ({ ...prev, search: e.target.value }));
|
||||
};
|
||||
const transactionTypeChangeHandler = (
|
||||
val: OptionType | OptionType[] | null
|
||||
) => {
|
||||
setSelectedTransactionType(val as OptionType);
|
||||
setPendingFilters((prev) => ({
|
||||
...prev,
|
||||
transactionType: val ? ((val as OptionType).value as string) : '',
|
||||
}));
|
||||
};
|
||||
const bankChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedBank(val as OptionType);
|
||||
setPendingFilters((prev) => ({
|
||||
...prev,
|
||||
bankId: val ? ((val as OptionType).value as string) : '',
|
||||
}));
|
||||
};
|
||||
const partyTypeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedPartyType(val as OptionType);
|
||||
setPendingFilters((prev) => ({
|
||||
...prev,
|
||||
partyType: val ? ((val as OptionType).value as string) : '',
|
||||
}));
|
||||
};
|
||||
const sortByChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
setSelectedSortBy(val as OptionType);
|
||||
setPendingFilters((prev) => ({
|
||||
...prev,
|
||||
sortBy: val ? ((val as OptionType).value as string) : '',
|
||||
}));
|
||||
};
|
||||
const startDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setPendingFilters((prev) => ({ ...prev, startDate: e.target.value }));
|
||||
};
|
||||
const endDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setPendingFilters((prev) => ({ ...prev, endDate: e.target.value }));
|
||||
};
|
||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const newVal = val as OptionType;
|
||||
setPageSize(newVal.value as number);
|
||||
};
|
||||
const submitFilterHandler = () => {
|
||||
updateFilter('search', pendingFilters.search);
|
||||
updateFilter('transactionType', pendingFilters.transactionType);
|
||||
updateFilter('bankId', pendingFilters.bankId);
|
||||
updateFilter('partyType', pendingFilters.partyType);
|
||||
updateFilter('sortBy', pendingFilters.sortBy);
|
||||
updateFilter('startDate', pendingFilters.startDate);
|
||||
updateFilter('endDate', pendingFilters.endDate);
|
||||
};
|
||||
const resetFilterHandler = () => {
|
||||
setSelectedTransactionType(null);
|
||||
setSelectedBank(null);
|
||||
setSelectedPartyType(null);
|
||||
setSelectedSortBy(null);
|
||||
|
||||
const emptyFilters = {
|
||||
search: '',
|
||||
transactionType: '',
|
||||
bankId: '',
|
||||
partyType: '',
|
||||
sortBy: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
};
|
||||
setPendingFilters(emptyFilters);
|
||||
|
||||
updateFilter('search', '');
|
||||
updateFilter('transactionType', '');
|
||||
updateFilter('bankId', '');
|
||||
updateFilter('partyType', '');
|
||||
updateFilter('sortBy', '');
|
||||
updateFilter('startDate', '');
|
||||
updateFilter('endDate', '');
|
||||
};
|
||||
|
||||
const FinanceTable = ({ finances }: { finances: Finance[] }) => {
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
header: 'ID',
|
||||
accessorKey: 'id',
|
||||
},
|
||||
{
|
||||
header: 'Alokasi',
|
||||
accessorFn: (finance: Finance) => finance.references.length,
|
||||
cell: ({ row }: { row: Row<Finance> }) => (
|
||||
<Tooltip
|
||||
content={row.original.references
|
||||
.map((ref) => ref.references_number)
|
||||
.join(', ')}
|
||||
>
|
||||
<span className='text-primary'>
|
||||
{row.original.references.length}
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
accessorKey: 'payment_code',
|
||||
},
|
||||
{
|
||||
header: 'References Number',
|
||||
accessorKey: 'references_number',
|
||||
accessorKey: 'reference_number',
|
||||
},
|
||||
{
|
||||
header: 'Jenis Transaksi',
|
||||
@@ -41,12 +210,12 @@ const FinanceTable = ({ finances }: { finances: Finance[] }) => {
|
||||
},
|
||||
{
|
||||
header: 'Pihak',
|
||||
accessorFn: (finance: Finance) => finance.transaction_owner.name,
|
||||
accessorFn: (finance: Finance) => finance.party.name,
|
||||
},
|
||||
{
|
||||
header: 'Tanggal',
|
||||
accessorFn: (finance: Finance) =>
|
||||
formatDate(finance.transaction_date, 'DD MMM YYYY'),
|
||||
formatDate(finance.payment_date, 'DD MMM YYYY'),
|
||||
},
|
||||
{
|
||||
header: 'Metode Pembayaran',
|
||||
@@ -55,17 +224,16 @@ const FinanceTable = ({ finances }: { finances: Finance[] }) => {
|
||||
{
|
||||
header: 'Bank',
|
||||
accessorFn: (finance: Finance) =>
|
||||
`${finance.bank_account.alias} - ${finance.bank_account.account_number} - ${finance.bank_account.owner}`,
|
||||
`${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`,
|
||||
},
|
||||
{
|
||||
header: 'Pengeluaran (Rp)',
|
||||
accessorFn: (finance: Finance) =>
|
||||
formatCurrency(finance.balance_amount),
|
||||
formatCurrency(finance.expense_amount),
|
||||
},
|
||||
{
|
||||
header: 'Pemasukan (Rp)',
|
||||
accessorFn: (finance: Finance) =>
|
||||
formatCurrency(finance.transaction_amount),
|
||||
accessorFn: (finance: Finance) => formatCurrency(finance.income_amount),
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
@@ -88,8 +256,113 @@ const FinanceTable = ({ finances }: { finances: Finance[] }) => {
|
||||
];
|
||||
}, []);
|
||||
return (
|
||||
<section className='size-full p-4'>
|
||||
<Table<Finance> data={finances} columns={columns} />
|
||||
<section className='size-full p-6 flex flex-col gap-6'>
|
||||
<div className='flex justify-end gap-2'>
|
||||
<Button color='warning' className='min-w-24'>
|
||||
Injection Saldo Bank
|
||||
</Button>
|
||||
<Button color='info' className='text-white min-w-24'>
|
||||
Saldo Awal
|
||||
</Button>
|
||||
<Button color='primary' className='min-w-24'>
|
||||
Tambah
|
||||
</Button>
|
||||
</div>
|
||||
<Card
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
}}
|
||||
footer={
|
||||
<div className='flex justify-end gap-2'>
|
||||
<Button
|
||||
color='warning'
|
||||
className='min-w-24'
|
||||
onClick={resetFilterHandler}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
className='min-w-24'
|
||||
onClick={submitFilterHandler}
|
||||
>
|
||||
Cari
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='grid grid-cols-4 gap-6'>
|
||||
<SelectInput
|
||||
options={transactionTypeOptions}
|
||||
label='Jenis Transaksi'
|
||||
value={selectedTransactionType}
|
||||
onChange={transactionTypeChangeHandler}
|
||||
isClearable
|
||||
/>
|
||||
{isResponseSuccess(bankRawData) && (
|
||||
<SelectInput
|
||||
options={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'
|
||||
value={selectedBank}
|
||||
onChange={bankChangeHandler}
|
||||
isClearable
|
||||
/>
|
||||
)}
|
||||
<SelectInput
|
||||
options={partyTypeOptions}
|
||||
label='Pihak'
|
||||
value={selectedPartyType}
|
||||
onChange={partyTypeChangeHandler}
|
||||
isClearable
|
||||
/>
|
||||
<DebouncedTextInput
|
||||
name='search'
|
||||
label='Cari'
|
||||
placeholder='Cari'
|
||||
value={pendingFilters.search}
|
||||
onChange={searchChangeHandler}
|
||||
/>
|
||||
<SelectInput
|
||||
options={sortByOptions}
|
||||
label='Urutkan Berdasarkan'
|
||||
value={selectedSortBy}
|
||||
onChange={sortByChangeHandler}
|
||||
isClearable
|
||||
/>
|
||||
<DateInput
|
||||
name='startDate'
|
||||
label='Periode Tanggal (Mulai)'
|
||||
value={pendingFilters.startDate}
|
||||
onChange={startDateChangeHandler}
|
||||
/>
|
||||
<DateInput
|
||||
name='endDate'
|
||||
label='Periode Tanggal (Akhir)'
|
||||
value={pendingFilters.endDate}
|
||||
onChange={endDateChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Table<Finance>
|
||||
data={isResponseSuccess(finances) ? finances.data : []}
|
||||
columns={columns}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
page={tableFilterState.page}
|
||||
onPageChange={setPage}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
+404
-2636
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,11 @@
|
||||
/**
|
||||
* Dummy data for Finance[]
|
||||
* Generated from: finance.json
|
||||
* Generated from: finance_payments.json
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually.
|
||||
*/
|
||||
|
||||
import {
|
||||
FinanceBankAccount,
|
||||
FinanceTransactionOwner,
|
||||
FinanceReferences,
|
||||
Finance,
|
||||
} from '../../types/api/finance/finance';
|
||||
import { FinanceBank, Finance } from '../../types/api/finance/finance';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
import dummyData from './finance.dummy.json';
|
||||
|
||||
@@ -18,9 +13,7 @@ import dummyData from './finance.dummy.json';
|
||||
* Get dummy Finance[] data
|
||||
* @returns Promise with BaseApiResponse containing Finance[]
|
||||
*/
|
||||
export async function getAllDummyFinance(): Promise<
|
||||
BaseApiResponse<Finance[]>
|
||||
> {
|
||||
export async function getAllFetcher(): Promise<BaseApiResponse<Finance[]>> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
@@ -33,19 +26,17 @@ export async function getAllDummyFinance(): Promise<
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSingleDummyFinance(
|
||||
id: string
|
||||
export async function getFetcher(
|
||||
id: number
|
||||
): Promise<BaseApiResponse<Finance>> {
|
||||
console.log(dummyData as unknown as Finance[]);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const data = dummyData.find((item) => item.id === id);
|
||||
resolve({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: 'Data retrieved successfully',
|
||||
data: (dummyData as unknown as Finance[]).find(
|
||||
(finance) => finance.id === id
|
||||
) as Finance,
|
||||
data: data as unknown as Finance,
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
@@ -4,10 +4,7 @@ import { BaseApiResponse } from '@/types/api/api-general';
|
||||
import { httpClient } from '@/services/http/client';
|
||||
import { Finance } from '@/types/api/finance/finance';
|
||||
// DUMMY_START
|
||||
import {
|
||||
getAllDummyFinance,
|
||||
getSingleDummyFinance,
|
||||
} from '@/dummy/finance/finance.dummy';
|
||||
import { getAllFetcher, getFetcher } from '@/dummy/finance/finance.dummy';
|
||||
// DUMMY_END
|
||||
|
||||
export class FinanceApiService extends BaseApiService<
|
||||
@@ -21,7 +18,7 @@ export class FinanceApiService extends BaseApiService<
|
||||
|
||||
async getAllFetcher(): Promise<BaseApiResponse<Finance[]>> {
|
||||
// DUMMY_START
|
||||
return await getAllDummyFinance();
|
||||
return await getAllFetcher();
|
||||
// DUMMY_END
|
||||
|
||||
// LIVE_START
|
||||
@@ -37,10 +34,9 @@ export class FinanceApiService extends BaseApiService<
|
||||
// LIVE_END
|
||||
}
|
||||
|
||||
async getSingleFetcher(id: string): Promise<BaseApiResponse<Finance>> {
|
||||
async getSingle(id: number): Promise<BaseApiResponse<Finance>> {
|
||||
// DUMMY_START
|
||||
console.log(id);
|
||||
return await getSingleDummyFinance(id);
|
||||
return await getFetcher(id);
|
||||
// DUMMY_END
|
||||
|
||||
// LIVE_START
|
||||
|
||||
Vendored
+19
-21
@@ -1,31 +1,29 @@
|
||||
export interface Finance {
|
||||
id: string;
|
||||
references_number: string;
|
||||
bank_account: FinanceBankAccount;
|
||||
id: number;
|
||||
payment_code: string;
|
||||
reference_number: string;
|
||||
transaction_type: string;
|
||||
transaction_owner: FinanceTransactionOwner;
|
||||
transaction_account_number: string;
|
||||
transaction_date: string;
|
||||
party: FinanceParty;
|
||||
payment_date: string;
|
||||
created_at: string;
|
||||
payment_method: string;
|
||||
transaction_amount: number;
|
||||
balance_amount: number;
|
||||
bank: FinanceBank;
|
||||
expense_amount: number;
|
||||
income_amount: number;
|
||||
nominal: number;
|
||||
notes: string;
|
||||
references: FinanceReferences[];
|
||||
}
|
||||
|
||||
export interface FinanceReferences {
|
||||
references_number: string;
|
||||
total_allocation: number;
|
||||
}
|
||||
|
||||
export interface FinanceTransactionOwner {
|
||||
export interface FinanceParty {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FinanceBankAccount {
|
||||
alias: string;
|
||||
name: string;
|
||||
type: string;
|
||||
account_number: string;
|
||||
}
|
||||
export interface FinanceBank {
|
||||
id: number;
|
||||
name: string;
|
||||
alias: string;
|
||||
owner: string;
|
||||
account_number: string;
|
||||
owner: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user