revamp template

This commit is contained in:
fro1991
2025-02-01 16:44:51 +07:00
parent c432df568a
commit 276a289580
1212 changed files with 112762 additions and 0 deletions

View File

@ -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 };

View 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 };

View 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 };

View 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 };

View File

@ -0,0 +1,4 @@
export * from './DateRangePicker';
export * from './List';
export * from './ListToolBar';
export * from './DetailDialog';