You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
14 KiB
388 lines
14 KiB
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' |
|
import { Col, Row, Table } from 'reactstrap' |
|
import { format_angka } from '../util' |
|
import collect from 'collect.js' |
|
import jquery from 'jquery' |
|
import { Skeleton } from 'primereact/skeleton' |
|
|
|
import { Sidebar } from 'primereact/sidebar' |
|
import '/node_modules/primeflex/primeflex.css' |
|
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table' |
|
import { Text } from '@mantine/core' |
|
|
|
import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query' |
|
import dayjs from 'dayjs' |
|
var relativeTime = require('dayjs/plugin/relativeTime') |
|
var customParseFormat = require('dayjs/plugin/customParseFormat') |
|
const fetchSize = 101 |
|
|
|
const Sof = ({ dataSend }) => { |
|
const base_url = '/engineN/' |
|
|
|
const [data, setData] = useState([]) |
|
const [total, setTotal] = useState({ totalC: 0, totalP1: 0, totalP2: 0 }) |
|
const [loading, setLoading] = useState(false) |
|
|
|
const [visibleSidebar, setVisibleSidebar] = useState(false) |
|
const [query, setQuery] = useState(null) |
|
const [tahunBulan, setTahunBulan] = useState(null) |
|
|
|
const currentMonth = '<?=currentMonth()?>' |
|
const currentYear = '<?=currentYear()?>' |
|
|
|
useEffect(() => { |
|
setLoading(true) |
|
jquery.get({ |
|
url: base_url + 'kewilayahan/kytp/sebaranSof', |
|
dataType: 'json', |
|
type: 'POST', |
|
data: { |
|
...dataSend, |
|
tahun: currentYear, |
|
bulan: currentMonth |
|
}, |
|
success: (data) => { |
|
setData(data.data) |
|
setTotal({ |
|
totalC: collect(data.data).sum('JML_C'), |
|
totalP1: collect(data.data).sum('JML_P1'), |
|
totalP2: collect(data.data).sum('JML_P2') |
|
}) |
|
setLoading(false) |
|
} |
|
}) |
|
}, [dataSend]) |
|
|
|
const angkaOnClick = (key, tahunBulan) => { |
|
setQuery(key) |
|
setTahunBulan(tahunBulan) |
|
setVisibleSidebar(true) |
|
} |
|
|
|
const TableDetailGraph = ({ dataSend, query, tahunBulan }) => { |
|
const tableContainerRef = useRef(null) |
|
const rowVirtualizerInstanceRef = useRef(null) |
|
const [columnFilters, setColumnFilters] = useState([]) |
|
const [globalFilter, setGlobalFilter] = useState() |
|
const [sorting, setSorting] = useState([]) |
|
const base_url = location.protocol + '//' + location.hostname + '/engineN/' |
|
const { data, fetchNextPage, isError, isFetching, isLoading } = useInfiniteQuery({ |
|
queryKey: ['table-data', columnFilters, globalFilter, sorting], |
|
|
|
queryFn: async ({ pageParam = 0 }) => { |
|
const url = new URL(base_url + 'kewilayahan/sebaran/sof/detail') |
|
url.searchParams.set('start', `${pageParam * fetchSize}`) |
|
url.searchParams.set('size', `${fetchSize}`) |
|
url.searchParams.set('filters', JSON.stringify(columnFilters ?? [])) |
|
url.searchParams.set('globalFilter', globalFilter ?? '') |
|
url.searchParams.set('sorting', JSON.stringify(sorting ?? [])) |
|
|
|
const response = await fetch(url.href, { |
|
method: 'POST', |
|
headers: { |
|
Accept: 'application/json', |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
query, |
|
tahunBulan, |
|
...dataSend |
|
}) |
|
}) |
|
|
|
const json = await response.json() |
|
return json |
|
}, |
|
getNextPageParam: (_lastGroup, groups) => groups.length, |
|
keepPreviousData: true, |
|
refetchOnWindowFocus: false |
|
}) |
|
|
|
const flatData = useMemo(() => data?.pages.flatMap((page) => page.data) ?? [], [data]) |
|
const totalDBRowCount = data?.pages?.[0]?.meta?.totalRowCount ?? 0 |
|
const totalFetched = flatData.length |
|
|
|
const fetchMoreOnBottomReached = useCallback( |
|
(containerRefElement) => { |
|
if (containerRefElement) { |
|
const { scrollHeight, scrollTop, clientHeight } = containerRefElement |
|
//once the user has scrolled within 400px of the bottom of the table, fetch more data if we can |
|
if (scrollHeight - scrollTop - clientHeight < 400 && !isFetching && totalFetched < totalDBRowCount) { |
|
fetchNextPage() |
|
} |
|
} |
|
}, |
|
|
|
[fetchNextPage, isFetching, totalFetched, totalDBRowCount] |
|
) |
|
|
|
const columns = [ |
|
{ |
|
accessorKey: 'NPWP', |
|
header: 'NPWP', |
|
enableClickToCopy: true, |
|
size: 150 |
|
}, |
|
{ |
|
accessorKey: 'NAMA_WP', |
|
header: 'Nama' |
|
}, |
|
{ |
|
accessorKey: 'ALAMAT_MFWP', |
|
header: 'Alamat' |
|
}, |
|
{ |
|
accessorKey: 'KELURAHAN_MFWP', |
|
header: 'Wil. Adm.', |
|
Cell: (data) => { |
|
const dataRow = data.row.original |
|
return `${dataRow.KELURAHAN_MFWP ?? ''} ${dataRow.KECAMATAN_MFWP ?? ''} ${dataRow.KOTA_MFWP ?? ''} ${dataRow.PROPINSI_MFWP ?? ''}` |
|
} |
|
}, |
|
{ |
|
accessorKey: 'STATUS_WP_MFWP', |
|
header: 'Status WP' |
|
}, |
|
{ |
|
accessorKey: 'JNS_WP_MFWP', |
|
header: 'Jenis WP' |
|
}, |
|
{ |
|
accessorKey: 'NM_KANTOR', |
|
header: 'KPP Terdaftar' |
|
}, |
|
{ |
|
accessorKey: 'NAMA_AR_MFWP', |
|
header: 'AR' |
|
}, |
|
{ |
|
accessorKey: 'FLAG_WPS_WPK', |
|
header: 'WPS/WPK', |
|
size: 100, |
|
mantineTableBodyCellProps: { |
|
align: 'center' |
|
} |
|
}, |
|
{ |
|
accessorKey: 'LAPISAN', |
|
header: 'Lapisan' |
|
}, |
|
{ |
|
accessorKey: 'JUMLAH_PEMBAYARAN_THN_TERAKHIR', |
|
header: 'Rp', |
|
Cell: ({ cell }) => parseFloat(cell.getValue()).toLocaleString('id-ID'), |
|
mantineTableHeadCellProps: { |
|
align: 'right' |
|
}, |
|
mantineTableBodyCellProps: { |
|
align: 'right' |
|
}, |
|
size: 100 |
|
}, |
|
{ |
|
accessorKey: 'KETERANGAN', |
|
header: 'SPT' |
|
}, |
|
{ |
|
accessorKey: 'TGL_DAFTAR', |
|
header: 'Tgl Daftar', |
|
Cell: ({ cell }) => { |
|
return dayjs(cell.getValue(), 'DD-MMM-YY').format('YYYY-MM-DD') |
|
} |
|
} |
|
] |
|
|
|
useEffect(() => { |
|
if (rowVirtualizerInstanceRef.current) { |
|
try { |
|
rowVirtualizerInstanceRef.current.scrollToIndex(0) |
|
} catch (e) { |
|
console.error(e) |
|
} |
|
} |
|
}, [sorting, columnFilters, globalFilter]) |
|
|
|
//a check on mount to see if the table is already scrolled to the bottom and immediately needs to fetch more data |
|
|
|
useEffect(() => { |
|
fetchMoreOnBottomReached(tableContainerRef.current) |
|
}, [fetchMoreOnBottomReached]) |
|
|
|
const table1 = useMantineReactTable({ |
|
columns, |
|
data: flatData, |
|
enablePagination: false, |
|
enableRowNumbers: true, |
|
enableRowVirtualization: true, //optional, but recommended if it is likely going to be more than 100 rows |
|
manualFiltering: true, |
|
manualSorting: true, |
|
mantineTableContainerProps: { |
|
ref: tableContainerRef, //get access to the table container element |
|
sx: { maxHeight: '600px' }, //give the table a max height |
|
onScroll: ( |
|
event //add an event listener to the table container element |
|
) => fetchMoreOnBottomReached(event.target) |
|
}, |
|
mantineToolbarAlertBannerProps: { |
|
color: 'red', |
|
children: 'Error loading data' |
|
}, |
|
onColumnFiltersChange: setColumnFilters, |
|
onGlobalFilterChange: setGlobalFilter, |
|
onSortingChange: setSorting, |
|
renderBottomToolbarCustomActions: () => ( |
|
<Text className="text-sm"> |
|
Fetched {totalFetched} of {totalDBRowCount} total rows. |
|
</Text> |
|
), |
|
state: { |
|
columnFilters, |
|
globalFilter, |
|
isLoading, |
|
showAlertBanner: isError, |
|
showProgressBars: isFetching, |
|
sorting |
|
}, |
|
rowVirtualizerInstanceRef, //get access to the virtualizer instance |
|
rowVirtualizerProps: { overscan: 10 }, |
|
mantineTableBodyCellProps: { className: 'p-1 text-xs' }, |
|
mantineTableBodyProps: { className: 'mb-3' } |
|
}) |
|
|
|
return <MantineReactTable table={table1} /> |
|
} |
|
|
|
const queryClient = new QueryClient() |
|
|
|
return ( |
|
<> |
|
{loading ? ( |
|
<Row> |
|
<Col> |
|
<Skeleton className="" shape="rectangle" height="20rem" width="100%"></Skeleton> |
|
</Col> |
|
</Row> |
|
) : ( |
|
<Row> |
|
<Col> |
|
<div className="d-flex justify-content-center"> |
|
<Table bordered style={{ width: 'auto', fontSize: '0.85rem' }}> |
|
<thead className="bg-primary text-white"> |
|
<tr> |
|
<th className="text-center text-white" rowSpan="2"> |
|
Lapisan |
|
</th> |
|
<th className="text-center text-white" colSpan="2"> |
|
s.d Sekarang |
|
</th> |
|
<th className="text-center text-white" colSpan="2"> |
|
s.d Bulan Lalu |
|
</th> |
|
<th className="text-center text-white" colSpan="2"> |
|
s.d 2 Bulan Lalu |
|
</th> |
|
</tr> |
|
<tr> |
|
<th className="text-center text-white">Jml WP</th> |
|
<th className="text-center text-white">%</th> |
|
<th className="text-center text-white">Jml WP</th> |
|
<th className="text-center text-white">%</th> |
|
<th className="text-center text-white">Jml WP</th> |
|
<th className="text-center text-white">%</th> |
|
</tr> |
|
<tr className=""> |
|
<th className="text-center text-white">1</th> |
|
<th className="text-center text-white">2</th> |
|
<th className="text-center text-white">3</th> |
|
<th className="text-center text-white">4</th> |
|
<th className="text-center text-white">5</th> |
|
<th className="text-center text-white">6</th> |
|
<th className="text-center text-white">7</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
{data.map((val, idx) => { |
|
return ( |
|
<tr key={idx}> |
|
<td className="text-start p-1 font-weight-bold">{val.LAPISAN}</td> |
|
<td className="text-center p-1 cursor-pointer text-blue underline" onClick={() => angkaOnClick(val.key, val.THNBLN_C)}> |
|
{Number(val.JML_C).toLocaleString('id-ID')} |
|
</td> |
|
<td className="text-center p-1">{((val.JML_C / total.totalC) * 100).toFixed(2) + '%'}</td> |
|
<td className="text-center p-1 cursor-pointer text-blue underline" onClick={() => angkaOnClick(val.key, val.THNBLN_P1)}> |
|
{Number(val.JML_P1).toLocaleString('id-ID')} |
|
</td> |
|
<td className="text-center p-1">{((val.JML_P1 / total.totalP1) * 100).toFixed(2) + '%'}</td> |
|
<td className="text-center p-1 cursor-pointer text-blue underline" onClick={() => angkaOnClick(val.key, val.THNBLN_P2)}> |
|
{Number(val.JML_P2).toLocaleString('id-ID')} |
|
</td> |
|
<td className="text-center p-1">{((val.JML_P2 / total.totalP2) * 100).toFixed(2) + '%'}</td> |
|
</tr> |
|
) |
|
})} |
|
</tbody> |
|
<tfoot> |
|
<tr className="font-weight-bold"> |
|
<td className="text-center">Total</td> |
|
<td className="text-center">{Number(total.totalC).toLocaleString('id-ID')}</td> |
|
<td className="text-center">100%</td> |
|
<td className="text-center">{Number(total.totalP1).toLocaleString('id-ID')}</td> |
|
<td className="text-center">100%</td> |
|
<td className="text-center">{Number(total.totalP2).toLocaleString('id-ID')}</td> |
|
<td className="text-center">100%</td> |
|
</tr> |
|
</tfoot> |
|
</Table> |
|
</div> |
|
</Col> |
|
</Row> |
|
)} |
|
<Row className="f-14"> |
|
<Col> |
|
<span> |
|
Berdasarkan kompilasi data KPD Mobile dan pengolahan data-data perpajakan lainnya (KPD Lainnya), daftar Wajib Pajak yang <b>bayar tidak wajar </b> |
|
agar segera dilakukan dinamisasi/kegiatan intensifikasi pajak (PPM dan/atau PKM) melalui mekanisme komite kepatuhan (DSP4 dan/atau WRA), agar sesuai |
|
dengan kondisi kegiatan usaha yang sebenarnya, oleh: |
|
</span> |
|
<ul> |
|
<li> |
|
AR yang <b>mengampu WP</b> tersebut; |
|
</li> |
|
<li> |
|
AR yang <b>mengampu wilayah</b> tersebut dengan mengirimkan LHKPD (data ICALEP) ke unit kerja yang mengadministrasikan kegiatan usaha yang |
|
diawasi; |
|
</li> |
|
<li>Input di DRM</li> |
|
</ul> |
|
</Col> |
|
</Row> |
|
<Row> |
|
<Col> |
|
<Sidebar |
|
header={ |
|
<> |
|
<h4>Detail Data</h4> |
|
</> |
|
} |
|
visible={visibleSidebar} |
|
position="bottom" |
|
onHide={() => setVisibleSidebar(false)} |
|
style={{ height: 'calc(100vh - 100px)' }} |
|
blockScroll |
|
pt={{ header: { className: 'p-1' }, closeButton: { style: { width: '2rem', height: '1rem' } } }} |
|
> |
|
<Row> |
|
<Col> |
|
<QueryClientProvider client={queryClient}> |
|
<TableDetailGraph dataSend={dataSend} query={query} tahunBulan={tahunBulan} /> |
|
</QueryClientProvider> |
|
</Col> |
|
</Row> |
|
</Sidebar> |
|
</Col> |
|
</Row> |
|
</> |
|
) |
|
} |
|
|
|
export default Sof
|
|
|