revamp template
This commit is contained in:
@ -0,0 +1,74 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { DateRange } from 'react-day-picker';
|
||||
import { format } from 'date-fns';
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import { cn } from '@/lib/utils';
|
||||
import moment from 'moment';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface DateRangePickerProps {
|
||||
date: DateRange | undefined;
|
||||
setDate: (date: DateRange | undefined) => void;
|
||||
interval: 'day' | 'week' | 'month';
|
||||
}
|
||||
|
||||
function getDateRangeLength(startDate: Date, endDate: Date) {
|
||||
const start = moment(startDate);
|
||||
const end = moment(endDate);
|
||||
|
||||
return end.diff(start, 'days') + 1;
|
||||
}
|
||||
|
||||
const DateRangePicker = ({ date, setDate, interval }: DateRangePickerProps) => {
|
||||
const handleSelectDate = useCallback(
|
||||
(date: DateRange | undefined) => {
|
||||
if (date && date.from && date.to) {
|
||||
const dateRange = getDateRangeLength(date.from, date.to);
|
||||
setDate(date);
|
||||
} else {
|
||||
setDate(date);
|
||||
}
|
||||
},
|
||||
[interval, setDate]
|
||||
);
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
id="date"
|
||||
className={cn(
|
||||
'btn btn-sm btn-light data-[state=open]:bg-light-active',
|
||||
!date && 'text-gray-400'
|
||||
)}
|
||||
>
|
||||
<KeenIcon icon="calendar" className="me-0.5" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, 'LLL dd, y')} - {format(date.to, 'LLL dd, y')}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, 'LLL dd, y')
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date range</span>
|
||||
)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export { DateRangePicker };
|
||||
333
src/pages/pengajuan_kredit/summary/blocks/DetailDialog.tsx
Normal file
333
src/pages/pengajuan_kredit/summary/blocks/DetailDialog.tsx
Normal file
@ -0,0 +1,333 @@
|
||||
import { useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { fShortenNumber, fCurrency } from '@/utils/FormatNumber';
|
||||
import { formatDate } from 'date-fns';
|
||||
import { DataGrid, DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import axios from 'axios';
|
||||
import { apiConfig } from '@/config/api.config';
|
||||
import moment from 'moment';
|
||||
const API_URL = apiConfig.service_bank;
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog';
|
||||
|
||||
interface IModalProps {
|
||||
open: boolean;
|
||||
title: string;
|
||||
desc: string;
|
||||
props: { start_date: string; end_date: string; filter: { [key: string]: any } };
|
||||
onOpenChange: () => void;
|
||||
}
|
||||
const defaultSorting = [{ id: 'id', desc: false }];
|
||||
|
||||
const DetailDialog = ({ open, title, desc, props, onOpenChange }: IModalProps) => {
|
||||
const navBar = useRef<any | null>(null);
|
||||
const parentRef = useRef<any | null>(null);
|
||||
|
||||
// console.log('open, title, desc, props, onOpenChange :', open, title, desc, props, onOpenChange);
|
||||
|
||||
const fecthData = async (page: number, limit: number, sorting: any, filter: any) => {
|
||||
sorting = sorting.length == 0 ? [{ id: 'id', desc: false }] : sorting;
|
||||
filter = filter.length == 0 ? [] : filter[0].value;
|
||||
const startDate = props.start_date;
|
||||
const endDate = props.end_date;
|
||||
|
||||
delete props.filter?.start_date;
|
||||
delete props.filter?.end_date;
|
||||
delete props.filter['ca.open_date'];
|
||||
filter = {
|
||||
...props.filter,
|
||||
open_date_from: startDate,
|
||||
open_date_to: endDate
|
||||
};
|
||||
const response = await axios.get(`${API_URL}/bank/dpk/list/`, {
|
||||
params: {
|
||||
filter: JSON.stringify(filter),
|
||||
limit: limit,
|
||||
page: page + 1,
|
||||
with_deleted: false,
|
||||
order_field: sorting[0].id,
|
||||
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC'
|
||||
}
|
||||
});
|
||||
return { data: response.data.data.list, totalCount: response.data.data.total_count };
|
||||
};
|
||||
|
||||
const columns = useMemo<ColumnDef<any>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row) => row.cif_number,
|
||||
id: 'cif_number',
|
||||
header: ({ column }) => <DataGridColumnHeader title="CIF Number" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.name,
|
||||
id: 'name',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.account_number,
|
||||
id: 'account_number',
|
||||
header: ({ column }) => <DataGridColumnHeader title="No. Rekening" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.branch.code,
|
||||
id: 'branch.code',
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Kode Cabang Rekening" column={column} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.open_date).format('YYYY-MM-DD'),
|
||||
id: 'open_date',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Tanggal Buka" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.product.code,
|
||||
id: 'product.code',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Product Type" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.product.name,
|
||||
id: 'product.name',
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Product Description" column={column} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => fCurrency(row.current_balance),
|
||||
id: 'current_balance',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Current Balance" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.country_code,
|
||||
id: 'country_code',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Negara" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.country_name,
|
||||
id: 'country_name',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Warganegara" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.exposed_person_flag,
|
||||
id: 'exposed_person_flag',
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Exposed Person Flag" column={column} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.income_tier.name,
|
||||
id: 'incomde_tier.code',
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Penghasilan Per Bulan (tiering)" column={column} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.cif_type,
|
||||
id: 'cif_type',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Tipe CIF" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.status,
|
||||
id: 'status',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Status Rekening" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.gender,
|
||||
id: 'gender',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Jenis Kelamin" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => (row.birth_date ? moment(row.birth_date).format('YYYY-MM-DD') : ''),
|
||||
id: 'birth_date',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Tanggal Lahir" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.class_economi.name,
|
||||
id: 'class.economi.id',
|
||||
header: ({ column }) => (
|
||||
<DataGridColumnHeader title="Sub Klasifikasi Ekonomi" column={column} />
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.district.name,
|
||||
id: 'class.district.id',
|
||||
header: ({ column }) => <DataGridColumnHeader title="District" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.munisipiu.name,
|
||||
id: 'class.munisipiu.id',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Munisipiu" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.administrativu.name,
|
||||
id: 'class.administrativu.id',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Administrativu" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.suco.name,
|
||||
id: 'class.suco.id',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Suco" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.aldeia.name,
|
||||
id: 'class.aldeia.id',
|
||||
header: ({ column }) => <DataGridColumnHeader title="Aldeia" column={column} />,
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: {
|
||||
headerClassName: 'w-[350px]'
|
||||
}
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="container-fixed max-w-[99%] flex flex-col p-10 overflow-hidden [&>button]:hidden">
|
||||
<DialogHeader className="p-0 border-0">
|
||||
<DialogTitle></DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
<div className="flex items-center justify-between flex-wrap grow gap-5 pb-7.5">
|
||||
<div className="flex flex-col justify-center gap-2">
|
||||
<h1 className="text-xl font-semibold leading-none text-gray-900">{title}</h1>
|
||||
<div className="flex items-center gap-2 text-sm font-normal text-gray-700">
|
||||
{desc}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-sm btn-light" onClick={onOpenChange}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<DialogBody className="scrollable-y py-0 mb-5 ps-0 pe-3 -me-7" ref={parentRef}>
|
||||
<div className="grid gap-5 lg:gap-7.5">
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
rowSelection={true}
|
||||
pagination={{ size: 10 }}
|
||||
sorting={[{ id: 'id', desc: false }]}
|
||||
serverSide={true}
|
||||
layout={{ card: true }}
|
||||
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
|
||||
fecthData(pageIndex, pageSize, sorting, columnFilters)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export { DetailDialog };
|
||||
133
src/pages/pengajuan_kredit/summary/blocks/List.tsx
Normal file
133
src/pages/pengajuan_kredit/summary/blocks/List.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
import { DataGrid, DataGridColumnHeader } from '@/components';
|
||||
import { fCurrency, fPercent } from '@/utils/FormatNumber';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { format } from 'date-fns';
|
||||
interface BgSummaryListInterface {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
toolbar: ReactNode;
|
||||
interval: string;
|
||||
count: string;
|
||||
openDetail: (title: string, props: {}) => void;
|
||||
}
|
||||
|
||||
import { useFetchCreditSummaryData } from '../hooks';
|
||||
import moment from 'moment';
|
||||
import { snakeToCamelCase, snakeToTitleCase } from '@/utils';
|
||||
|
||||
const List = ({
|
||||
start_date,
|
||||
end_date,
|
||||
interval,
|
||||
count,
|
||||
toolbar,
|
||||
openDetail
|
||||
}: BgSummaryListInterface) => {
|
||||
const { data, isLoading, error } = useFetchCreditSummaryData(
|
||||
start_date,
|
||||
end_date,
|
||||
interval,
|
||||
count
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] = useState({
|
||||
_filter: false
|
||||
});
|
||||
|
||||
const columns = useMemo<ColumnDef<any>[]>(() => {
|
||||
if (!data || data.length === 0) return [];
|
||||
const dateKeys = Object.keys(data[0])
|
||||
.filter((key) => key.match(/^\d{4}-\d{2}-\d{2}/))
|
||||
.map((key) => key.split('_')[0])
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
const baseColumns = [
|
||||
{
|
||||
accessorFn: (row: any) => row['label'],
|
||||
id: 'label',
|
||||
header: ({ column }: any) => (
|
||||
<DataGridColumnHeader
|
||||
title="Pengajuan Kredit"
|
||||
className="font-semibold text-gray-900"
|
||||
column={column}
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
cell: ({ row }: any) => {
|
||||
const _className =
|
||||
row.original.type === 'type'
|
||||
? 'ms-0 font-semibold text-gray-700'
|
||||
: row.original.type === 'type'
|
||||
? 'ms-2 text-gray-900'
|
||||
: 'ms-4 text-gray-700';
|
||||
return <p className={_className}>{row.original['label']}</p>;
|
||||
},
|
||||
meta: {
|
||||
headerClassName: 'w-auto'
|
||||
}
|
||||
}
|
||||
];
|
||||
// Create dynamic groups for multi-level headers
|
||||
const dateColumns = dateKeys.map((date) => ({
|
||||
id: date,
|
||||
header: (() => {
|
||||
if (interval === 'month') {
|
||||
return format(new Date(date), 'MMM yyyy');
|
||||
} else if (interval === 'week') {
|
||||
const momentDate = moment(date);
|
||||
const startOfMonth = momentDate.clone().startOf('month');
|
||||
const weekNumber = momentDate.isoWeek() - startOfMonth.isoWeek() + 1;
|
||||
return `Week ${weekNumber}, \n ${momentDate.format('MMM yyyy')}`;
|
||||
} else if (interval === 'day') {
|
||||
return format(new Date(date), 'MMM dd, yyyy');
|
||||
}
|
||||
})(),
|
||||
columns: ['total'].map((metric) => ({
|
||||
accessorFn: (row: any) => row[`${date}_${metric}`],
|
||||
id: `${date}_${metric}`,
|
||||
header: snakeToTitleCase(metric),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
cell: ({ row }: { row: { original: Record<string, any> } }) => {
|
||||
const __type = row.original.type;
|
||||
const _className =
|
||||
__type === 'type'
|
||||
? 'ms-0 font-semibold text-gray-900'
|
||||
: __type === 'type'
|
||||
? 'ms-2 text-gray-900'
|
||||
: 'ms-6 text-gray-700';
|
||||
|
||||
let value = row.original[`${date}_${metric}`] || '0';
|
||||
if (count === 'sum') {
|
||||
return <span className={_className}>{fCurrency(value)}</span>;
|
||||
} else {
|
||||
return <span className={_className}>{value}</span>;
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
headerClassName: 'text-center text-gray-900',
|
||||
cellClassName: 'text-end'
|
||||
}
|
||||
})),
|
||||
meta: {
|
||||
headerClassName: 'text-center'
|
||||
}
|
||||
}));
|
||||
|
||||
return [...baseColumns, ...dateColumns];
|
||||
}, [data, openDetail, start_date, end_date]);
|
||||
|
||||
return (
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
data={data}
|
||||
pagination={{ size: 100 }}
|
||||
sorting={[{ id: 'label', desc: false }]}
|
||||
layout={{ card: true }}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { List };
|
||||
15
src/pages/pengajuan_kredit/summary/blocks/ListToolBar.tsx
Normal file
15
src/pages/pengajuan_kredit/summary/blocks/ListToolBar.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { type ReactNode } from 'react';
|
||||
export interface DpkSummaryListToolBarInterface {
|
||||
children?: ReactNode;
|
||||
}
|
||||
const ListToolBar = ({ children }: DpkSummaryListToolBarInterface) => {
|
||||
return (
|
||||
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
|
||||
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
|
||||
<div className="flex justify-between w-full items-center">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ListToolBar };
|
||||
4
src/pages/pengajuan_kredit/summary/blocks/index.ts
Normal file
4
src/pages/pengajuan_kredit/summary/blocks/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './DateRangePicker';
|
||||
export * from './List';
|
||||
export * from './ListToolBar';
|
||||
export * from './DetailDialog';
|
||||
Reference in New Issue
Block a user