Initial commit, repo migration

This commit is contained in:
unknown
2024-05-14 22:19:14 +07:00
commit b51b940ac4
137 changed files with 19393 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
export default function NotFound() {
return (
<>
<Head>
<title>Page not found</title>
</Head>
<div className='flex flex-col justify-center h-screen items-center bg-hitam'>
<p className='text-red-500 text-3xl animate-bounce font-bebas tracking-wide'>404</p>
<p className='text-red-500 text-7xl font-bebas tracking-wide'>PAGE NOT FOUND !</p>
<div className='mt-3'>
<Link href={'/dashboard'}>
<Button label='Back' className='p-button-sm p-button-danger h-8 ' />
</Link>
</div>
</div>
</>
)
}
+71
View File
@@ -0,0 +1,71 @@
/**
* GLOBAL CSS
*/
import 'primeicons/primeicons.css' //icons
import '../styles/globals.css'
/**
* PRIME REACT CSS
*/
import 'primereact/resources/primereact.min.css' //core css
import 'primereact/resources/themes/bootstrap4-light-blue/theme.css' //theme
import '../styles/primeFont.css'
/**
* CUSTOM CSS
*/
import '../styles/accordion.css'
import '../styles/blockUi.css'
import '../styles/button.css'
import '../styles/cClock.css'
import '../styles/card.css'
import '../styles/datatables.css'
import '../styles/datePicker.css'
import '../styles/dialog.css'
import '../styles/dropdown.css'
import '../styles/menu.css'
import '../styles/navbar.css'
import '../styles/panel.css'
import '../styles/step.css'
import '../styles/tabView.css'
import '../styles/table.css'
/**
* MOMENT JS
*/
import 'moment/locale/id'
import { addLocale, locale } from 'primereact/api'
import { Provider } from 'react-redux'
import store from '../store/index'
addLocale('id', {
firstDayOfWeek: 1,
dayNamesMin: ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'],
monthNames: [
'Januari',
'Februari',
'Maret',
'April',
'Mei',
'Juni',
'Juli',
'Agustus',
'September',
'Oktober',
'November',
'Desember',
],
monthNamesShort: ['jan', 'feb', 'mar', 'apr', 'mei', 'jun', 'jul', 'agu', 'sep', 'okt', 'nov', 'des'],
today: 'Hari ini',
clear: 'Clear',
})
locale('id')
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
export default MyApp
+36
View File
@@ -0,0 +1,36 @@
import { Head, Html, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<link rel='icon' href='/img/e-verif.png' />
<meta name='title' content='BKN RI' />
<meta name='description' content='SISTEM APLIKASI VERIFIKASI ANGGARAN BKN RI' />
<meta property='og:type' content='website' />
<meta property='og:url' content='https://www.bkn.go.id/' />
<meta property='og:title' content='BKN RI' />
<meta property='og:description' content='SISTEM APLIKASI VERIFIKASI ANGGARAN BKN RI' />
<meta property='og:image' content='https://i.ibb.co/t2Yh2Vz/logo.png' />
<meta property='twitter:card' content='summary_large_image' />
<meta property='twitter:url' content='https://www.bkn.go.id/' />
<meta property='twitter:title' content='BKN RI' />
<meta property='twitter:description' content='SISTEM APLIKASI VERIFIKASI ANGGARAN BKN RI' />
<meta property='twitter:image' content='https://i.ibb.co/t2Yh2Vz/logo.png' />
<link rel='preconnect' href='https://fonts.googleapis.com' />
<link rel='preconnect' href='https://fonts.gstatic.com' crossOrigin='true' />
<link
href='https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;600;800&display=swap'
rel='stylesheet'
/>
</Head>
<body className='bg-abu'>
<Main />
<NextScript />
</body>
</Html>
)
}
+211
View File
@@ -0,0 +1,211 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { DashboardAll, DashboardP2k } from '@/services/dashboard-service'
import { JenisTagihanList } from '@/services/referensi/jenisTagihan-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { Dropdown } from 'primereact/dropdown'
import { InputText } from 'primereact/inputtext'
import React, { useEffect, useState } from 'react'
export default function Dashboard() {
const [data, setData] = useState([])
const [displayDialog, setDisplayDialog] = useState(false)
const [dataDialog, setDataDialog] = useState([])
const [search, setSearch] = useState({})
const [ddJenisTagihan, setDdJenisTagihan] = useState([])
// DATATABLE
const [loading, setLoading] = useState(false)
const [totalRecords, setTotalRecords] = useState(0)
const [first, setFirst] = useState(0)
const [page, setPage] = useState(1)
const [length, setLength] = useState(10)
const [orderCol, setOrderCol] = useState(2)
const [sort, setSort] = useState([])
const [orderDir, setOrderDir] = useState('desc')
useEffect(() => {
JenisTagihanList().then((res) => setDdJenisTagihan(res.data))
}, [])
useEffect(() => {
const { role } = JSON.parse(localStorage.getItem('user'))
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, jenis_tagihan, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.jenis_tagihan = jenis_tagihan ? jenis_tagihan : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
if (role === 'PPK' || role === 'Verifikator') {
DashboardP2k(params).then((res) => {
setLoading(false)
setData(res.data)
setTotalRecords(res.recordsFiltered - 1)
})
} else {
DashboardAll(params).then((res) => {
setLoading(false)
setData(res.data)
setTotalRecords(res.recordsFiltered - 1)
})
}
}, [page, first, length, orderCol, orderDir, search])
const bodyDetail = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button
label='Detail'
className='p-button-sm'
onClick={() => {
setDisplayDialog(true), setDataDialog(rowData)
}}
/>
</div>
)
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', jenis_tagihan: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Jenis Tagihan</label>
<Dropdown
name='jenis_tagihan'
value={values.jenis_tagihan}
optionValue='jenis_id'
optionLabel='nama'
options={ddJenisTagihan}
onChange={handleChange}
placeholder='Select Jenis Tagihan'
showClear={values.jenis_tagihan ? true : false}
className='w-52'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Input</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
return (
<React.Fragment>
<Head>
<title>Dashboard</title>
</Head>
<Belakang>
<Judul>Data Transaction</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
loading={loading}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
sort={sort}
totalRecords={totalRecords}
length={length}
onSort={(e) => {
setSort(e.multiSortMeta)
console.log('sorting', e)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'nama_unit':
setOrderCol(1)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
dataDialog={dataDialog}
displayDialog={displayDialog}
setDisplayDialog={setDisplayDialog}
>
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='nama_unit' header='Unit PPK' sortable></Column>
<Column field='tanggal_kirim' header='Tanggal Pengajuan' sortable></Column>
<Column field='status_tagihan' header='Status'></Column>
<Column field='no_spm' header='No SPM'></Column>
<Column field='no_sp2d' header='No SP2D'></Column>
<Column field='tgl_spm' header='Tanggal SPM'></Column>
<Column field='tgl_sp2d' header='Tanggal SP2D'></Column>
<Column header='Detail' body={bodyDetail}></Column>
</DatatablePrimeV2>
</Belakang>
</React.Fragment>
)
}
+506
View File
@@ -0,0 +1,506 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { LandingPageLayout } from '@/components/Layouts'
import { CekTagihan } from '@/services/cek-tagihan-service'
import { HomepageGet } from '@/services/homepage-service'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { ColumnGroup } from 'primereact/columngroup'
import { DataTable } from 'primereact/datatable'
import { InputText } from 'primereact/inputtext'
import { Panel } from 'primereact/panel'
import { Row } from 'primereact/row'
import { Steps } from 'primereact/steps'
import React, { useEffect, useState } from 'react'
import { FaFileArrowUp, FaFileCircleCheck, FaFileLines, FaFileSignature } from 'react-icons/fa6'
export default function LandingPage({ token }) {
const [cekTagihan, setCekTagihan] = useState('')
const [dataCekTagihan, setDataCekTagihan] = useState([])
const [data, setData] = useState([
{
status_tagihan_daily: {
'Ditolak/dikembalikan': { total: 0, percentage: 0 },
'Disetujui dengan catatan': { total: 0, percentage: 0 },
'Disetujui tanpa catatan': { total: 0, percentage: 0 },
},
status_tagihan_ytd: {
'Ditolak/dikembalikan': { total: 0, percentage: 0 },
'Disetujui dengan catatan': { total: 0, percentage: 0 },
'Disetujui tanpa catatan': { total: 0, percentage: 0 },
},
jenis_tagihan_daily: {
GUP: {
percentage: 0,
total: 0,
},
LS: {
percentage: 0,
total: 0,
},
PTUP: {
percentage: 0,
total: 0,
},
},
jenis_tagihan_yearly: {
GUP: {
percentage: 0,
total: 0,
},
LS: {
percentage: 0,
total: 0,
},
PTUP: {
percentage: 0,
total: 0,
},
},
},
])
const [dataKetetapan, setDataKetetapan] = useState([])
const [dataPrgoress, setDataPrgoress] = useState([])
useEffect(() => {
HomepageGet()
.then((res) => {
setData([res.data])
setDataKetetapan(res.data.monitoring_ketepatan)
setDataPrgoress(res.data.monitoring_progress)
})
.catch((err) => {
setData(err)
console.log('err homepage get', err)
})
}, [])
const handleCari = () => {
CekTagihan(cekTagihan)
.then((res) => setDataCekTagihan(res.data))
.catch((err) => console.log(err))
}
const items = [
{ label: 'Pengajuan tagihan' },
{ label: 'Berkas telah diverifikasi' },
{ label: 'Berkas telah divalidasi' },
{ label: 'Berkas telah diapprove' },
{ label: 'Berkas disetujui bendahara' },
{ label: 'Berkas disetujui PPSPM' },
{ label: 'SPM dikirim', className: 'last-dor' },
]
const headerGroup = (
<ColumnGroup>
<Row>
<Column header='No SPP' rowSpan={2} />
<Column header='Unit Kerja' rowSpan={2} />
<Column header='Progress' colSpan={6} />
</Row>
<Row>
<Column header='PPK' />
<Column header='Verifikasi' />
<Column header='Rekomendasi' />
<Column header='Persetujuan' />
<Column header='PPSPM' />
</Row>
</ColumnGroup>
)
return (
<div className='landing-page'>
<Head>
<title>E-Verifikasi</title>
</Head>
<div className='flex items-center 2xl:px-60 md:px-20 py-3 justify-between border-b-2 mx-3'>
<div className='pl-3'>
<Image alt='logo' src={'/img/e-verif-title.png'} width={511 / 6} height={540 / 6} />
</div>
<div className='title font-bebas 2xl:text-3xl py-2 md:text-xl text-hitam text-lg text-center'>
<p className='tracking-wide text-3xl'>SISTEM APLIKASI</p>
<p className='tracking-wide text-3xl'>Penyelesaian Tagihan dan Pertanggungjawaban Keuangan</p>
</div>
<div className='hidden md:block'>
{token ? (
<Link href={'/dashboard'}>
<Button label='Dashboard' className='h-8' />
</Link>
) : (
<Link href={'/login'}>
<Button label='Login' className='h-8' />
</Link>
)}
</div>
</div>
<LandingPageLayout>
<Panel
headerTemplate={() => {
return (
<div className='bg-gradient-to-r from-orange3 to-yellow rounded-t p-2'>
<p>Cek Tagihan</p>
</div>
)
}}
toggleable
className='md:hidden mb-1'
>
<div className='mb-5 p-2'>
<InputText
value={cekTagihan}
onChange={(e) => setCekTagihan(e.target.value)}
placeholder='Masukkan nomor SPP'
style={{ marginBottom: '10px', width: '100%' }}
/>
<Button
label='Cari tagihan'
icon='pi pi-search'
className='p-button-sm'
style={{ width: '100%' }}
onClick={() => handleCari()}
/>
</div>
{Object.keys(dataCekTagihan).length > 0 && (
<React.Fragment>
<div className='tracking-doc p-2'>
<div className='rounded-t border-b-2 border-orange pl-1 mb-3 font-semibold text-xl pb-1'>
Tracking Dokumen
</div>
<Steps model={items} className='custom-step' activeIndex={parseInt(dataCekTagihan.detail.tahapan)} />
</div>
<div className='detail-doc p-2'>
<div className='rounded-t border-b-2 border-orange pl-1 mb-3 font-semibold text-xl pb-1'>
Detail Dokumen
</div>
<div className='text-sm'>
<p>
No SPP: <span className='font-semibold'>{dataCekTagihan.detail.no_spp}</span>
</p>
<p>
Nama tagihan: <span className='font-semibold'>{dataCekTagihan.detail.nama_tagihan}</span>
</p>
<p>
Nama unit: <span className='font-semibold'>{dataCekTagihan.detail.nama_unit}</span>
</p>
</div>
</div>
</React.Fragment>
)}
</Panel>
{/* YTD DOKUMEN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-gray-200 rounded-t p-1'>
<p>Year to Date</p>
</div>
)
}}
toggleable
>
<div className='grid grid-cols-4 gap-3 p-5'>
<div className='satu bg-logo-orange1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Jumlah Dokumen</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data.length > 0 && Object.keys(data[0].status_tagihan_ytd).length > 0
? data[0].status_tagihan_ytd['Ditolak/dikembalikan'].total +
data[0].status_tagihan_ytd['Disetujui dengan catatan'].total +
data[0].status_tagihan_ytd['Disetujui tanpa catatan'].total
: 0}
</p>
</div>
</div>
<div className='kanan'>
<FaFileLines color='#D85610' className='text-4xl opacity-50' />
</div>
</div>
<div className='dua bg-logo-pink1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Dikembalikan</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data.length > 0 && data[0].status_tagihan_ytd['Ditolak/dikembalikan'].total}
</p>
<p className='text-lg font-semibold'>
({data.length > 0 && data[0].status_tagihan_ytd['Ditolak/dikembalikan'].percentage}%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileArrowUp color='#D81432' className='text-4xl opacity-50' />
</div>
</div>
<div className='tiga bg-logo-kuning1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Dengan Catatan</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data.length > 0 && data[0].status_tagihan_ytd['Disetujui dengan catatan'].total}
</p>
<p className='text-lg font-semibold'>
({data.length > 0 && data[0].status_tagihan_ytd['Disetujui dengan catatan'].percentage}%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileSignature color='#DC980C' className='text-4xl opacity-50' />
</div>
</div>
<div className='empat bg-logo-biru1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Disetujui</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data.length > 0 && data[0].status_tagihan_ytd['Disetujui tanpa catatan'].total}
</p>
<p className='text-lg font-semibold'>
({data.length > 0 && data[0].status_tagihan_ytd['Disetujui tanpa catatan'].percentage}%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileCircleCheck color='#3666A0' className='text-4xl opacity-50' />
</div>
</div>
</div>
</Panel>
{/* TODAY DOKUMEN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-gray-200 rounded-t p-1 text-sm'>
<p>Today</p>
</div>
)
}}
toggleable
>
<div className='grid grid-cols-4 gap-3 p-5'>
<div className='satu bg-logo-orange1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Jumlah Dokumen</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data[0].status_tagihan_daily['Ditolak/dikembalikan'].total +
data[0].status_tagihan_daily['Disetujui dengan catatan'].total +
data[0].status_tagihan_daily['Disetujui tanpa catatan'].total}
</p>
</div>
</div>
<div className='kanan'>
<FaFileLines color='#D85610' className='text-4xl opacity-50' />
</div>
</div>
<div className='dua bg-logo-pink1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Dikembalikan</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].status_tagihan_daily['Ditolak/dikembalikan'].total}</p>
<p className='text-lg font-semibold'>
({data[0].status_tagihan_daily['Ditolak/dikembalikan'].percentage}
%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileArrowUp color='#D81432' className='text-4xl opacity-50' />
</div>
</div>
<div className='tiga bg-logo-kuning1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Dengan Catatan</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data[0].status_tagihan_daily['Disetujui dengan catatan'].total}
</p>
<p className='text-lg font-semibold'>
({data[0].status_tagihan_daily['Disetujui dengan catatan'].percentage}
%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileSignature color='#DC980C' className='text-4xl opacity-50' />
</div>
</div>
<div className='empat bg-logo-biru1 flex items-center justify-around p-1 rounded'>
<div className='kiri'>
<p className='text-sm'>Disetujui</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>
{data[0].status_tagihan_daily['Disetujui tanpa catatan'].total}
</p>
<p className='text-lg font-semibold'>
({data[0].status_tagihan_daily['Disetujui tanpa catatan'].percentage}
%)
</p>
</div>
</div>
<div className='kanan'>
<FaFileCircleCheck color='#3666A0' className='text-4xl opacity-50' />
</div>
</div>
</div>
</Panel>
{/* YTD ALL TAGIHAN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-gray-200 rounded-t p-1 text-sm'>
<p>Year to Date</p>
</div>
)
}}
toggleable
>
<div className='grid grid-cols-3 gap-3 p-5'>
<div className='satu bg-logo-orange1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan LS</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_yearly.LS.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_yearly.LS.percentage}
%)
</p>
</div>
</div>
<div className='dua bg-logo-pink1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan GU</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_yearly.GUP.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_yearly.GUP.percentage}
%)
</p>
</div>
</div>
<div className='tiga bg-logo-kuning1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan TUP</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_yearly.PTUP.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_yearly.PTUP.percentage}
%)
</p>
</div>
</div>
</div>
</Panel>
{/* TODAY ALL TAGIHAN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-gray-200 rounded-t p-1 text-sm'>
<p>Today</p>
</div>
)
}}
toggleable
>
<div className='grid grid-cols-3 gap-3 p-3'>
<div className='satu bg-logo-orange1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan LS</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_daily.LS.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_daily.LS.percentage}
%)
</p>
</div>
</div>
<div className='dua bg-logo-pink1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan GU</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_daily.GUP.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_daily.GUP.percentage}
%)
</p>
</div>
</div>
<div className='tiga bg-logo-kuning1 flex items-center justify-around p-1 rounded flex-col'>
<p className='text-sm'>Tagihan TUP</p>
<div className='flex gap-3'>
<p className='text-lg font-semibold'>{data[0].jenis_tagihan_daily.PTUP.total}</p>
<p className='text-lg font-semibold'>
({data[0].jenis_tagihan_daily.PTUP.percentage}
%)
</p>
</div>
</div>
</div>
</Panel>
{/* MONITORING PROGRESS PENYELESAIAN DOKUMEN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-logo-biru1 rounded-t p-1 text-sm text-white'>
<p>Monitoring Progress Penyelesaian Dokumen</p>
</div>
)
}}
toggleable
>
<DataTable
value={dataPrgoress}
headerColumnGroup={headerGroup}
tableStyle={{ minWidth: '100%' }}
size='small'
stripedRows={true}
className='p-5'
>
<Column field='no_spp' />
<Column field='unit' />
<Column field='ppk' />
<Column field='verifikasi' />
<Column field='rekomendasi' />
<Column field='persetujuan' />
<Column field='ppspm' />
</DataTable>
</Panel>
{/* MONITORING KETEPATAN PENGAJUAN DOKUMEN */}
<Panel
headerTemplate={() => {
return (
<div className='bg-logo-biru1 rounded-t p-1 text-sm text-white'>
<p>Monitoring Ketepatan Pengajuan Dokumen</p>
</div>
)
}}
toggleable
>
<DataTable
value={dataKetetapan}
tableStyle={{ minWidth: '100%' }}
size='small'
stripedRows={true}
className='p-5'
>
<Column header='No SPP' field='no_spp' />
<Column header='No Tagihan' field='no_tagihan' />
<Column header='Jenis Tagihan' field='jenis_tagihan' />
<Column header='Unit Kerja' field='unit_kerja' />
<Column header='Tanggal Selesai Kegiatan' field='tangga_selesai_kegiatan' />
<Column header='Tanggal Dokumen Masuk' field='tanggal_dokumen_masuk' />
<Column header='Keterangan' field='keterangan' />
</DataTable>
</Panel>
</LandingPageLayout>
</div>
)
}
export const getServerSideProps = async (ctx) => {
const token = ctx.req.cookies.token || null
return {
props: {
token,
},
}
}
+20
View File
@@ -0,0 +1,20 @@
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import Head from 'next/head'
import Image from 'next/image'
export default function Approval() {
return (
<>
<Head>
<title>Approval</title>
</Head>
<Belakang>
<Judul>Approval</Judul>
<div className='flex justify-center'>
<Image src={'/img/coming-soon.png'} alt='coming-soon' width={600} height={600} />
</div>
</Belakang>
</>
)
}
@@ -0,0 +1,20 @@
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import Head from 'next/head'
import Image from 'next/image'
export default function PengendalianWaktuPenyelesaianDetail() {
return (
<>
<Head>
<title>Laporan Pengendalian Waktu Penyelesaian Detail</title>
</Head>
<Belakang>
<Judul>Laporan Pengendalian Waktu Penyelesaian Detail</Judul>
<div className='flex justify-center'>
<Image src={'/img/coming-soon.png'} alt='coming-soon' width={600} height={600} />
</div>
</Belakang>
</>
)
}
@@ -0,0 +1,20 @@
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import Head from 'next/head'
import Image from 'next/image'
export default function PengendalianWaktuPenyelesaian() {
return (
<>
<Head>
<title>Laporan Pengendalian Waktu Penyelesaian</title>
</Head>
<Belakang>
<Judul>Laporan Pengendalian Waktu Penyelesaian</Judul>
<div className='flex justify-center'>
<Image src={'/img/coming-soon.png'} alt='coming-soon' width={600} height={600} />
</div>
</Belakang>
</>
)
}
+141
View File
@@ -0,0 +1,141 @@
import { CardFilterLaporan } from '@/components/CardCustom'
import { DatatablePrime } from '@/components/Datatables'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { DDUserVerifKasub, Laporan11 } from '@/services/laporan/laporan-service'
import { UserGetOne } from '@/services/manajemen/user-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Column } from 'primereact/column'
import { Dropdown } from 'primereact/dropdown'
import { useEffect, useState } from 'react'
export default function PerUser() {
const [data, setData] = useState([])
const [draw, setDraw] = useState(1)
const [periode, setPeriode] = useState('')
const [optionVerifKasub, setOptionVerifKasub] = useState([])
const [userID, setUserID] = useState('')
const [refresh, setRefresh] = useState(0)
const [namaUser, setNamaUser] = useState('')
useEffect(() => {
let params = {
periode: periode,
user_id: userID,
}
Laporan11(params)
.then((res) => {
if (res.status === 'error') {
setData([])
} else {
setData(res.data)
}
})
.catch((err) => console.log(err))
}, [refresh])
useEffect(() => {
DDUserVerifKasub()
.then((res) => setOptionVerifKasub(res.data))
.catch((err) => console.log(err))
}, [])
return (
<>
<Head>
<title>Laporan Per User</title>
</Head>
<Belakang>
<Judul>Laporan Per User</Judul>
<CardFilterLaporan header={'Filter'}>
<Formik
initialValues={{ user_id: '', periode: undefined }}
onSubmit={(values, { setSubmitting }) => {
let periodeBefore = values.periode
Object.assign(values, { periode: moment(values.periode).format('YYYY-MM') })
setPeriode(values.periode)
setUserID(values.user_id)
UserGetOne({ user_id: values.user_id }).then((res) => {
setNamaUser(res.data.nama)
})
values.periode = periodeBefore
setRefresh(Math.random())
setSubmitting(false)
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting }) => {
return (
<form onSubmit={handleSubmit} className='flex gap-x-5'>
<div className='flex flex-col'>
<label htmlFor='user_id'>Username</label>
<Dropdown
name='user_id'
optionLabel='nama_user'
optionValue='user_id'
value={values.user_id}
options={optionVerifKasub}
onChange={handleChange}
placeholder='Select a Username'
className='w-52'
filter
/>
</div>
<div className='flex flex-col'>
<label htmlFor='periode'>Periode</label>
<Calendar
name='periode'
value={values.periode}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode'
/>
</div>
<div className='flex items-end mb-1'>
<Button type='submit' label='Tampilkan' className='p-button-sm' loading={isSubmitting} />
</div>
</form>
)
}}
</Formik>
</CardFilterLaporan>
<div className='border rounded p-3 mb-5 shadow'>
<table cellPadding={3} width={'100%'}>
<tbody>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold' width={'10%'}>
Nama
</td>
<td width={'1%'}>:</td>
<td width={'89%'}>{namaUser}</td>
</tr>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold'>Unit</td>
<td>:</td>
<td>Biro Keuangan</td>
</tr>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold'>Bulan</td>
<td>:</td>
<td>{periode && moment(periode).format('MMMM YYYY')}</td>
</tr>
</tbody>
</table>
</div>
<DatatablePrime data={data} draw={draw} setDraw={setDraw} buttonAddCustom={false}>
<Column field='no_tagihan' header='Nomor Tagihan' sortable></Column>
<Column field='nama_unit' header='Unit' sortable></Column>
<Column field='tanggal_proses' header='Tanggal Proses' sortable></Column>
</DatatablePrime>
</Belakang>
</>
)
}
@@ -0,0 +1,164 @@
import { CardFilterLaporan } from '@/components/CardCustom'
import { DatatablePrime } from '@/components/Datatables'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { DDUserVerifKasub, Laporan21 } from '@/services/laporan/laporan-service'
import { UserGetOne } from '@/services/manajemen/user-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Column } from 'primereact/column'
import { Dropdown } from 'primereact/dropdown'
import { useEffect, useState } from 'react'
export default function RekapBulananPerUser() {
const [data, setData] = useState([])
const [headers, setHeaders] = useState([])
const [draw, setDraw] = useState(1)
const [optionVerifKasub, setOptionVerifKasub] = useState([])
const [userID, setUserID] = useState('')
const [refresh, setRefresh] = useState(0)
const [periodeFrom, setPeriodeFrom] = useState('')
const [periodeTo, setPeriodeTo] = useState('')
const [namaUser, setNamaUser] = useState('')
useEffect(() => {
let params = {
from: periodeFrom,
to: periodeTo,
user_id: userID,
}
Laporan21(params)
.then((res) => {
if (res.status === 'error') {
setData([])
} else {
// console.log(res.data)
setData(res.data.rows)
setHeaders(res.data.headers)
}
})
.catch((err) => console.log(err))
}, [refresh])
useEffect(() => {
DDUserVerifKasub()
.then((res) => setOptionVerifKasub(res.data))
.catch((err) => console.log(err))
}, [])
const dynamicColumns = headers.map((col, i) => {
return <Column key={i} field={[i]} header={col} />
})
return (
<>
<Head>
<title>Laporan Rekapitulasi Bulanan per User</title>
</Head>
<Belakang>
<Judul>Laporan Rekapitulasi Bulanan per User</Judul>
<CardFilterLaporan header={'Filter'}>
<Formik
initialValues={{ user_id: '', from: undefined, to: undefined }}
onSubmit={(values, { setSubmitting }) => {
let periodeFromBefore = values.from
let periodeToBefore = values.to
Object.assign(values, { from: moment(values.from).format('YYYY-MM') })
Object.assign(values, { to: moment(values.to).format('YYYY-MM') })
setPeriodeFrom(values.from)
setPeriodeTo(values.to)
setUserID(values.user_id)
UserGetOne({ user_id: values.user_id }).then((res) => {
setNamaUser(res.data.nama)
})
values.from = periodeFromBefore
values.to = periodeToBefore
setRefresh(Math.random())
setSubmitting(false)
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting }) => {
return (
<form onSubmit={handleSubmit} className='flex gap-x-5 items-center'>
<div className='flex flex-col'>
<label htmlFor='user_id'>Username</label>
<Dropdown
name='user_id'
optionLabel='nama_user'
optionValue='user_id'
value={values.user_id}
options={optionVerifKasub}
onChange={handleChange}
placeholder='Select a Username'
className='w-52'
filter
/>
</div>
<div className='flex flex-col'>
<label htmlFor='from'>Periode From</label>
<Calendar
name='from'
value={values.from}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode From'
/>
</div>
<p>S/D</p>
<div className='flex flex-col'>
<label htmlFor='periode'>Periode To</label>
<Calendar
name='to'
value={values.to}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode To'
/>
</div>
<div className='flex items-end mb-1'>
<Button type='submit' label='Tampilkan' className='p-button-sm' loading={isSubmitting} />
</div>
</form>
)
}}
</Formik>
</CardFilterLaporan>
<div className='border rounded p-3 mb-5 shadow'>
<table cellPadding={3} width={'100%'}>
<tbody>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold' width={'10%'}>
Nama
</td>
<td width={'1%'}>:</td>
<td width={'89%'}>{namaUser}</td>
</tr>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold'>Unit</td>
<td>:</td>
<td>Biro Keuangan</td>
</tr>
<tr style={{ borderBottom: '1px solid', borderColor: '#e5e7eb' }}>
<td className='font-semibold'>Bulan</td>
<td>:</td>
<td>{periodeFrom && moment(periodeFrom).format('MMMM YYYY')}</td>
</tr>
</tbody>
</table>
</div>
<DatatablePrime data={data} draw={draw} setDraw={setDraw} buttonAddCustom={false} showHeader={false}>
{dynamicColumns}
</DatatablePrime>
</Belakang>
</>
)
}
@@ -0,0 +1,116 @@
import { CardFilterLaporan } from '@/components/CardCustom'
import { DatatablePrime } from '@/components/Datatables'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { Laporan33 } from '@/services/laporan/laporan-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Column } from 'primereact/column'
import { useEffect, useState } from 'react'
export default function RekapPerJenisBelanja() {
const [data, setData] = useState([])
const [draw, setDraw] = useState(1)
const [refresh, setRefresh] = useState(0)
const [periodeFrom, setPeriodeFrom] = useState('')
const [periodeTo, setPeriodeTo] = useState('')
const headers = [
'Jenis Bayar',
'Jumlah Dokumen',
'Disetujui Tanpa Catatan',
'Disetujui Dengan Catatan',
'Tidak Disetujui',
]
useEffect(() => {
let params = {
from: periodeFrom,
to: periodeTo,
}
Laporan33(params)
.then((res) => {
if (res.status === 'error') {
setData([])
} else {
setData(res.data)
}
})
.catch((err) => console.log(err))
}, [refresh])
const dynamicColumns = headers.map((col, i) => {
return <Column key={i} field={[i]} header={col} />
})
return (
<>
<Head>
<title>Laporan Rekapitulasi per Jenis Belanja</title>
</Head>
<Belakang>
<Judul>Laporan Rekapitulasi per Jenis Belanja</Judul>
<CardFilterLaporan header={'Filter'}>
<Formik
initialValues={{ user_id: '', from: undefined, to: undefined }}
onSubmit={(values, { setSubmitting }) => {
let periodeFromBefore = values.from
let periodeToBefore = values.to
Object.assign(values, { from: moment(values.from).format('YYYY-MM') })
Object.assign(values, { to: moment(values.to).format('YYYY-MM') })
setPeriodeFrom(values.from)
setPeriodeTo(values.to)
values.from = periodeFromBefore
values.to = periodeToBefore
setRefresh(Math.random())
setSubmitting(false)
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting }) => {
return (
<form onSubmit={handleSubmit} className='flex gap-x-5 items-center'>
<div className='flex flex-col'>
<label htmlFor='from'>Periode From</label>
<Calendar
name='from'
value={values.from}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode From'
/>
</div>
<p>S/D</p>
<div className='flex flex-col'>
<label htmlFor='periode'>Periode To</label>
<Calendar
name='to'
value={values.to}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode To'
/>
</div>
<div className='flex items-end mb-1'>
<Button type='submit' label='Tampilkan' className='p-button-sm' loading={isSubmitting} />
</div>
</form>
)
}}
</Formik>
</CardFilterLaporan>
<DatatablePrime data={data} draw={draw} setDraw={setDraw} buttonAddCustom={false} showHeader={false}>
{dynamicColumns}
</DatatablePrime>
</Belakang>
</>
)
}
+110
View File
@@ -0,0 +1,110 @@
import { CardFilterLaporan } from '@/components/CardCustom'
import { DatatablePrime } from '@/components/Datatables'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { Laporan32 } from '@/services/laporan/laporan-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Column } from 'primereact/column'
import { useEffect, useState } from 'react'
export default function RekapPerUnitKerja() {
const [data, setData] = useState([])
const [draw, setDraw] = useState(1)
const [refresh, setRefresh] = useState(0)
const [periodeFrom, setPeriodeFrom] = useState('')
const [periodeTo, setPeriodeTo] = useState('')
const headers = ['Unit', 'Jumlah Dokumen', 'Disetujui Tanpa Catatan', 'Disetujui Dengan Catatan', 'Tidak Disetujui']
useEffect(() => {
let params = {
from: periodeFrom,
to: periodeTo,
}
Laporan32(params)
.then((res) => {
if (res.status === 'error') {
setData([])
} else {
setData(res.data)
}
})
.catch((err) => console.log(err))
}, [refresh])
const dynamicColumns = headers.map((col, i) => {
return <Column key={i} field={[i]} header={col} />
})
return (
<>
<Head>
<title>Laporan Rekapitulasi per Unit Kerja</title>
</Head>
<Belakang>
<Judul>Laporan Rekapitulasi per Unit Kerja</Judul>
<CardFilterLaporan header={'Filter'}>
<Formik
initialValues={{ user_id: '', from: undefined, to: undefined }}
onSubmit={(values, { setSubmitting }) => {
let periodeFromBefore = values.from
let periodeToBefore = values.to
Object.assign(values, { from: moment(values.from).format('YYYY-MM') })
Object.assign(values, { to: moment(values.to).format('YYYY-MM') })
setPeriodeFrom(values.from)
setPeriodeTo(values.to)
values.from = periodeFromBefore
values.to = periodeToBefore
setRefresh(Math.random())
setSubmitting(false)
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting }) => {
return (
<form onSubmit={handleSubmit} className='flex gap-x-5 items-center'>
<div className='flex flex-col'>
<label htmlFor='from'>Periode From</label>
<Calendar
name='from'
value={values.from}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode From'
/>
</div>
<p>S/D</p>
<div className='flex flex-col'>
<label htmlFor='periode'>Periode To</label>
<Calendar
name='to'
value={values.to}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode To'
/>
</div>
<div className='flex items-end mb-1'>
<Button type='submit' label='Tampilkan' className='p-button-sm' loading={isSubmitting} />
</div>
</form>
)
}}
</Formik>
</CardFilterLaporan>
<DatatablePrime data={data} draw={draw} setDraw={setDraw} buttonAddCustom={false} showHeader={false}>
{dynamicColumns}
</DatatablePrime>
</Belakang>
</>
)
}
@@ -0,0 +1,118 @@
import { CardFilterLaporan } from '@/components/CardCustom'
import { DatatablePrime } from '@/components/Datatables'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { Laporan31 } from '@/services/laporan/laporan-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Column } from 'primereact/column'
import { useEffect, useState } from 'react'
export default function RekapBulananPerUser() {
const [data, setData] = useState([])
const [draw, setDraw] = useState(1)
const [refresh, setRefresh] = useState(0)
const [periodeFrom, setPeriodeFrom] = useState('')
const [periodeTo, setPeriodeTo] = useState('')
const headers = ['Bulan', 'Dokumen', 'Disetujui Tanpa Catatan', 'Disetujui Dengan Catatan', 'Tidak Disetujui']
useEffect(() => {
let params = {
from: periodeFrom,
to: periodeTo,
}
Laporan31(params)
.then((res) => {
if (res.status === 'error') {
setData([])
} else {
setData(res.data)
}
})
.catch((err) => console.log(err))
}, [refresh])
const bodyTanggal = (rowData) => {
return moment(rowData[0]).format('MMM YYYY')
}
const dynamicColumns = headers.map((col, i) => {
let bodyx
if (i === 0) {
bodyx = { body: bodyTanggal }
}
return <Column key={i} field={[i]} header={col} {...bodyx} />
})
return (
<>
<Head>
<title>Laporan Rekapitulasi Hasil Verifikasi per Bulan</title>
</Head>
<Belakang>
<Judul>Laporan Rekapitulasi Hasil Verifikasi per Bulan</Judul>
<CardFilterLaporan header={'Filter'}>
<Formik
initialValues={{ user_id: '', from: undefined, to: undefined }}
onSubmit={(values, { setSubmitting }) => {
let periodeFromBefore = values.from
let periodeToBefore = values.to
Object.assign(values, { from: moment(values.from).format('YYYY-MM') })
Object.assign(values, { to: moment(values.to).format('YYYY-MM') })
setPeriodeFrom(values.from)
setPeriodeTo(values.to)
values.from = periodeFromBefore
values.to = periodeToBefore
setRefresh(Math.random())
setSubmitting(false)
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting }) => {
return (
<form onSubmit={handleSubmit} className='flex gap-x-5 items-center'>
<div className='flex flex-col'>
<label htmlFor='from'>Periode From</label>
<Calendar
name='from'
value={values.from}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode From'
/>
</div>
<p>S/D</p>
<div className='flex flex-col'>
<label htmlFor='periode'>Periode To</label>
<Calendar
name='to'
value={values.to}
onChange={handleChange}
view='month'
dateFormat='MM yy'
placeholder='Select a Periode To'
/>
</div>
<div className='flex items-end mb-1'>
<Button type='submit' label='Tampilkan' className='p-button-sm' loading={isSubmitting} />
</div>
</form>
)
}}
</Formik>
</CardFilterLaporan>
<DatatablePrime data={data} draw={draw} setDraw={setDraw} buttonAddCustom={false} showHeader={false}>
{dynamicColumns}
</DatatablePrime>
</Belakang>
</>
)
}
+20
View File
@@ -0,0 +1,20 @@
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import Head from 'next/head'
import Image from 'next/image'
export default function PerUser() {
return (
<>
<Head>
<title>Laporan Per User</title>
</Head>
<Belakang>
<Judul>Laporan Per User</Judul>
<div className='flex justify-center'>
<Image src={'/img/coming-soon.png'} alt='coming-soon' width={600} height={600} />
</div>
</Belakang>
</>
)
}
+127
View File
@@ -0,0 +1,127 @@
import { Depan } from '@/components/Layouts'
import { useAuth } from '@/hooks/auth'
import { useFormik } from 'formik'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Captcha } from 'primereact/captcha'
import { InputText } from 'primereact/inputtext'
import { Password } from 'primereact/password'
import { Toast } from 'primereact/toast'
import { useRef, useState } from 'react'
export default function Login() {
const toast = useRef(null)
const { login } = useAuth({})
const [loading, setLoading] = useState(false)
const formik = useFormik({
initialValues: {
username: '',
password: '',
recaptcha: '',
},
validate: (data) => {
let errors = {}
if (!data.username) errors.username = 'Username is required.'
if (!data.password) errors.password = 'Password is required.'
if (!data.recaptcha) errors.recaptcha = 'Recaptcha is required.'
return errors
},
onSubmit: (data) => {
setLoading(true)
const username = data.username
const password = data.password
const recaptcha = data.recaptcha
login({ username, password, recaptcha, toast, setLoading })
},
validateOnChange: false,
})
const isFieldValid = (field) => !!(formik.touched[field] && formik.errors[field])
const errorFieldMessage = (field) => {
return (
isFieldValid(field) && (
<small className='p-error' style={{ fontSize: '11px' }}>
{formik.errors[field]}
</small>
)
)
}
const handleCaptcha = (captcha) => {
formik.setFieldValue('recaptcha', captcha)
}
return (
<>
<Toast ref={toast} position='bottom-right' />
<Head>
<title>Login</title>
</Head>
<Depan>
<div className='mb-3 mt-10 text-center'>
<Image alt='logo' src={'/img/e-verif-title.png'} width={511 / 6} height={540 / 6} />
</div>
<p className='text-center font-bebas 2xl:text-3xl text-2xl'>
SISTEM APLIKASI <br /> PENYELESAIAN TAGIHAN dan PERTANGGUNGJAWABAN KEUANGAN
</p>
<p className='2xl:text-2xl font-medium mt-10'>Login</p>
<p className='2xl:text-base text-xs'>Enter your username and password to sign in</p>
<form onSubmit={formik.handleSubmit}>
<div className='flex flex-col mt-3'>
<label htmlFor='username' className='text-sm 2xl:text-lg'>
Username
</label>
<InputText
name={'username'}
id={'username'}
placeholder={'Your username'}
value={formik.values.username}
onChange={formik.handleChange}
/>
{errorFieldMessage('username')}
</div>
<div className='flex flex-col mt-3'>
<label htmlFor='password' className='text-sm 2xl:text-lg'>
Password
</label>
<Password
name={'password'}
id={'password'}
placeholder={'Your password'}
value={formik.values.password}
onChange={formik.handleChange}
toggleMask={true}
feedback={false}
inputStyle={{ width: '100%' }}
/>
{errorFieldMessage('password')}
</div>
<div className='flex mt-3 justify-end text-sm 2xl:text-base'>
<Link href={'/'}>
<a className='text-xs 2xl:text-lg'>Forgot password?</a>
</Link>
</div>
<Captcha
id={'recaptcha'}
siteKey='6Lc8zOwgAAAAAH0Zu_uYLAi223irUrsM65SZm7-C'
onResponse={(captcha) => handleCaptcha(captcha)}
className='relative 2xl:my-3 left-1/2 -translate-x-1/2'
/>
{errorFieldMessage('recaptcha')}
<Button
label='Sign in'
type='submit'
className='w-full h-10'
onClick={formik.handleSubmit}
loading={loading}
></Button>
</form>
</Depan>
</>
)
}
+55
View File
@@ -0,0 +1,55 @@
import { DatatablePrime } from '@/components/Datatables'
import FormJamKerja from '@/components/Form/JamKerja'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { JamKerjaGet } from '@/services/manajemen/jam-kerja-service'
import Head from 'next/head'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function JamKerja() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
useEffect(() => {
JamKerjaGet()
.then((res) => {
console.log(res)
setData([res.data])
})
.catch((err) => {
console.log(err)
})
}, [refresh])
return (
<>
<Toast ref={toast} />
<Head>
<title>Jam Kerja</title>
</Head>
<Belakang>
<Judul>Jam Kerja</Judul>
<DatatablePrime
data={data}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormJamKerja}
buttonAdd={true}
buttonAddLabel='Tambah/replace data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='days' header='Hari' sortable></Column>
<Column field='time' header='Jam Kerja' sortable></Column>
</DatatablePrime>
</Belakang>
</>
)
}
+110
View File
@@ -0,0 +1,110 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormRole from '@/components/Form/Role'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { RoleDelete, RoleGetOne, RoleList } from '@/services/manajemen/role-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function Role() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
RoleList(params).then((res) => setData(res.data))
}, [refresh, search])
const editRole = (data) => {
RoleGetOne({ role_id: data.role_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteRole = () => {
RoleDelete({ role_id: dialogDelete.role_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editRole(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded'
onClick={() => setDialogDelete({ role_id: rowData.role_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Role</title>
</Head>
<Belakang>
<Judul>Role</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormRole}
buttonAdd={false}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Role' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteRole} />
)}
</>
)
}
+113
View File
@@ -0,0 +1,113 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormUser from '@/components/Form/User'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { UserDelete, UserGetOne, UserList } from '@/services/manajemen/user-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function User() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
UserList(params).then((res) => {
setData(res.data)
})
}, [refresh, search])
const editUser = (data) => {
UserGetOne({ user_id: data.user_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteUser = () => {
UserDelete({ user_id: dialogDelete.user_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editUser(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ user_id: rowData.jenis_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>User</title>
</Head>
<Belakang>
<Judul>User</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormUser}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Nama' sortable></Column>
<Column field='username' header='Username' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteUser} />
)}
</>
)
}
+346
View File
@@ -0,0 +1,346 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { TagihanGet, VerifikasiSave } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import 'moment/locale/id'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { RadioButton } from 'primereact/radiobutton'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormPengesahan() {
const router = useRouter()
const { tagihan_id } = router.query
const [dataTagihan, setDataTagihan] = useState([])
const [dataDetail, setDataDetail] = useState([])
useEffect(() => {
tagihan_id &&
TagihanGet({ ref_id: tagihan_id })
.then((res) => {
setDataTagihan(res.data[0])
setDataDetail(res.data[1])
})
.catch((err) => console.log(err))
}, [tagihan_id])
const toast = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
/**
* *Breadcrumb
*/
const items = [{ label: 'Pengesahan', url: '/otorisasi/pengesahan' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const formikInitialValues = () => {
const detail =
dataDetail.length > 0 &&
dataDetail.map((value) => {
return {
detail_id: value.detail_id,
jenis_belanja: value.jenis_belanja,
jenis_kegiatan: value.jenis_kegiatan,
akun: value.akun_id,
keperluan_untuk: value.keperluan,
syarat: value.syarat.map((valueSyarat) => {
return {
syarat_id: valueSyarat.syarat_id,
nama_persyaratan: valueSyarat.syarat_dokumen,
catatans: {
verif: valueSyarat.verif_note,
bendahara: valueSyarat.bendahara_note,
spm: valueSyarat.spm_note,
},
ms: valueSyarat.ms,
}
}),
}
})
return { details: detail, persetujuan: false, tagihan_id: tagihan_id }
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form Pengesahan</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-5'></div>
<Judul>Form Pengesahan</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={dataTagihan && dataTagihan.no_spp} />
<CardCustom header={'Unit PPK'} content={dataTagihan && dataTagihan.unit} />
<CardCustom header={'Jenis Tagihan'} content={dataTagihan && dataTagihan.tagihan} />
</div>
</Card>
{dataDetail.length > 0 && (
<Formik
initialValues={formikInitialValues()}
onSubmit={async (values, { setSubmitting }) => {
VerifikasiSave(values)
.then((res) => {
if (res.status == 'ok') {
Swal.fire({
title: 'Success',
text: 'Verifikasi berhasil',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/otorisasi/pengesahan')
})
}
setSubmitting(false)
})
.catch((err) => {
console.log(err)
setSubmitting(false)
})
}}
validate={(data) => {
let errors = {}
data.details.map((values, indexTagihan) => {
const indexTagihanx = indexTagihan + 1
values.syarat.map((valuesSyarats, indexSyarat) => {
const indexSyaratx = indexSyarat + 1
if (!valuesSyarats.ms) {
errors.ms = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' MS/TMS Tidak Boleh Kosong',
})
} else if (valuesSyarats.ms === 'tms') {
if (!valuesSyarats.catatans.bendahara) {
errors.keterangan = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail:
'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' Catatan Tidak Boleh Kosong',
})
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, setFieldValue, isSubmitting }) => {
const handleKirim = () => {
setFieldValue('persetujuan', true)
handleSubmit()
}
const handleTolak = () => {
setFieldValue('persetujuan', false)
handleSubmit()
}
let cekTMSTolak = []
values.details.map((valRekomKabag) => {
valRekomKabag.syarat.map((valCekSyarat) => {
cekTMSTolak.push(valCekSyarat.ms)
})
})
let cekTMSTolakH = cekTMSTolak.find((isi_tms) => isi_tms === 'tms')
return (
<React.Fragment>
<Accordion
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className='mt-3 shadow'
>
{dataDetail.map((dataTagihan, indexTagihan) => {
return (
<AccordionTab header={`Tagihan ${indexTagihan + 1}`} key={indexTagihan}>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'Jenis Belanja'} content={dataTagihan.jenis_belanja} type='cream' />
<CardCustom header={'Jenis Kegiatan'} content={dataTagihan.jenis_kegiatan} type='cream' />
<CardCustom header={'Akun'} content={dataTagihan.akun_id} type='cream' />
<CardCustom header={'Keperluan Untuk'} content={dataTagihan.keperluan} type='cream' />
<div className='border text-center'>
<p className='font-semibold py-1 bg-green-100'>File</p>
<div className='flex items-center justify-center h-[40px]'>
<Link href={process.env.NEXT_PUBLIC_API_FILE + dataTagihan.filepath}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</div>
</div>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<div className='flex border rounded py-1 px-3 text-xs gap-5 w-fit mb-1'>
<div className='flex gap-3 items-center'>
<span className='bg-logo-pink1 w-10 h-3 rounded'></span>
<p>Syarat dengan status TMS (Tidak memenuhi syarat)</p>
</div>
<div className='flex gap-3 items-center'>
<span className='bg-logo-kuning1 w-10 h-3 rounded'></span>
<p>Syarat dengan catatan</p>
</div>
</div>
<Accordion className='accordion-syarat'>
{dataTagihan.syarat.map((valueSyarat, indexSyarat) => {
return (
<AccordionTab
header={valueSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName={`${
values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'
? 'p-accordion-syarat-tms'
: values.details[indexTagihan].syarat[indexSyarat].catatans.verif
? 'p-accordion-syarat-catatan'
: ''
}`}
>
<div className='mx-32'>
<table className='custom-table'>
<thead>
<tr>
<th>MS</th>
<th>TMS</th>
</tr>
</thead>
<tbody>
<tr>
<td width={'10%'}>
<div className='flex flex-col justify-center items-center'>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='ms'
onChange={(e) => {
handleChange(e)
setFieldValue('tolak', false)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'ms'}
/>
</div>
</td>
<td className='text-center' width={'10%'}>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='tms'
onChange={(e) => {
handleChange(e)
setFieldValue('tolak', true)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'}
/>
</td>
</tr>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat} activeIndex={1}>
<TabPanel header='Catatan Verif'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.verif`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.verif
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Verif'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.bendahara`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
? values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
: ''
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Bendahara'
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.spm`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.spm
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan SPM'
readOnly
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
</div>
<SubJudul className='text-lg mb-1'>Rekomendasi Kabag</SubJudul>
<InputText value={dataTagihan.rekomendasi_kabag} className='w-72' readOnly />
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center gap-x-3'>
<Button
type='button'
label='Tolak'
onClick={() => handleTolak()}
className='p-button-sm p-button-danger'
disabled={cekTMSTolakH ? false : true}
loading={isSubmitting}
/>
<Button
type='submit'
label='Kirim'
onClick={() => handleKirim()}
className='p-button-sm'
disabled={cekTMSTolakH ? true : false}
loading={isSubmitting}
/>
</div>
</React.Fragment>
)
}}
</Formik>
)}
</Belakang>
</>
)
}
+171
View File
@@ -0,0 +1,171 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { VerifikasiList } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { InputText } from 'primereact/inputtext'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function Pengesahan() {
const toast = useRef(null)
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState({})
// DATATABLE
const [first, setFirst] = useState(0)
const [page, setPage] = useState(1)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
const [sort, setSort] = useState([])
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
VerifikasiList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, search, orderCol, orderDir])
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Link href={`/otorisasi/pengesahan/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
</div>
)
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Kirim</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyTanggal = (rowData) => {
return rowData.tanggal_kirim === '-' ? rowData.tanggal_kirim : moment(rowData.tanggal_kirim).format('DD MMMM YYYY')
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Pengesahan</title>
</Head>
<Belakang>
<Judul>Pengesahan</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
length={length}
loading={loading}
page={page}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
totalRecords={totalRecords}
sort={sort}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
>
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='unit' header='Unit' sortable></Column>
<Column field='tanggal' header='Tanggal Kirim' body={bodyTanggal} sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
+403
View File
@@ -0,0 +1,403 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { ddRekomendasi } from '@/constant/globalData'
import { TagihanGet, VerifikasiSave } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import 'moment/locale/id'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { Dropdown } from 'primereact/dropdown'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { RadioButton } from 'primereact/radiobutton'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormPersetujuan() {
const router = useRouter()
const { tagihan_id } = router.query
const [dataTagihan, setDataTagihan] = useState([])
const [dataDetail, setDataDetail] = useState([])
useEffect(() => {
tagihan_id &&
TagihanGet({ ref_id: tagihan_id })
.then((res) => {
setDataTagihan(res.data[0])
setDataDetail(res.data[1])
})
.catch((err) => console.log(err))
}, [tagihan_id])
const toast = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
/**
* *Breadcrumb
*/
const items = [{ label: 'Persetujuan', url: '/otorisasi/persetujuan' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const formikInitialValues = () => {
const detail =
dataDetail.length > 0 &&
dataDetail.map((value) => {
let cekTMS = []
value.syarat.map((valueTMS) => {
cekTMS.push(valueTMS.ms)
})
let cekTMSH = cekTMS.find((isi_tms) => isi_tms === 'tms')
return {
detail_id: value.detail_id,
jenis_belanja: value.jenis_belanja,
jenis_kegiatan: value.jenis_kegiatan,
akun: value.akun_id,
keperluan_untuk: value.keperluan,
rekom_kabag: cekTMSH === 'tms' ? 'Ditolak/dikembalikan' : '',
syarat: value.syarat.map((valueSyarat) => {
return {
syarat_id: valueSyarat.syarat_id,
nama_persyaratan: valueSyarat.syarat_dokumen,
catatans: {
verif: valueSyarat.verif_note,
bendahara: valueSyarat.bendahara_note,
spm: valueSyarat.spm_note,
},
ms: valueSyarat.ms,
}
}),
}
})
return { details: detail, persetujuan: false, tagihan_id: tagihan_id }
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form Persetujuan</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-5'></div>
<Judul>Form Persetujuan</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={dataTagihan && dataTagihan.no_spp} />
<CardCustom header={'Unit PPK'} content={dataTagihan && dataTagihan.unit} />
<CardCustom header={'Jenis Tagihan'} content={dataTagihan && dataTagihan.tagihan} />
</div>
</Card>
{dataDetail.length > 0 && (
<Formik
initialValues={formikInitialValues()}
onSubmit={async (values, { setSubmitting }) => {
VerifikasiSave(values)
.then((res) => {
if (res.status == 'ok') {
Swal.fire({
title: 'Success',
text: 'Verifikasi berhasil',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/otorisasi/persetujuan')
})
}
setSubmitting(false)
})
.catch((err) => {
setSubmitting(false)
console.log(err)
})
}}
validate={(data) => {
let errors = {}
data.details.map((values, indexTagihan) => {
const indexTagihanx = indexTagihan + 1
if (!values.rekom_kabag) {
errors.rekom = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Rekomendasi Tagihan ' + indexTagihanx + ' Tidak Boleh Kosong',
})
}
values.syarat.map((valuesSyarats, indexSyarat) => {
const indexSyaratx = indexSyarat + 1
if (!valuesSyarats.ms) {
errors.ms = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' MS/TMS Tidak Boleh Kosong',
})
} else if (valuesSyarats.ms === 'tms') {
if (!valuesSyarats.catatans.verif) {
errors.keterangan = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail:
'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' Catatan Tidak Boleh Kosong',
})
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, setFieldValue, isSubmitting }) => {
const handleKirim = () => {
setFieldValue('persetujuan', true)
handleSubmit()
}
const handleTolak = () => {
setFieldValue('persetujuan', false)
handleSubmit()
}
let cekTMSTolak = []
values.details.map((valRekomKabag) => {
cekTMSTolak.push(valRekomKabag.rekom_kabag)
})
let cekTMSTolakH = cekTMSTolak.find((isi_tms) => isi_tms === 'Ditolak/dikembalikan')
return (
<React.Fragment>
<Accordion
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className='mt-3 shadow'
>
{dataDetail.map((dataTagihan, indexTagihan) => {
let cekTMS = []
values.details[indexTagihan].syarat.map((valueTMS) => {
cekTMS.push(valueTMS.ms)
})
let cekTMSH = cekTMS.find((isi_tms) => isi_tms === 'tms')
return (
<AccordionTab header={`Tagihan ${indexTagihan + 1}`} key={indexTagihan}>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'Jenis Belanja'} content={dataTagihan.jenis_belanja} type='cream' />
<CardCustom header={'Jenis Kegiatan'} content={dataTagihan.jenis_kegiatan} type='cream' />
<CardCustom header={'Akun'} content={dataTagihan.akun_id} type='cream' />
<CardCustom header={'Keperluan Untuk'} content={dataTagihan.keperluan} type='cream' />
<div className='border text-center'>
<p className='font-semibold py-1 bg-green-100'>File</p>
<div className='flex items-center justify-center h-[40px]'>
<Link href={process.env.NEXT_PUBLIC_API_FILE + dataTagihan.filepath}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</div>
</div>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<div className='flex border rounded py-1 px-3 text-xs gap-5 w-fit mb-1'>
<div className='flex gap-3 items-center'>
<span className='bg-logo-pink1 w-10 h-3 rounded'></span>
<p>Syarat dengan status TMS (Tidak memenuhi syarat)</p>
</div>
<div className='flex gap-3 items-center'>
<span className='bg-logo-kuning1 w-10 h-3 rounded'></span>
<p>Syarat dengan catatan</p>
</div>
</div>
<Accordion className='accordion-syarat'>
{dataTagihan.syarat.map((valueSyarat, indexSyarat) => {
return (
<AccordionTab
header={valueSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName={`${
values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'
? 'p-accordion-syarat-tms'
: values.details[indexTagihan].syarat[indexSyarat].catatans.verif
? 'p-accordion-syarat-catatan'
: ''
}`}
>
<div className='mx-32'>
<table className='custom-table'>
<thead>
<tr>
<th>MS</th>
<th>TMS</th>
</tr>
</thead>
<tbody>
<tr>
<td width={'10%'}>
<div className='flex flex-col justify-center items-center'>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='ms'
onChange={(e) => {
handleChange(e)
setFieldValue(`details.${indexTagihan}.rekom_kabag`, null)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'ms'}
/>
</div>
</td>
<td className='text-center' width={'10%'}>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='tms'
onChange={(e) => {
handleChange(e)
setFieldValue(
`details.${indexTagihan}.rekom_kabag`,
'Ditolak/dikembalikan'
)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'}
/>
</td>
</tr>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat} activeIndex={0}>
<TabPanel header='Catatan Verifikator'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.verif`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.verif
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Verifikator'
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.bendahara`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Bendahara'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.spm`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.spm
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan SPM'
readOnly
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
</div>
<SubJudul className='text-lg mb-1'>Rekomendasi Verif</SubJudul>
<InputText
value={dataTagihan.rekomendasi_verifikator}
className='w-72'
readOnly
style={{ marginBottom: '12px' }}
/>
<SubJudul className='text-lg mb-1'>Rekomendasi Kasub</SubJudul>
<InputText
value={dataTagihan.rekomendasi_kasub}
className='w-72'
readOnly
style={{ marginBottom: '12px' }}
/>
<SubJudul className='text-lg mb-1'>Rekomendasi Kabag</SubJudul>
<Dropdown
name={`details.${indexTagihan}.rekom_kabag`}
optionLabel='name'
optionValue='name'
value={
cekTMSH === 'tms' ? 'Ditolak/dikembalikan' : values.details[indexTagihan].rekom_kabag
}
options={
cekTMSH === 'tms'
? ddRekomendasi.filter((val) => val.name === 'Ditolak/dikembalikan')
: ddRekomendasi.filter((val) => val.name !== 'Ditolak/dikembalikan')
}
onChange={handleChange}
placeholder='Pilih rekomendasi'
className='w-80'
showClear={
values.details[indexTagihan].rekom_kabag
? values.details[indexTagihan].rekom_kabag === 'Ditolak/dikembalikan'
? false
: true
: false
}
readOnly={cekTMSH === 'tms' ? true : false}
/>
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center gap-x-3'>
<Button
type='button'
label='Tolak'
onClick={() => handleTolak()}
className='p-button-sm p-button-danger'
disabled={cekTMSTolakH === 'Ditolak/dikembalikan' ? false : true}
loading={isSubmitting}
/>
<Button
type='button'
label='Kirim'
onClick={() => handleKirim()}
className='p-button-sm'
disabled={cekTMSTolakH === 'Ditolak/dikembalikan' ? true : false}
loading={isSubmitting}
/>
</div>
</React.Fragment>
)
}}
</Formik>
)}
</Belakang>
</>
)
}
+171
View File
@@ -0,0 +1,171 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { VerifikasiList } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { InputText } from 'primereact/inputtext'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function Rekomendasi() {
const toast = useRef(null)
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState({})
// DATATABLE
const [first, setFirst] = useState(0)
const [page, setPage] = useState(1)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
const [sort, setSort] = useState([])
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
VerifikasiList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, search, orderCol, orderDir])
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Link href={`/otorisasi/persetujuan/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
</div>
)
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Kirim</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyTanggal = (rowData) => {
return rowData.tanggal_kirim === '-' ? rowData.tanggal_kirim : moment(rowData.tanggal_kirim).format('DD MMMM YYYY')
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Persetujuan</title>
</Head>
<Belakang>
<Judul>Persetujuan</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
length={length}
loading={loading}
page={page}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
totalRecords={totalRecords}
sort={sort}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
>
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='unit' header='Unit' sortable></Column>
<Column field='tanggal' header='Tanggal Kirim' body={bodyTanggal} sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
+367
View File
@@ -0,0 +1,367 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { ddRekomendasi } from '@/constant/globalData'
import { TagihanGet, VerifikasiSave } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import 'moment/locale/id'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { Dropdown } from 'primereact/dropdown'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { RadioButton } from 'primereact/radiobutton'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormRekomendasi() {
const router = useRouter()
const { tagihan_id } = router.query
const [dataTagihan, setDataTagihan] = useState([])
const [dataDetail, setDataDetail] = useState([])
useEffect(() => {
tagihan_id &&
TagihanGet({ ref_id: tagihan_id })
.then((res) => {
setDataTagihan(res.data[0])
setDataDetail(res.data[1])
})
.catch((err) => console.log(err))
}, [tagihan_id])
const toast = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
/**
* *Breadcrumb
*/
const items = [{ label: 'Verifikasi', url: '/otorisasi/verifikasi' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const formikInitialValues = () => {
const detail =
dataDetail.length > 0 &&
dataDetail.map((value) => {
let cekTMS = []
value.syarat.map((valueTMS) => {
cekTMS.push(valueTMS.ms)
})
let cekTMSH = cekTMS.find((isi_tms) => isi_tms === 'tms')
return {
detail_id: value.detail_id,
jenis_belanja: value.jenis_belanja,
jenis_kegiatan: value.jenis_kegiatan,
akun: value.akun_id,
keperluan_untuk: value.keperluan,
syarat: value.syarat.map((valueSyarat) => {
return {
syarat_id: valueSyarat.syarat_id,
nama_persyaratan: valueSyarat.syarat_dokumen,
catatans: {
verif: valueSyarat.verif_note,
bendahara: valueSyarat.bendahara_note,
spm: valueSyarat.spm_note,
},
ms: valueSyarat.ms,
}
}),
rekom_kasub: cekTMSH === 'tms' ? 'Ditolak/dikembalikan' : '',
}
})
return { details: detail, persetujuan: true, tagihan_id: tagihan_id }
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form Rekomendasi</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-5'></div>
<Judul>Form Rekomendasi</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={dataTagihan && dataTagihan.no_spp} />
<CardCustom header={'Unit PPK'} content={dataTagihan && dataTagihan.unit} />
<CardCustom header={'Jenis Tagihan'} content={dataTagihan && dataTagihan.tagihan} />
</div>
</Card>
{dataDetail.length > 0 && (
<Formik
enableReinitialize
initialValues={formikInitialValues()}
onSubmit={async (values, { setSubmitting }) => {
VerifikasiSave(values)
.then((res) => {
if (res.status == 'ok') {
Swal.fire({
title: 'Success',
text: 'Verifikasi berhasil',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/otorisasi/rekomendasi')
})
setSubmitting(false)
}
})
.catch((err) => {
setSubmitting(false)
console.log(err)
})
}}
validate={(data) => {
let errors = {}
data.details.map((values, indexTagihan) => {
const indexTagihanx = indexTagihan + 1
if (!values.rekom_kasub) {
errors.rekom = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Rekomendasi Tagihan ' + indexTagihanx + ' Tidak Boleh Kosong',
})
}
values.syarat.map((valuesSyarats, indexSyarat) => {
const indexSyaratx = indexSyarat + 1
if (!valuesSyarats.ms) {
errors.ms = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' MS/TMS Tidak Boleh Kosong',
})
} else if (valuesSyarats.ms === 'tms') {
if (!valuesSyarats.catatans.verif) {
errors.keterangan = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail:
'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' Catatan Tidak Boleh Kosong',
})
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, isSubmitting, setFieldValue }) => {
return (
<React.Fragment>
<Accordion
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className='mt-3 shadow'
>
{dataDetail.map((dataTagihan, indexTagihan) => {
let cekTMS = []
values.details[indexTagihan].syarat.map((valueTMS) => {
cekTMS.push(valueTMS.ms)
})
let cekTMSH = cekTMS.find((isi_tms) => isi_tms === 'tms')
return (
<AccordionTab header={`Tagihan ${indexTagihan + 1}`} key={indexTagihan}>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'Jenis Belanja'} content={dataTagihan.jenis_belanja} type='cream' />
<CardCustom header={'Jenis Kegiatan'} content={dataTagihan.jenis_kegiatan} type='cream' />
<CardCustom header={'Akun'} content={dataTagihan.akun_id} type='cream' />
<CardCustom header={'Keperluan Untuk'} content={dataTagihan.keperluan} type='cream' />
<div className='border text-center'>
<p className='font-semibold py-1 bg-green-100'>File</p>
<div className='flex items-center justify-center h-[40px]'>
<Link href={process.env.NEXT_PUBLIC_API_FILE + dataTagihan.filepath}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</div>
</div>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<div className='flex border rounded py-1 px-3 text-xs gap-5 w-fit mb-1'>
<div className='flex gap-3 items-center'>
<span className='bg-logo-pink1 w-10 h-3 rounded'></span>
<p>Syarat dengan status TMS (Tidak memenuhi syarat)</p>
</div>
<div className='flex gap-3 items-center'>
<span className='bg-logo-kuning1 w-10 h-3 rounded'></span>
<p>Syarat dengan catatan</p>
</div>
</div>
<Accordion className='accordion-syarat'>
{dataTagihan.syarat.map((valueSyarat, indexSyarat) => {
return (
<AccordionTab
header={valueSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName={`${
values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'
? 'p-accordion-syarat-tms'
: values.details[indexTagihan].syarat[indexSyarat].catatans.verif
? 'p-accordion-syarat-catatan'
: ''
}`}
>
<div className='mx-32'>
<table className='custom-table'>
<thead>
<tr>
<th>MS</th>
<th>TMS</th>
</tr>
</thead>
<tbody>
<tr>
<td width={'10%'}>
<div className='flex flex-col justify-center items-center'>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='ms'
onChange={handleChange}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'ms'}
/>
</div>
</td>
<td className='text-center' width={'10%'}>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='tms'
onChange={(e) => {
handleChange(e)
setFieldValue(
`details.${indexTagihan}.rekom_kasub`,
'Ditolak/dikembalikan'
)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'}
/>
</td>
</tr>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat}>
<TabPanel header='Catatan Verifikator'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.verif`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.verif
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Verifikator'
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.bendahara`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Bendahara'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.spm`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.spm
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan SPM'
readOnly
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
</div>
<SubJudul className='text-lg mb-1'>Rekomendasi Verif</SubJudul>
<InputText
value={dataTagihan.rekomendasi_verifikator}
className='w-72'
readOnly
style={{ marginBottom: '12px' }}
/>
<SubJudul className='text-lg mb-1'>Rekomendasi Validasi</SubJudul>
<Dropdown
name={`details.${indexTagihan}.rekom_kasub`}
optionLabel='name'
optionValue='name'
value={values.details[indexTagihan].rekom_kasub}
options={
cekTMSH === 'tms'
? ddRekomendasi.filter((val) => val.name === 'Ditolak/dikembalikan')
: ddRekomendasi.filter((val) => val.name !== 'Ditolak/dikembalikan')
}
onChange={handleChange}
placeholder='Pilih rekomendasi'
className='w-80'
showClear={
values.details[indexTagihan].rekom_kasub
? values.details[indexTagihan].rekom_kasub === 'Ditolak/dikembalikan'
? false
: true
: false
}
readOnly={cekTMSH === 'tms' ? true : false}
/>
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center'>
<Button
type='submit'
label='Kirim'
onClick={handleSubmit}
className='p-button-sm'
loading={isSubmitting}
/>
</div>
</React.Fragment>
)
}}
</Formik>
)}
</Belakang>
</>
)
}
+171
View File
@@ -0,0 +1,171 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { VerifikasiList } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { InputText } from 'primereact/inputtext'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function Rekomendasi() {
const toast = useRef(null)
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState({})
// DATATABLE
const [first, setFirst] = useState(0)
const [page, setPage] = useState(0)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
const [sort, setSort] = useState([])
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
VerifikasiList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, search, orderCol, orderDir])
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Link href={`/otorisasi/rekomendasi/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
</div>
)
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Kirim</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyTanggal = (rowData) => {
return rowData.tanggal_kirim === '-' ? rowData.tanggal_kirim : moment(rowData.tanggal_kirim).format('DD MMMM YYYY')
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Rekomendasi</title>
</Head>
<Belakang>
<Judul>Rekomendasi</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
length={length}
loading={loading}
page={page}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
totalRecords={totalRecords}
sort={sort}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
>
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='unit' header='Unit'></Column>
<Column field='tanggal' header='Tanggal Kirim' body={bodyTanggal} sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
+348
View File
@@ -0,0 +1,348 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { TagihanGet, VerifikasiSave } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import 'moment/locale/id'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { RadioButton } from 'primereact/radiobutton'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormSpm() {
const router = useRouter()
const { tagihan_id } = router.query
const [dataTagihan, setDataTagihan] = useState([])
const [dataDetail, setDataDetail] = useState([])
useEffect(() => {
tagihan_id &&
TagihanGet({ ref_id: tagihan_id })
.then((res) => {
setDataTagihan(res.data[0])
setDataDetail(res.data[1])
})
.catch((err) => console.log(err))
}, [tagihan_id])
const toast = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
/**
* *Breadcrumb
*/
const items = [{ label: 'PPSPM', url: '/otorisasi/spm' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const formikInitialValues = () => {
const detail =
dataDetail.length > 0 &&
dataDetail.map((value) => {
return {
detail_id: value.detail_id,
jenis_belanja: value.jenis_belanja,
jenis_kegiatan: value.jenis_kegiatan,
akun: value.akun_id,
keperluan_untuk: value.keperluan,
rekom_verif: '',
syarat: value.syarat.map((valueSyarat) => {
return {
syarat_id: valueSyarat.syarat_id,
nama_persyaratan: valueSyarat.syarat_dokumen,
catatans: {
verif: valueSyarat.verif_note,
kasub: valueSyarat.kasub_note,
kabag: valueSyarat.kabag_note,
bendahara: valueSyarat.bendahara_note,
spm: valueSyarat.spm_note,
},
ms: valueSyarat.ms,
}
}),
}
})
return { details: detail, persetujuan: false, tagihan_id: tagihan_id }
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form PPSPM</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-5'></div>
<Judul>Form PPSPM</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={dataTagihan && dataTagihan.no_spp} />
<CardCustom header={'Unit PPK'} content={dataTagihan && dataTagihan.unit} />
<CardCustom header={'Jenis Tagihan'} content={dataTagihan && dataTagihan.tagihan} />
</div>
</Card>
{dataDetail.length > 0 && (
<Formik
initialValues={formikInitialValues()}
onSubmit={async (values, { setSubmitting }) => {
VerifikasiSave(values)
.then((res) => {
if (res.status == 'ok') {
Swal.fire({
title: 'Success',
text: 'Verifikasi berhasil',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/otorisasi/spm')
})
}
setSubmitting(false)
})
.catch((err) => {
setSubmitting(false)
console.log(err)
})
}}
validate={(data) => {
let errors = {}
data.details.map((values, indexTagihan) => {
const indexTagihanx = indexTagihan + 1
values.syarat.map((valuesSyarats, indexSyarat) => {
const indexSyaratx = indexSyarat + 1
if (!valuesSyarats.ms) {
errors.ms = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' MS/TMS Tidak Boleh Kosong',
})
} else if (valuesSyarats.ms === 'tms') {
if (!valuesSyarats.catatans.spm) {
errors.keterangan = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail:
'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' Catatan Tidak Boleh Kosong',
})
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, setFieldValue, isSubmitting }) => {
const handleKirim = () => {
setFieldValue('persetujuan', true)
handleSubmit()
}
const handleTolak = () => {
setFieldValue('persetujuan', false)
handleSubmit()
}
let cekTMSTolak = []
values.details.map((valRekomKabag) => {
valRekomKabag.syarat.map((valCekSyarat) => {
cekTMSTolak.push(valCekSyarat.ms)
})
})
let cekTMSTolakH = cekTMSTolak.find((isi_tms) => isi_tms === 'tms')
return (
<React.Fragment>
<Accordion
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className='mt-3 shadow'
>
{dataDetail.map((dataTagihan, indexTagihan) => {
return (
<AccordionTab header={`Tagihan ${indexTagihan + 1}`} key={indexTagihan}>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'Jenis Belanja'} content={dataTagihan.jenis_belanja} type='cream' />
<CardCustom header={'Jenis Kegiatan'} content={dataTagihan.jenis_kegiatan} type='cream' />
<CardCustom header={'Akun'} content={dataTagihan.akun_id} type='cream' />
<CardCustom header={'Keperluan Untuk'} content={dataTagihan.keperluan} type='cream' />
<div className='border text-center'>
<p className='font-semibold py-1 bg-green-100'>File</p>
<div className='flex items-center justify-center h-[40px]'>
<Link href={process.env.NEXT_PUBLIC_API_FILE + dataTagihan.filepath}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</div>
</div>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<div className='flex border rounded py-1 px-3 text-xs gap-5 w-fit mb-1'>
<div className='flex gap-3 items-center'>
<span className='bg-logo-pink1 w-10 h-3 rounded'></span>
<p>Syarat dengan status TMS (Tidak memenuhi syarat)</p>
</div>
<div className='flex gap-3 items-center'>
<span className='bg-logo-kuning1 w-10 h-3 rounded'></span>
<p>Syarat dengan catatan</p>
</div>
</div>
<Accordion className='accordion-syarat'>
{dataTagihan.syarat.map((valueSyarat, indexSyarat) => {
return (
<AccordionTab
header={valueSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName={`${
values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'
? 'p-accordion-syarat-tms'
: values.details[indexTagihan].syarat[indexSyarat].catatans.bendahara
? 'p-accordion-syarat-catatan'
: ''
}`}
>
<div className='mx-32'>
<table className='custom-table'>
<thead>
<tr>
<th>MS</th>
<th>TMS</th>
</tr>
</thead>
<tbody>
<tr>
<td width={'10%'}>
<div className='flex flex-col justify-center items-center'>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='ms'
onChange={(e) => {
handleChange(e)
setFieldValue('tolak', false)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'ms'}
/>
</div>
</td>
<td className='text-center' width={'10%'}>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='tms'
onChange={(e) => {
handleChange(e)
setFieldValue('tolak', true)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'}
/>
</td>
</tr>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat} activeIndex={2}>
<TabPanel header='Catatan Verif'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.verif`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.verif
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Verif'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.bendahara`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Bendahara'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.spm`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.spm
? values.details[indexTagihan].syarat[indexSyarat].catatans.spm
: ''
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan SPM'
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
</div>
<SubJudul className='text-lg mb-1'>Rekomendasi Kabag</SubJudul>
<InputText value={dataTagihan.rekomendasi_kabag} className='w-72' readOnly />
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center gap-x-3'>
<Button
type='button'
label='Tolak'
onClick={() => handleTolak()}
className='p-button-sm p-button-danger'
loading={isSubmitting}
disabled={cekTMSTolakH ? false : true}
/>
<Button
type='submit'
label='Kirim'
onClick={() => handleKirim()}
className='p-button-sm'
loading={isSubmitting}
disabled={cekTMSTolakH ? true : false}
/>
</div>
</React.Fragment>
)
}}
</Formik>
)}
</Belakang>
</>
)
}
+257
View File
@@ -0,0 +1,257 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { SPMList } from '@/services/otorisasi/spm-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { DataTable } from 'primereact/datatable'
import { InputText } from 'primereact/inputtext'
import { Tag } from 'primereact/tag'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function Ppspm() {
const toast = useRef(null)
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState({})
// DATATABLE
const [first, setFirst] = useState(0)
const [page, setPage] = useState(1)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
const [sort, setSort] = useState([])
const [expandedRows, setExpandedRows] = useState(null)
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
SPMList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, search, orderCol, orderDir])
const actionBodyTemplate = (rowData) => {
let all_spp_exist = 'all_spp_exist' in rowData ? rowData.all_spp_exist : true
if (all_spp_exist) {
if (rowData.detail.length > 1) {
if (rowData.nama_tagihan === 'LS') {
return (
<div className='flex gap-x-2'>
{rowData.status === 6 ? (
<Tag severity='success' value='Done'></Tag>
) : (
<Link href={`/otorisasi/spm/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
)}
</div>
)
} else {
return ''
}
} else {
return (
<div className='flex gap-x-2'>
{rowData.status === 6 ? (
<Tag severity='success' value='Done'></Tag>
) : (
<Link href={`/otorisasi/spm/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
)}
</div>
)
}
} else {
return ''
}
}
const actionBodyTemplate2 = (rowData, belomSPM, all_spp_exist) => {
return (
rowData && (
<div className='flex gap-x-2'>
{rowData.status === 6 ? (
<Tag severity='success' value='Done'></Tag>
) : rowData.status < 5 ? (
<Tag severity='warning' value='On Process'></Tag>
) : belomSPM || !all_spp_exist ? (
<Tag severity='warning' value='Waiting Others'></Tag>
) : (
<Link href={`/otorisasi/spm/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
)}
</div>
)
)
}
const datatableDetail = (data, all_spp_exist) => {
let cekStatus = []
data.map((val) => {
cekStatus.push(val.status)
})
let belomSPM = cekStatus.find((val) => val < 5)
return (
<DataTable value={data} responsiveLayout='scroll' style={{ width: '100%' }} className='dt-detail mx-20'>
<Column field='no_spp' header='No SPP'></Column>
<Column
header='Action/status'
body={(e) => actionBodyTemplate2(e, belomSPM, all_spp_exist)}
exportable={false}
></Column>
</DataTable>
)
}
const allowExpansion = (rowData) => {
return rowData.detail.length > 0 && rowData.nama_tagihan === 'GUP'
}
const expandRow = {
expandedRows: expandedRows,
onRowToggle: (e) => setExpandedRows(e.data),
responsiveLayout: 'scroll',
rowExpansionTemplate: (e) => {
let all_spp_exist = 'all_spp_exist' in e ? e.all_spp_exist : true
return datatableDetail(e.detail, all_spp_exist)
},
dataKey: 'no_spp',
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Kirim</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyTanggal = (rowData) => {
return rowData.tanggal_kirim === '-' ? rowData.tanggal_kirim : moment(rowData.tanggal_kirim).format('DD MMMM YYYY')
}
return (
<>
<Toast ref={toast} />
<Head>
<title>PPSPM</title>
</Head>
<Belakang>
<Judul>PPSPM</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
length={length}
loading={loading}
page={page}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
totalRecords={totalRecords}
sort={sort}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'tanggal':
setOrderCol(3)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
expandRow={expandRow}
>
<Column expander={allowExpansion} />
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='nama_tagihan' header='Nama Tagihan'></Column>
<Column field='spp_gabungan' header='SPP Gabungan'></Column>
<Column field='tanggal' header='Tanggal Kirim' body={bodyTanggal} sortable></Column>
<Column header='Action/status' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
+379
View File
@@ -0,0 +1,379 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { ddRekomendasi } from '@/constant/globalData'
import { TagihanGet, VerifikasiSave } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import 'moment/locale/id'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Dropdown } from 'primereact/dropdown'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { RadioButton } from 'primereact/radiobutton'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormVerifikasi() {
const router = useRouter()
const { tagihan_id } = router.query
const [dataTagihan, setDataTagihan] = useState([])
const [dataDetail, setDataDetail] = useState([])
useEffect(() => {
tagihan_id &&
TagihanGet({ ref_id: tagihan_id })
.then((res) => {
setDate(res.data[0].tanggal_bast)
setDataTagihan(res.data[0])
setDataDetail(res.data[1])
})
.catch((err) => console.log(err))
}, [tagihan_id])
const toast = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
const [date, setDate] = useState(null)
/**
* *Breadcrumb
*/
const items = [{ label: 'Verifikasi', url: '/otorisasi/verifikasi' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const formikInitialValues = () => {
const detail =
dataDetail.length > 0 &&
dataDetail.map((value) => {
return {
detail_id: value.detail_id,
jenis_belanja: value.jenis_belanja,
jenis_kegiatan: value.jenis_kegiatan,
akun: value.akun_id,
keperluan_untuk: value.keperluan,
rekom_verif: value.rekomendasi_verifikator,
tanggal_bast: date,
syarat: value.syarat.map((valueSyarat) => {
return {
syarat_id: valueSyarat.syarat_id,
nama_persyaratan: valueSyarat.syarat_dokumen,
catatans: {
verif: valueSyarat.verif_note,
bendahara: valueSyarat.bendahara_note,
spm: valueSyarat.spm_note,
},
ms: valueSyarat.ms,
}
}),
}
})
return { tanggal_bast: date, details: detail, persetujuan: true, tagihan_id: tagihan_id }
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form Verifikasi</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-5'></div>
<Judul>Form Verifikasi</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={dataTagihan && dataTagihan.no_spp} />
<CardCustom header={'Unit PPK'} content={dataTagihan && dataTagihan.unit} />
<CardCustom header={'Jenis Tagihan'} content={dataTagihan && dataTagihan.tagihan} />
</div>
</Card>
{dataDetail.length > 0 && (
<Formik
initialValues={formikInitialValues()}
onSubmit={async (values) => {
VerifikasiSave(values)
.then((res) => {
if (res.status == 'ok') {
Swal.fire({
title: 'Success',
text: 'Verifikasi berhasil',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/otorisasi/verifikasi')
})
}
})
.catch((err) => console.log(err))
}}
validate={(data) => {
let errors = {}
data.details.map((values, indexTagihan) => {
const indexTagihanx = indexTagihan + 1
if (!values.rekom_verif) {
errors.rekom = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Rekomendasi Tagihan ' + indexTagihanx + ' Tidak Boleh Kosong',
})
}
values.syarat.map((valuesSyarats, indexSyarat) => {
const indexSyaratx = indexSyarat + 1
if (!valuesSyarats.ms) {
errors.ms = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail: 'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' MS/TMS Tidak Boleh Kosong',
})
} else if (valuesSyarats.ms === 'tms') {
if (!valuesSyarats.catatans.verif) {
errors.keterangan = 'required'
toast.current.show({
severity: 'error',
summary: 'Error Message',
detail:
'Tagihan ' + indexTagihanx + ' Syarat ke ' + indexSyaratx + ' Catatan Tidak Boleh Kosong',
})
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, setFieldValue }) => {
return (
<React.Fragment>
<Accordion
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className='mt-3 shadow'
>
{dataDetail.map((dataTagihan, indexTagihan) => {
let cekTMS = []
values.details[indexTagihan].syarat.map((valueTMS) => {
cekTMS.push(valueTMS.ms)
})
let cekTMSH = cekTMS.find((isi_tms) => isi_tms === 'tms')
return (
<AccordionTab header={`Tagihan ${indexTagihan + 1}`} key={indexTagihan}>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'Jenis Belanja'} content={dataTagihan.jenis_belanja} type='biru' />
<CardCustom header={'Jenis Kegiatan'} content={dataTagihan.jenis_kegiatan} type='biru' />
<CardCustom header={'Akun'} content={dataTagihan.akun_id} type='biru' />
<CardCustom header={'Keperluan Untuk'} content={dataTagihan.keperluan} type='biru' />
{dataTagihan && dataTagihan.tanggal_bast ? (
<CardCustom
header={'Tanggal Selesai Kegiatan/serah terima'}
content={
<InputText
value={`details.${indexTagihan}.tanggal_bast`}
disabled
className='text-center'
/>
}
type='biru'
/>
) : (
<CardCustom
header={'Tanggal Selesai Kegiatan/serah terima'}
content={
<Calendar
name={`details.${indexTagihan}.tanggal_bast`}
value={values.details[indexTagihan].tanggal_bast}
onChange={handleChange}
dateFormat='dd MM yy'
showButtonBar
className='h-[40px]'
></Calendar>
}
type='biru'
/>
)}
<div className='border text-center'>
<p className='font-semibold py-1 bg-biru'>File</p>
<div className='flex items-center justify-center h-[40px]'>
<Link href={process.env.NEXT_PUBLIC_API_FILE + dataTagihan.filepath}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</div>
</div>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<div className='flex border rounded py-1 px-3 text-xs gap-5 w-fit mb-1'>
<div className='flex gap-3 items-center'>
<span className='bg-logo-pink1 w-10 h-3 rounded'></span>
<p>Syarat dengan status TMS (Tidak memenuhi syarat)</p>
</div>
<div className='flex gap-3 items-center'>
<span className='bg-logo-kuning1 w-10 h-3 rounded'></span>
<p>Syarat dengan catatan</p>
</div>
</div>
<Accordion className='accordion-syarat'>
{dataTagihan.syarat.map((valueSyarat, indexSyarat) => {
return (
<AccordionTab
header={valueSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName={`${
values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'
? 'p-accordion-syarat-tms'
: values.details[indexTagihan].syarat[indexSyarat].catatans.verif
? 'p-accordion-syarat-catatan'
: ''
}`}
>
<div className='mx-32'>
<table className='custom-table'>
<thead>
<tr>
<th>MS</th>
<th>TMS</th>
</tr>
</thead>
<tbody>
<tr>
<td width={'10%'}>
<div className='flex flex-col justify-center items-center'>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='ms'
onChange={handleChange}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'ms'}
/>
</div>
</td>
<td className='text-center' width={'10%'}>
<RadioButton
name={`details.${indexTagihan}.syarat.${indexSyarat}.ms`}
value='tms'
onChange={(e) => {
handleChange(e)
setFieldValue(
`details.${indexTagihan}.rekom_verif`,
'Ditolak/dikembalikan'
)
}}
checked={values.details[indexTagihan].syarat[indexSyarat].ms === 'tms'}
/>
</td>
</tr>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat}>
<TabPanel header='Catatan Verifikator'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.verif`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.verif
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Verifikator'
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.bendahara`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans
.bendahara
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan Bendahara'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
name={`details.${indexTagihan}.syarat.${indexSyarat}.catatans.spm`}
rows={5}
cols={30}
value={
values.details[indexTagihan].syarat[indexSyarat].catatans.spm
}
onChange={handleChange}
className='w-full'
placeholder='Masukkan catatan SPM'
readOnly
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
</div>
<SubJudul className='text-lg mb-1'>Rekomendasi</SubJudul>
<Dropdown
name={`details.${indexTagihan}.rekom_verif`}
optionLabel='name'
optionValue='name'
value={
cekTMSH === 'tms' ? 'Ditolak/dikembalikan' : values.details[indexTagihan].rekom_verif
}
options={
cekTMSH === 'tms'
? ddRekomendasi.filter((val) => val.name === 'Ditolak/dikembalikan')
: ddRekomendasi.filter((val) => val.name !== 'Ditolak/dikembalikan')
}
onChange={handleChange}
placeholder='Pilih rekomendasi'
className='w-80'
showClear={
values.details[indexTagihan].rekom_verif
? values.details[indexTagihan].rekom_verif === 'Ditolak/dikembalikan'
? false
: true
: false
}
readOnly={cekTMSH === 'tms' ? true : false}
/>
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center'>
<Button type='submit' label='Kirim' onClick={handleSubmit} className='p-button-sm' />
</div>
</React.Fragment>
)
}}
</Formik>
)}
</Belakang>
</>
)
}
+191
View File
@@ -0,0 +1,191 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Label } from '@/components/Label'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { VerifikasiList } from '@/services/otorisasi/verifikasi-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import Link from 'next/link'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { InputText } from 'primereact/inputtext'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function Verifikasi() {
const toast = useRef(null)
const [data, setData] = useState([])
const [search, setSearch] = useState('')
const [loading, setLoading] = useState(false)
// DATATABLE
const [first, setFirst] = useState(0)
const [page, setPage] = useState(0)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
const [sort, setSort] = useState([])
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
VerifikasiList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, orderCol, orderDir, search])
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Link href={`/otorisasi/verifikasi/form?tagihan_id=${rowData.tagihan_id}`}>
<Button icon='pi pi-play' label='Proses' className='p-button-sm p-button-rounded' />
</Link>
</div>
)
}
const bodyTanggal = (rowData) => {
return rowData.tanggal_kirim === '-' ? rowData.tanggal_kirim : moment(rowData.tanggal_kirim).format('DD MMMM YYYY')
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Kirim</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyUrgensi = (rowData) => {
let color
switch (rowData.urgensi.toLowerCase()) {
case 'low':
color = 'success'
break
case 'medium':
color = 'warning'
break
case 'high':
color = 'danger'
break
}
return <Label type={color}>{rowData.urgensi}</Label>
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Verifikasi</title>
</Head>
<Belakang>
<Judul>Verifikasi</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<DatatablePrimeV2
data={data}
first={first}
length={length}
page={page}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
totalRecords={totalRecords}
sort={sort}
loading={loading}
>
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='unit' header='Unit'></Column>
<Column field='tanggal' header='Tanggal Kirim' body={bodyTanggal} sortable></Column>
<Column header='Urgensi' body={bodyUrgensi}></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
@@ -0,0 +1,359 @@
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, SubJudul } from '@/components/TextCustom'
import { TagihanGetOne, TagihanSubmit } from '@/services/pengajuan-tagihan/tagihan-service'
import { Formik } from 'formik'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { FileUpload } from 'primereact/fileupload'
import { InputTextarea } from 'primereact/inputtextarea'
import { ProgressBar } from 'primereact/progressbar'
import { TabPanel, TabView } from 'primereact/tabview'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
import axios from '../../../lib/axios'
export default function Ditolak() {
const router = useRouter()
const [data, setData] = useState([])
const toast = useRef(null)
const [progressUpload, setProgressUpload] = useState(0)
useEffect(() => {
TagihanGetOne({ ref_id: router.query.ditolak }).then((res) => {
if (res.status === 'ok') {
setData(res.data)
} else {
console.log(res.message)
}
})
}, [router])
const formikInitialValues = () => {
let groupTagihan = []
let detailIDTolak = []
data.length > 0 &&
data[1].map((valData) => {
valData.syarat.map((valSyarat) => {
if (valSyarat.ms === 'tms') {
let cek_detail_id = detailIDTolak.find((e) => e === valData.detail_id)
!cek_detail_id && detailIDTolak.push(valData.detail_id)
}
})
})
detailIDTolak.map((valDetailIDTolak, iDetailIDTolak) => {
data.length > 0 &&
data[1]
.filter((valTagihan) => valTagihan.detail_id === detailIDTolak[iDetailIDTolak])
.map((valData) => {
groupTagihan.push({
detail_id: valData.detail_id,
jenis_belanja: valData.jenis_belanja,
jenis_kegiatan: valData.jenis_kegiatan,
syarat: valData.syarat.map((valSyarat) => {
if (valSyarat.ms === 'tms') {
return {
syarat_id: valSyarat.syarat_id,
syarat_dokumen: valSyarat.syarat_dokumen,
keterangan: valSyarat.keterangan,
file: '',
file_name: '',
ms: valSyarat.ms,
verif_note: valSyarat.verif_note,
kasub_note: valSyarat.kasub_note,
kabag_note: valSyarat.kabag_note,
bendahara_note: valSyarat.bendahara_note,
spm_note: valSyarat.spm_note,
}
}
}),
akun_id: valData.akun_id,
filepath_old: valData.filepath,
keperluan: valData.keperluan,
draft: false,
})
})
})
return {
tagihan_id: data[0].tagihan_id,
detail_tagihan: groupTagihan,
}
}
return (
<React.Fragment>
<Toast ref={toast} />
<Head>
<title>Pengajuan Tagihan Ulang</title>
</Head>
<Belakang>
{data.length > 0 && (
<React.Fragment>
<div className='mb-5'></div>
<Judul>Form Pengajuan Tagihan Ulang</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={data[0].no_spp} />
<CardCustom header={'Unit PPK'} content={data[0].unit} />
<CardCustom header={'Jenis Tagihan'} content={data[0].tagihan} />
</div>
</Card>
<Formik
initialValues={formikInitialValues()}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true)
TagihanSubmit({ ref_id: data[0].tagihan_id })
.then((res) => {
if (res.status === 'ok') {
Swal.fire({
title: 'Success',
text: 'Tagihan berhasil dikirim',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
setSubmitting(false)
router.push('/pengajuan-tagihan')
})
}
})
.catch((err) => {
setSubmitting(false)
console.log(err)
})
}}
validate={(data) => {
let errors = {}
data.detail_tagihan.map((val) => {
if (!val.file) {
toast.current.show({
severity: 'error',
detail: 'file belum diupload',
closable: false,
})
errors.filepath = val
}
})
return errors
}}
validateOnChange={false}
>
{({ values, setFieldValue, handleSubmit, isSubmitting }) => {
const handleRevisi = () => {
handleSubmit()
}
return (
<React.Fragment>
<Accordion className='mt-3 shadow'>
{values.detail_tagihan.map((valDetailTagihan, indexDetailTagihan) => {
const customBase64Uploader = async (event) => {
// convert file to base64 encoded
const file = event.files[0]
if (file) {
if (file.type === 'application/pdf') {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = async function () {
const base64data = reader.result
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, base64data)
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file_name`, file.name)
const kirimSatuan = {
detail_id: valDetailTagihan.detail_id,
file: base64data,
file_name: file.name,
}
await axios
.post('tagihan/revisi_detail', kirimSatuan, {
headers: {
'Content-Type': 'application/json',
},
onUploadProgress: (progresEvent) => {
const percentedCompleted = Math.round(
(progresEvent.loaded * 100) / progresEvent.total
)
setProgressUpload(percentedCompleted)
},
})
.then(() => {
toast.current.show({
severity: 'success',
detail: 'File berhasil diupload',
closable: false,
})
})
.catch((err) => console.log(err))
}
} else {
event.options.clear()
toast.current.show({
severity: 'error',
detail: 'Invalid format file',
closable: false,
})
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, null)
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file_name`, null)
}
}
}
return (
<AccordionTab
header={`Tagihan ` + (parseInt(indexDetailTagihan) + 1)}
key={indexDetailTagihan}
>
<div className='grid grid-cols-2 gap-3'>
<CardCustom
header={'Jenis Belanja'}
content={valDetailTagihan.jenis_belanja}
type='cream'
/>
<CardCustom
header={'Jenis Kegiatan'}
content={valDetailTagihan.jenis_kegiatan}
type='cream'
/>
<CardCustom header={'Akun'} content={valDetailTagihan.akun_id} type='cream' />
<CardCustom
header={'Keperluan Untuk'}
content={valDetailTagihan.keperluan}
type='cream'
/>
</div>
<div className='my-3'>
<SubJudul className='text-lg mb-1'>Syarat</SubJudul>
<Accordion className='accordion-syarat'>
{valDetailTagihan.syarat.map((valSyarat, indexSyarat) => {
if (valSyarat) {
return (
<AccordionTab
header={valSyarat.syarat_dokumen}
key={indexSyarat}
headerClassName='accordiontab-revisi'
>
<table className='custom-table' key={indexSyarat}>
<tbody>
<tr className=''>
<td colSpan={4}>
<p className='text-xl font-semibold mb-1'>Catatan</p>
<TabView key={indexSyarat}>
<TabPanel header='Catatan Verif'>
<InputTextarea
rows={5}
cols={30}
value={valSyarat.verif_note ? valSyarat.verif_note : ''}
className='w-full'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan Bendahara'>
<InputTextarea
rows={5}
cols={30}
value={valSyarat.bendahara_note ? valSyarat.bendahara_note : ''}
className='w-full'
readOnly
/>
</TabPanel>
<TabPanel header='Catatan SPM'>
<InputTextarea
rows={5}
cols={30}
value={valSyarat.spm_note ? valSyarat.spm_note : ''}
className='w-full'
readOnly
/>
</TabPanel>
</TabView>
</td>
</tr>
</tbody>
</table>
</AccordionTab>
)
}
})}
</Accordion>
</div>
<div className='my-3'>
<table className='custom-table'>
<thead>
<tr>
<th>File Sebelum</th>
<th>Upload File Revisi</th>
</tr>
</thead>
<tbody>
<tr>
<td className='text-center'>
<Link href={process.env.NEXT_PUBLIC_BACKEND_API + valDetailTagihan.filepath_old}>
<a target={'_blank'} className='flex items-center justify-center gap-x-1'>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>Klik disini untuk lihat file</p>
</a>
</Link>
</td>
<td>
<FileUpload
name='upload-syarat'
url={'#'}
accept='.pdf'
emptyTemplate={<p className='m-0'>Drag and drop files to here to upload.</p>}
customUpload
uploadHandler={(e) => customBase64Uploader(e)}
onClear={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
onRemove={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
uploadOptions={{
className: values.detail_tagihan[indexDetailTagihan].file ? 'p-disabled' : '',
}}
cancelOptions={{
className: values.detail_tagihan[indexDetailTagihan].file ? 'p-disabled' : '',
}}
progressBarTemplate={
<ProgressBar
value={progressUpload}
style={{ height: '1rem', fontSize: '13px' }}
></ProgressBar>
}
/>
</td>
</tr>
</tbody>
</table>
</div>
</AccordionTab>
)
})}
</Accordion>
<div className='mt-3 flex justify-center gap-x-3'>
<Button
type='submit'
label='Kirim'
onClick={() => handleRevisi()}
className='p-button-sm'
loading={isSubmitting}
/>
</div>
</React.Fragment>
)
}}
</Formik>
</React.Fragment>
)}
</Belakang>
</React.Fragment>
)
}
+651
View File
@@ -0,0 +1,651 @@
import { CardCustom } from '@/components/CardCustom'
import { Belakang } from '@/components/Layouts'
import { Judul, LabelInput } from '@/components/TextCustom'
import {
TagihanAddDetail,
TagihanGetOne,
TagihanResubmit,
TagihanSubmit,
} from '@/services/pengajuan-tagihan/tagihan-service'
import { AkunList } from '@/services/referensi/akun-service'
import { JenisKegiatanList } from '@/services/referensi/jenisKegiatan-service'
import { PersyaratanSearch } from '@/services/referensi/persyaratan-service'
import { FieldArray, Formik } from 'formik'
import { fetchAPI } from 'lib/fetch'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { Dropdown } from 'primereact/dropdown'
import { FileUpload } from 'primereact/fileupload'
import { InputTextarea } from 'primereact/inputtextarea'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function Draft({ optionJenisBelanja = null }) {
const router = useRouter()
const [data, setData] = useState([])
const toast = useRef(null)
const [optionJenisKegiatan, setOptionJenisKegiatan] = useState([])
const [optionAkun, setOptionAkun] = useState([])
const [countJenisTagihan, setCountJenisTagihan] = useState(0)
const [kirimLoading, setKirimLoading] = useState(false)
const [tagihanID, setTagihanID] = useState('')
const [statusDraft, setStatusDraft] = useState(false)
useEffect(() => {
TagihanGetOne({ ref_id: router.query.draft }).then((res) => {
if (res.status === 'ok') {
setData(res.data)
setTagihanID(res.data[0].tagihan_id)
} else {
console.log(res.message)
}
})
JenisKegiatanList()
.then((res) => setOptionJenisKegiatan(res.data))
.catch((err) => console.log(err))
}, [router])
useEffect(() => {
let params = {}
params.draw = 0
AkunList(params).then((res) => setOptionAkun(res.data))
}, [])
const formikInitialValues = () => {
let lastIndex = 0
if (data.length > 0) {
lastIndex = data[1].length - 1
}
const init = data.length > 0 && {
jenis_tagihan_id: data[0].jenis_tagihan_id,
tagihan_id: data[0].tagihan_id,
detail_tagihan: [
{
detail_id: data[1][lastIndex].detail_id,
jenis_belanja: data[1][lastIndex].jenis_belanja_id,
jenis_kegiatan: data[1][lastIndex].jenis_kegiatan_id,
filepath: data[1][lastIndex].filepath,
syarat: data[1][lastIndex].syarat.map((valSyarat) => {
return {
syarat_id: valSyarat.syarat_id,
syarat_dokumen: valSyarat.syarat_dokumen,
keterangan: valSyarat.keterangan,
}
}),
akun_id: data[1][lastIndex].akun_id,
keperluan: data[1][lastIndex].keperluan,
draft: false,
status: false,
},
],
dataIndex: 0, //* For select data index
}
return init
}
const handleBtnKirim = () => {
Swal.fire({
title: 'Apakah data diatas sudah benar semua?',
text: 'Tagihan yang sudah dikirim tidak bisa diubah kembali',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, kirim!',
}).then((result) => {
if (result.isConfirmed) {
setKirimLoading(true)
TagihanSubmit({ ref_id: tagihanID })
.then((res) => {
if (res.status == 'ok') {
setKirimLoading(false)
Swal.fire({
title: 'Success',
text: 'Tagihan berhasil dikirim',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/pengajuan-tagihan')
})
} else {
setKirimLoading(false)
}
})
.catch((err) => {
console.log(err)
setKirimLoading(false)
})
}
})
}
return (
<React.Fragment>
<Toast ref={toast} />
<Head>
<title>Pengajuan Tagihan Draft</title>
</Head>
<Belakang>
{data.length > 0 && (
<React.Fragment>
<div className='mb-5'></div>
<Judul>Form Persetujuan Draft</Judul>
<Card>
<div className='grid grid-cols-2 gap-3'>
<CardCustom header={'No SPP'} content={data[0].no_spp} />
<CardCustom header={'Unit PPK'} content={data[0].unit} />
<CardCustom header={'Jenis Tagihan'} content={data[0].tagihan} />
</div>
</Card>
<Formik
initialValues={formikInitialValues()}
onSubmit={(data, { setSubmitting, setFieldValue }) => {
setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', true)
/**
* *Filter data detail tagihan follow the button kirim
*/
const filterData = data.detail_tagihan.find((item, i) => i === data.dataIndex)
/**
* *Create new format data detail tagihan reference to API
*/
const dataKirim = {
tagihan_id: data.tagihan_id,
detail_tagihan: [filterData],
}
if (data.dataIndex) {
TagihanAddDetail(dataKirim)
.then((res) => {
if (res.status === 'ok') {
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
if (statusDraft) {
router.push('/pengajuan-tagihan')
}
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', true)
setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
setSubmitting(false)
} else {
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
}
})
.catch((err) => {
console.log(err)
toast.current.show({
severity: 'error',
detail: 'Error saat pembuatan data/file belum diupload',
closable: false,
})
})
} else {
TagihanResubmit(dataKirim)
.then((res) => {
if (res.status === 'ok') {
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
if (statusDraft) {
router.push('/pengajuan-tagihan')
}
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', true)
setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
setSubmitting(false)
} else {
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
}
})
.catch((err) => {
console.log(err)
toast.current.show({
severity: 'error',
detail: 'Error saat pembuatan data/file belum diupload',
closable: false,
})
})
}
}}
validate={(data) => {
let errors = {}
data.detail_tagihan.map((val) => {
val.syarat.map((valSyarat, indexSyarat) => {
if (valSyarat) {
if (!valSyarat.file && valSyarat.ms === 'tms') {
errors.file = { ...errors.file, [indexSyarat]: 'File Harus Diisi ' }
}
}
})
})
return errors
}}
validateOnChange={false}
>
{({ values, setFieldValue, handleSubmit, isSubmitting, handleChange }) => {
/**
* *Form Detail Tagihan
* *Button Submit and draft
*/
const handleSubmitDetailTagihan = (e, { draft, index }) => {
e.preventDefault()
setFieldValue(`detail_tagihan[${index}].status`, draft)
setStatusDraft(draft)
handleSubmit()
}
/**
* *Change Jenis Belanja
* *Reset form detail tagihan
*/
const changeJenisBelanja = (event, index) => {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
setFieldValue(`detail_tagihan[${index}].jenis_belanja`, event.value)
/**
* SHOW LIST JENIS KEGIATAN WHERE JENIS_BELANJA_ID
*/
JenisKegiatanList({ jenis_belanja_id: event.value, draw: 0 })
.then((res) => setOptionJenisKegiatan(res.data))
.catch((err) => console.log(err))
}
/**
* *Dropdown list jenis kegiatan
* *When jenis kegiatan change hit API to get list syarat
*/
const changeJenisKegiatan = (event, index) => {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
setFieldValue(`detail_tagihan[${index}].jenis_kegiatan`, event.value)
PersyaratanSearch({
jenis_tagihan_id: values.jenis_tagihan_id,
jenis_belanja_id: values.detail_tagihan[index].jenis_belanja,
jenis_kegiatan_id: event.value,
})
.then((res) => {
if (res.data) {
res.data.map((item, index2) => {
setFieldValue(`detail_tagihan[${index}].syarat[${index2}].syarat_id`, item.syarat_id)
setFieldValue(
`detail_tagihan[${index}].syarat[${index2}].syarat_dokumen`,
item.syarat_dokumen
)
setFieldValue(`detail_tagihan[${index}].syarat[${index2}].keterangan`, item.keterangan)
})
} else {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
toast.current.show({
severity: 'info',
detail: 'Data syarat kosong',
closable: false,
})
}
})
.catch(() => {
toast.current.show({
severity: 'error',
detail: 'Error while get data',
closable: false,
})
})
}
return (
<React.Fragment>
<FieldArray name='detail_tagihan'>
{({ push }) => (
<React.Fragment>
<Accordion className='mt-3 shadow'>
{values.detail_tagihan.length > 0 &&
values.detail_tagihan.map((valDetailTagihan, indexDetailTagihan) => {
const customBase64Uploader = async (event) => {
// convert file to base64 encoded
const file = event.files[0]
if (file) {
if (file.type === 'application/pdf') {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function () {
const base64data = reader.result
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, base64data)
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file_name`, file.name)
}
toast.current.show({
severity: 'success',
detail: 'File berhasil diupload',
closable: false,
})
} else {
event.options.clear()
toast.current.show({
severity: 'error',
detail: 'Invalid format file',
closable: false,
})
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, null)
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file_name`, null)
}
}
}
return (
<AccordionTab header={`Tagihan ${indexDetailTagihan + 1}`} key={indexDetailTagihan}>
<div className='p-fluid grid grid-cols-2 gap-3'>
{/* JENIS BELANJA */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Belanja</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${indexDetailTagihan}].jenis_belanja`}
options={optionJenisBelanja}
optionLabel='nama'
optionValue='jenis_id'
value={values.detail_tagihan[indexDetailTagihan].jenis_belanja}
onChange={(e) => changeJenisBelanja(e, indexDetailTagihan)}
showClear={
values.detail_tagihan[indexDetailTagihan] !== undefined ? true : false
}
scrollHeight='200px'
placeholder='Select Jenis Belanja'
/>
{/* {errorFieldMessageDetail(indexDetailTagihan, 'jenis_belanja')} */}
</div>
{/* JENIS KEGIATAN */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Kegiatan</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${indexDetailTagihan}].jenis_kegiatan`}
options={optionJenisKegiatan}
optionLabel='nama'
optionValue='jenis_id'
value={values.detail_tagihan[indexDetailTagihan].jenis_kegiatan}
onChange={(e) => changeJenisKegiatan(e, indexDetailTagihan)}
showClear={
values.detail_tagihan[indexDetailTagihan] !== undefined ? true : false
}
scrollHeight='200px'
placeholder='Select Jenis Kegiatan'
filter
/>
{/* {errorFieldMessageDetail(indexDetailTagihan, 'jenis_kegiatan')} */}
</div>
</div>
{/* SYARAT */}
<div className='p-fluid grid grid-cols-2 gap-3'>
<div className='md:col-4 my-3'>
<LabelInput>Syarat</LabelInput>
<div className='relative mt-2 flex overflow-auto h-96'>
<table className='w-full text-sm text-left '>
<thead className='text-xs uppercase bg-gray-100 sticky top-0'>
<tr>
<th scope='col' className='py-3 px-6'>
Persyaratan
</th>
<th scope='col' className='py-3 px-6'>
Keterangan
</th>
</tr>
</thead>
<tbody>
{valDetailTagihan.syarat.map((item, index2) => (
<tr className='bg-white border-b' key={index2}>
<td scope='row' className='py-4 px-6'>
{item.syarat_dokumen}
</td>
<td scope='row' className='py-4 px-6'>
{item.keterangan}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className='upload-all my-3'>
<LabelInput>Upload Syarat</LabelInput>
<div className='py-4 px-6'>
{valDetailTagihan.file || valDetailTagihan.filepath ? (
<div className=''>
<div className='border mb-3'>
<div className='bg-abu3 px-2 py-1'>
<p>File Sebelum</p>
</div>
<div className='flex items-center justify-center h-[40px] py-8'>
<Link
href={
valDetailTagihan.file
? process.env.NEXT_PUBLIC_API_FILE + valDetailTagihan.file
: process.env.NEXT_PUBLIC_API_FILE + valDetailTagihan.filepath
}
>
<a
target={'_blank'}
className='flex items-center justify-center gap-x-1'
>
<i className='pi pi-file-pdf' style={{ fontSize: '2em' }} />
<p className='text-sm hover:underline'>
Klik disini untuk lihat file
</p>
</a>
</Link>
</div>
</div>
<div className=''>
<FileUpload
name='upload-syarat'
url={'#'}
accept='.pdf'
// maxFileSize={1000000}
emptyTemplate={
<p className='m-0'>Drag and drop files to here to upload.</p>
}
customUpload
uploadHandler={(e) => customBase64Uploader(e)}
onClear={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
onRemove={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
/>
</div>
</div>
) : (
<FileUpload
name='upload-syarat'
url={'#'}
accept='.pdf'
// maxFileSize={1000000}
emptyTemplate={
<p className='m-0'>Drag and drop files to here to upload.</p>
}
customUpload
uploadHandler={(e) => customBase64Uploader(e)}
onClear={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
onRemove={() =>
setFieldValue(`detail_tagihan[${indexDetailTagihan}].file`, false)
}
/>
)}
</div>
</div>
</div>
{/* AKUN */}
<div className='p-fluid'>
<div className='field col-6 md:col-4 my-3 w-1/2'>
<LabelInput>Akun</LabelInput>
<Dropdown
panelClassName='p-dropdown-form dd-panel-custom'
name={`detail_tagihan[${indexDetailTagihan}].akun_id`}
options={optionAkun}
optionLabel={(item) => `${item.akun_id} - ${item.nama}`}
optionValue='akun_id'
value={parseInt(values.detail_tagihan[indexDetailTagihan].akun_id)}
onChange={handleChange}
showClear={
values.detail_tagihan[indexDetailTagihan] !== undefined ? true : false
}
scrollHeight='300px'
placeholder='Select Akun'
filter
maxLength={100}
/>
{/* {errorFieldMessageDetail(indexDetailTagihan, 'akun')} */}
</div>
<div className='field col-12 md:col-4 my-3'>
<LabelInput>Keperluan</LabelInput>
<InputTextarea
name={`detail_tagihan[${indexDetailTagihan}].keperluan`}
rows={5}
cols={30}
value={
values.detail_tagihan[indexDetailTagihan].keperluan
? values.detail_tagihan[indexDetailTagihan].keperluan
: ''
}
onChange={handleChange}
placeholder='Masukkan detail keperluan'
/>
</div>
</div>
<div className='mt-3 flex justify-center gap-x-3'>
{values.detail_tagihan[indexDetailTagihan].status === true ? (
<div className='flex flex-col'>
<label className='p-2 bg-green-300 rounded shadow text-center'>
<i className='pi pi-check'></i> Tagihan Berhasil dikirim/draft
</label>
<small className='text-red-400'>
Tagihan ini hanya bisa didelete, tidak bisa diubah kembali!
</small>
</div>
) : (
<React.Fragment>
<Button
type='submit'
label='Draft'
icon='pi pi-save'
onClick={(e) =>
handleSubmitDetailTagihan(e, { draft: true, index: indexDetailTagihan })
}
className='p-button-sm p-button-info'
loading={isSubmitting}
/>
<Button
type='submit'
label='Simpan'
icon='pi pi-send'
onClick={(e) => {
handleSubmitDetailTagihan(e, { draft: false, index: indexDetailTagihan })
}}
className='p-button-sm p-button-success'
loading={isSubmitting}
/>
</React.Fragment>
)}
</div>
</AccordionTab>
)
})}
</Accordion>
<div className='flex justify-end gap-2 mt-3'>
<Button
label='Kirim'
icon={`pi ${kirimLoading ? 'pi-spin pi-spinner' : 'pi-send'}`}
className='p-button-sm p-button-info'
onClick={handleBtnKirim}
disabled={
values.detail_tagihan[countJenisTagihan] !== undefined &&
values.detail_tagihan[countJenisTagihan].status === true
? false
: true
}
/>
<Button
type='submit'
label='Tambah Tagihan'
icon='pi pi-plus'
className='p-button-sm p-button-primary'
onClick={() => {
setCountJenisTagihan(countJenisTagihan + 1)
setFieldValue('dataIndex', countJenisTagihan + 1)
push({
detail_id: values.detail_tagihan[0].detail_id,
jenis_belanja: '',
jenis_kegiatan: '',
syarat: [],
})
}}
disabled={
values.detail_tagihan[countJenisTagihan] !== undefined &&
values.detail_tagihan[countJenisTagihan].status === true
? false
: true
}
/>
</div>
</React.Fragment>
)}
</FieldArray>
</React.Fragment>
)
}}
</Formik>
</React.Fragment>
)}
</Belakang>
</React.Fragment>
)
}
export const getServerSideProps = async (ctx) => {
let token = ctx.req.cookies.token
// const optionJenisTagihan = await fetchAPI('jenis-tagihan/list', token)
const optionJenisBelanja = await fetchAPI('jenis-belanja/list', token)
return {
props: {
// optionJenisTagihan,
optionJenisBelanja,
},
}
}
@@ -0,0 +1,710 @@
import { Belakang } from '@/components/Layouts'
import { Judul, LabelInput } from '@/components/TextCustom'
import { DetailSPPGet, NoSPPListGet } from '@/services/pengajuan-tagihan/integrasi-service'
import { TagihanAddDetail, TagihanGetOne, TagihanSubmit } from '@/services/pengajuan-tagihan/tagihan-service'
import { AkunList } from '@/services/referensi/akun-service'
import { JenisKegiatanList } from '@/services/referensi/jenisKegiatan-service'
import { PersyaratanSearch } from '@/services/referensi/persyaratan-service'
import { Formik } from 'formik'
import { fetchAPI } from 'lib/fetch'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BlockUI } from 'primereact/blockui'
import { Button } from 'primereact/button'
import { Card } from 'primereact/card'
import { Dropdown } from 'primereact/dropdown'
import { FileUpload } from 'primereact/fileupload'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function ForceClose({ optionJenisTagihan = null, optionJenisBelanja = null }) {
const router = useRouter()
const [data, setData] = useState([{ no_spp: '', tgl_spp: '', jenis_tagihan: '' }])
const toast = useRef(null)
const [countJenisTagihan, setCountJenisTagihan] = useState(0)
const [optionJenisKegiatan, setOptionJenisKegiatan] = useState([])
const [optionNoSpp, setOptionNoSpp] = useState([])
const [optionAkun, setOptionAkun] = useState([])
const [statusDraft, setStatusDraft] = useState(false)
const [kirimLoading, setKirimLoading] = useState(false)
useEffect(() => {
TagihanGetOne({ ref_id: router.query.force }).then((res) => {
if (res.status === 'ok') {
setData(res.data)
DetailSPPGet({ no_spp: res.data[0].no_spp }).then((res) => {
if (res.data.akun.length > 0) {
setOptionAkun(res.data.akun)
} else {
let params = {}
params.draw = 0
AkunList(params).then((res) => {
setOptionAkun(res.data)
})
}
})
} else {
console.log(res.message)
}
})
NoSPPListGet()
.then((res) => setOptionNoSpp(res.data))
.catch((err) => console.log(err))
}, [router])
return (
<React.Fragment>
<Toast ref={toast} />
<Head>
<title>Pengajuan Tagihan Ulang</title>
</Head>
<Belakang>
<Judul>Form Pengajuan Tagihan</Judul>
<Card>
<Formik
initialValues={{}}
onSubmit={() => {
setCountJenisTagihan(countJenisTagihan + 1)
}}
>
{({ handleSubmit, setFieldValue }) => {
const handleBtnKirim = () => {
Swal.fire({
title: 'Apakah data diatas sudah benar semua?',
text: 'Tagihan yang sudah dikirim tidak bisa diubah kembali',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, kirim!',
}).then((result) => {
if (result.isConfirmed) {
setKirimLoading(true)
TagihanSubmit({ ref_id: data[0].tagihan_id })
.then((res) => {
if (res.status == 'ok') {
setKirimLoading(false)
Swal.fire({
title: 'Success',
text: 'Tagihan berhasil dikirim',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/pengajuan-tagihan')
})
} else {
setKirimLoading(false)
}
})
.catch((err) => {
console.log(err)
setKirimLoading(false)
})
}
})
}
const handleTambahTagihan = (e) => {
e.preventDefault()
Swal.fire({
title: 'Apakah data yang diinputkan sudah benar benar?',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, benar!',
cancelButtonText: 'Tidak',
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Pastikan kembali!',
text: 'Kamu tidak bisa pakai data ini kembali!',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, benar!',
cancelButtonText: 'Tidak',
}).then((result) => {
if (result.isConfirmed) {
setFieldValue('dataIndex', countJenisTagihan)
handleSubmit()
}
})
}
})
}
return (
<React.Fragment>
<div className='p-fluid grid grid-cols-2 gap-3'>
<div className='field col-12 md:col-4'>
<LabelInput>No SPP</LabelInput>
<InputText disabled value={data[0].no_spp} />
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Tanggal SPP</LabelInput>
<InputText disabled value={data[0].tgl_spp} />
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Tagihan</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name='jenis_tagihan'
options={optionJenisTagihan}
optionLabel='nama'
optionValue='jenis_id'
value={data[0].jenis_tagihan_id}
placeholder='Select Jenis Tagihan'
disabled={true}
/>
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Jenis SPP</LabelInput>
<InputText disabled value='' />
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Uraian</LabelInput>
<InputTextarea rows={5} cols={30} value='' disabled />
</div>
</div>
{/* DETAIL TAGIHAN */}
<Formik
initialValues={{
tagihan_id: '',
detail_tagihan: [],
dataIndex: 0, //* For select data index
}}
onSubmit={(data_detail_tagihan) => {
/**
* *Filter data detail tagihan follow the button kirim
*/
const filterData = data_detail_tagihan.detail_tagihan.find(
(item, i) => i === data_detail_tagihan.dataIndex
)
/**
* *Create new format data detail tagihan reference to API
*/
const dataKirim = {
tagihan_id: data[0].tagihan_id,
detail_tagihan: [filterData],
}
TagihanAddDetail(dataKirim)
.then((res) => {
if (res.status === 'ok') {
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
if (statusDraft) {
router.push('/pengajuan-tagihan')
}
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', true)
} else {
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
}
})
.catch((err) => {
console.log(err)
toast.current.show({
severity: 'error',
detail: 'Error saat pembuatan data/file belum diupload',
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
})
}}
validate={(data) => {
let errorsArray = []
let errors = {}
if (!data.detail_tagihan[data.dataIndex]) {
errors.jenis_belanja = 'Jenis Belanja is required.'
errors.jenis_kegiatan = 'Jenis Kegiatan is required.'
errors.akun = 'Akun is required.'
}
if (
data.detail_tagihan[data.dataIndex] &&
data.detail_tagihan[data.dataIndex].jenis_belanja === ''
) {
errors.jenis_belanja = 'Jenis Belanja is required dong.'
}
if (data.detail_tagihan[data.dataIndex] && !data.detail_tagihan[data.dataIndex].jenis_kegiatan) {
errors.jenis_kegiatan = 'Jenis Kegiatan is required.'
}
if (data.detail_tagihan[data.dataIndex] && !data.detail_tagihan[data.dataIndex].akun_id) {
errors.akun = 'Akun is required.'
}
if (Object.keys(errors).length > 0) {
errorsArray.push({ dataIndex: countJenisTagihan - 1, errors })
}
return errorsArray
}}
validateOnChange={false}
>
{({ values, handleChange, handleSubmit, setFieldValue, setValues, errors }) => {
/**
* *Message error for formik detail tagihan
*/
const isFieldValidDetail = (index, field) =>
errors[0] && errors[0].dataIndex === index && errors[0].errors[field]
const errorFieldMessageDetail = (index, field) => {
return (
isFieldValidDetail(index, field) && (
<small className='p-error' style={{ fontSize: '11px' }}>
{errors[0].errors[field]}
</small>
)
)
}
/**
* *Dropdown list jenis kegiatan
* *When jenis kegiatan change hit API to get list syarat
*/
const changeJenisKegiatan = (event, index) => {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
setFieldValue(`detail_tagihan[${index}].jenis_kegiatan`, event.value)
PersyaratanSearch({
jenis_tagihan_id: data[0].jenis_tagihan_id,
jenis_belanja_id: values.detail_tagihan[index].jenis_belanja,
jenis_kegiatan_id: event.value,
})
.then((res) => {
if (res.data) {
res.data.map((item, index2) => {
setFieldValue(`detail_tagihan[${index}].syarat[${index2}].syarat_id`, item.syarat_id)
setFieldValue(
`detail_tagihan[${index}].syarat[${index2}].syarat_dokumen`,
item.syarat_dokumen
)
setFieldValue(`detail_tagihan[${index}].syarat[${index2}].keterangan`, item.keterangan)
})
} else {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
toast.current.show({
severity: 'info',
detail: 'Data syarat kosong',
closable: false,
})
}
})
.catch(() => {
toast.current.show({
severity: 'error',
detail: 'Error while get data',
closable: false,
})
})
}
/**
* *Change Jenis Belanja
* *Reset form detail tagihan
*/
const changeJenisBelanja = (event, index) => {
setFieldValue(`detail_tagihan[${index}].syarat`, [])
setFieldValue(`detail_tagihan[${index}].jenis_belanja`, event.value)
/**
* SHOW LIST JENIS KEGIATAN WHERE JENIS_BELANJA_ID
*/
JenisKegiatanList({ jenis_belanja_id: event.value, draw: 0 })
.then((res) => setOptionJenisKegiatan(res.data))
.catch((err) => console.log(err))
}
const headerTemplate = (index) => {
const handleDelete = (index) => {
setValues({
values,
detail_tagihan: values.detail_tagihan.filter((item, i) => i !== index),
})
setCountJenisTagihan(countJenisTagihan - 1)
}
return (
<div className='flex justify-between items-center w-full'>
<p>Tagihan {index + 1}</p>
<Button
type='button'
icon='pi pi-times'
className='p-button-rounded p-button-danger p-button-text button-close-tagihan'
aria-label='Cancel'
onClick={() => handleDelete(index)}
title='Delete tagihan'
/>
</div>
)
}
/**
* *Form Detail Tagihan
* *Button Submit and draft
*/
const handleSubmitDetailTagihan = (e, { draft, index }) => {
e.preventDefault()
setStatusDraft(draft)
setFieldValue(`detail_tagihan[${index}].draft`, draft)
setFieldValue(`detail_tagihan[${index}].status`, true)
handleSubmit()
}
const tagihanList = []
const tagihan = (index) => {
const customBase64Uploader = async (event) => {
// convert file to base64 encoded
const file = event.files[0]
if (file) {
if (file.type === 'application/pdf') {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function () {
const base64data = reader.result
setFieldValue(`detail_tagihan[${index}].file`, base64data)
setFieldValue(`detail_tagihan[${index}].file_name`, file.name)
}
toast.current.show({
severity: 'success',
detail: 'File berhasil diupload',
closable: false,
})
} else {
event.options.clear()
toast.current.show({
severity: 'error',
detail: 'Invalid format file',
closable: false,
})
setFieldValue(`detail_tagihan[${index}].file`, null)
setFieldValue(`detail_tagihan[${index}].file_name`, null)
}
}
}
return (
<AccordionTab
key={index}
headerTemplate={headerTemplate(index)}
className={`accordion-tab-${index}`}
>
<BlockUI
blocked={
values.detail_tagihan[index] !== undefined && values.detail_tagihan[index].status
? true
: false
}
className='block-ui-custom cursor-not-allowed'
>
<div className='p-fluid grid grid-cols-2 gap-3'>
{/* JENIS BELANJA */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Belanja</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${index}].jenis_belanja`}
options={optionJenisBelanja}
optionLabel='nama'
optionValue='jenis_id'
value={
values.detail_tagihan[index] !== undefined
? values.detail_tagihan[index].jenis_belanja
: setFieldValue(`detail_tagihan[${index}].jenis_belanja`, '')
}
onChange={(e) => changeJenisBelanja(e, index)}
showClear={values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='200px'
placeholder='Select Jenis Belanja'
className={`${errorFieldMessageDetail(index, 'jenis_belanja') && 'p-invalid'}`}
/>
{errorFieldMessageDetail(index, 'jenis_belanja')}
</div>
{/* JENIS KEGIATAN */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Kegiatan</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${index}].jenis_kegiatan`}
options={optionJenisKegiatan}
optionLabel='nama'
optionValue='jenis_id'
value={
values.detail_tagihan[index] !== undefined
? values.detail_tagihan[index].jenis_kegiatan
: setFieldValue(`detail_tagihan[${index}].jenis_kegiatan`, '')
}
onChange={(e) => changeJenisKegiatan(e, index)}
showClear={values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='200px'
placeholder='Select Jenis Kegiatan'
filter
className={`${errorFieldMessageDetail(index, 'jenis_kegiatan') && 'p-invalid'}`}
/>
{errorFieldMessageDetail(index, 'jenis_kegiatan')}
</div>
</div>
{/* SYARAT */}
<div className='p-fluid grid grid-cols-2 gap-3'>
<div className='md:col-4 my-3'>
<LabelInput>Syarat</LabelInput>
<div className='relative mt-2 flex overflow-auto h-96'>
<table className='w-full text-sm text-left '>
<thead className='text-xs uppercase bg-gray-100 sticky top-0'>
<tr>
<th scope='col' className='py-3 px-6'>
Persyaratan
</th>
<th scope='col' className='py-3 px-6'>
Keterangan
</th>
</tr>
</thead>
<tbody>
{values.detail_tagihan[index] &&
values.detail_tagihan[index].syarat &&
values.detail_tagihan[index].syarat.map((item, index2) => (
<tr className='bg-white border-b' key={index2}>
<td scope='row' className='py-4 px-6'>
{item.syarat_dokumen}
</td>
<td scope='row' className='py-4 px-6'>
{item.keterangan}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className='upload-all my-3'>
<LabelInput>Upload Syarat</LabelInput>
<div className='py-4 px-6'>
<FileUpload
name='upload-syarat'
url={'#'}
accept='.pdf'
// maxFileSize={1000000}
emptyTemplate={<p className='m-0'>Drag and drop files to here to upload.</p>}
customUpload
uploadHandler={(e) => customBase64Uploader(e)}
onClear={() => setFieldValue(`detail_tagihan[${index}].file`, false)}
onRemove={() => setFieldValue(`detail_tagihan[${index}].file`, false)}
/>
</div>
</div>
</div>
{/* AKUN */}
<div className='p-fluid'>
{optionNoSpp.length > 0 ? (
<div className='field col-6 md:col-4 my-3 w-1/2'>
<LabelInput>Akun</LabelInput>
<Dropdown
panelClassName='p-dropdown-form dd-panel-custom'
name={`detail_tagihan[${index}].akun_id`}
options={optionAkun}
value={
values.detail_tagihan[index] !== undefined
? values.detail_tagihan[index].akun_id
: setFieldValue(`detail_tagihan[${index}].akun_id`, '')
}
onChange={handleChange}
showClear={values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='300px'
placeholder='Select Akun'
filter
maxLength={100}
className={`${errorFieldMessageDetail(index, 'akun') && 'p-invalid'}`}
/>
{errorFieldMessageDetail(index, 'akun')}
</div>
) : (
<div className='field col-6 md:col-4 my-3 w-1/2'>
<LabelInput>Akun</LabelInput>
<Dropdown
panelClassName='p-dropdown-form dd-panel-custom'
name={`detail_tagihan[${index}].akun_id`}
options={optionAkun}
optionLabel={(item) => `${item.akun_id} - ${item.nama}`}
optionValue='akun_id'
value={
values.detail_tagihan[index] !== undefined
? values.detail_tagihan[index].akun_id
: setFieldValue(`detail_tagihan[${index}].akun_id`, '')
}
onChange={handleChange}
showClear={values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='300px'
placeholder='Select Akun'
filter
maxLength={100}
className={`${errorFieldMessageDetail(index, 'akun') && 'p-invalid'}`}
/>
{errorFieldMessageDetail(index, 'akun')}
</div>
)}
<div className='field col-12 md:col-4 my-3'>
<LabelInput>Keperluan</LabelInput>
<InputTextarea
name={`detail_tagihan[${index}].keperluan`}
rows={5}
cols={30}
value={
values.detail_tagihan[index] !== undefined
? values.detail_tagihan[index].keperluan
: ''
}
onChange={handleChange}
placeholder='Masukkan detail keperluan'
/>
</div>
</div>
</BlockUI>
{/* BUTTON DRAFT, SIMPAN AND MESSAGE */}
<div className='flex justify-center gap-2 mt-3'>
{values.detail_tagihan[index] !== undefined &&
values.detail_tagihan[index].status === true ? (
<div className='flex flex-col'>
<label className='p-2 bg-green-300 rounded shadow text-center'>
<i className='pi pi-check'></i> Tagihan Berhasil dikirim/draft
</label>
<small className='text-red-400'>
Tagihan ini hanya bisa didelete, tidak bisa diubah kembali!
</small>
</div>
) : (
<>
<Button
type='submit'
label='Draft'
icon='pi pi-save'
className='p-button-sm p-button-info'
onClick={(e) => handleSubmitDetailTagihan(e, { draft: true, index })}
/>
<Button
type='submit'
label='Simpan'
icon='pi pi-send'
className='p-button-sm p-button-success'
onClick={(e) => handleSubmitDetailTagihan(e, { draft: false, index })}
/>
</>
)}
</div>
</AccordionTab>
)
}
for (let index = 0; index < countJenisTagihan; index++) {
tagihanList.push(tagihan(index))
}
return (
<React.Fragment>
<form onSubmit={handleSubmit}>
<Accordion activeIndex={0} className='mt-5 accordion-custom'>
{tagihanList}
</Accordion>
</form>
{countJenisTagihan === 0 ? (
<div className='flex justify-end gap-2 mt-3'>
<Button
label='Kirim'
icon={`pi ${kirimLoading ? 'pi-spin pi-spinner' : 'pi-send'}`}
className='p-button-sm p-button-info'
onClick={handleBtnKirim}
disabled={true}
/>
<Button
type='submit'
label='Tambah Tagihan'
icon='pi pi-plus'
className='p-button-sm p-button-primary'
onClick={handleTambahTagihan}
disabled={false}
/>
</div>
) : (
<div className='flex justify-end gap-2 mt-3'>
<Button
label='Kirim'
icon={`pi ${kirimLoading ? 'pi-spin pi-spinner' : 'pi-send'}`}
className='p-button-sm p-button-info'
onClick={handleBtnKirim}
disabled={
values.detail_tagihan[countJenisTagihan - 1] !== undefined &&
values.detail_tagihan[countJenisTagihan - 1].status === true
? false
: true
}
/>
<Button
type='submit'
label='Tambah Tagihan'
icon='pi pi-plus'
className='p-button-sm p-button-primary'
onClick={handleTambahTagihan}
disabled={
values.detail_tagihan[countJenisTagihan - 1] !== undefined &&
values.detail_tagihan[countJenisTagihan - 1].status === true
? false
: true
}
/>
</div>
)}
</React.Fragment>
)
}}
</Formik>
</React.Fragment>
)
}}
</Formik>
</Card>
</Belakang>
</React.Fragment>
)
}
export const getServerSideProps = async (ctx) => {
let token = ctx.req.cookies.token
const optionJenisTagihan = await fetchAPI('jenis-tagihan/list', token)
const optionJenisBelanja = await fetchAPI('jenis-belanja/list', token)
return {
props: {
optionJenisTagihan,
optionJenisBelanja,
},
}
}
+863
View File
@@ -0,0 +1,863 @@
import { Belakang } from '@/components/Layouts'
import { Judul, LabelInput } from '@/components/TextCustom'
import { DetailSPPGet, NoSPPListGet } from '@/services/pengajuan-tagihan/integrasi-service'
import { TagihanAddDetail, TagihanCreate, TagihanSubmit } from '@/services/pengajuan-tagihan/tagihan-service'
import { AkunList } from '@/services/referensi/akun-service'
import { JenisKegiatanList } from '@/services/referensi/jenisKegiatan-service'
import { PersyaratanSearch } from '@/services/referensi/persyaratan-service'
import { useFormik } from 'formik'
import { fetchAPI } from 'lib/fetch'
import 'moment/locale/id'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { Accordion, AccordionTab } from 'primereact/accordion'
import { BlockUI } from 'primereact/blockui'
import { BreadCrumb } from 'primereact/breadcrumb'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Checkbox } from 'primereact/checkbox'
import { Chips } from 'primereact/chips'
import { Dropdown } from 'primereact/dropdown'
import { FileUpload } from 'primereact/fileupload'
import { InputText } from 'primereact/inputtext'
import { InputTextarea } from 'primereact/inputtextarea'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
import Swal from 'sweetalert2'
export default function FormPengajuanTagihan({ optionJenisTagihan = null, optionJenisBelanja = null }) {
const toast = useRef(null)
const router = useRouter()
const [countJenisTagihan, setCountJenisTagihan] = useState(0)
const [optionAkun, setOptionAkun] = useState([])
const [tagihanID, setTagihanID] = useState('')
const [kirimLoading, setKirimLoading] = useState(false)
const [optionJenisKegiatan, setOptionJenisKegiatan] = useState([])
const [statusDraft, setStatusDraft] = useState(false)
const [optionNoSpp, setOptionNoSpp] = useState([])
const [jenisSPP, setJenisSPP] = useState('')
const [uraianSPP, setUraianSPP] = useState('')
const [itsGU, setItsGU] = useState(false)
const [checkedGUGabungan, setCheckedGUGabungan] = useState(false)
const formik = useFormik({
initialValues: {
no_spp: '',
tgl_spp: '',
jenis_tagihan: '',
kode_jenis_spp: '',
spp_gabungan: [],
},
validate: (data) => {
let errors = {}
if (!data.no_spp) errors.no_spp = 'Nomor SPP is required.'
if (!data.tgl_spp) errors.tgl_spp = 'Tanggal SPP is required.'
if (!data.jenis_tagihan) errors.jenis_tagihan = 'Jenis Tagihan is required.'
return errors
},
onSubmit: (data) => {
const tanggalAsli = data.tgl_spp
/**
* Check tagihan if > 0 just add tagihan
* else request tagihan id
*/
if (countJenisTagihan > 0) {
setCountJenisTagihan(countJenisTagihan + 1)
} else {
TagihanCreate(data)
.then((res) => {
if (res.status === 'success') {
Object.assign(data, { tgl_spp: tanggalAsli })
setCountJenisTagihan(countJenisTagihan + 1)
formikDetailTagihan.setFieldValue('tagihan_id', res.data.tagihan_id)
setTagihanID(res.data.tagihan_id)
} else {
Object.assign(data, { tgl_spp: tanggalAsli })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
.catch((err) => {
console.log(err)
toast.current.show({
severity: 'error',
detail: 'Error while creating data',
closable: false,
})
})
}
},
})
// NOMER SPP
useEffect(() => {
NoSPPListGet()
.then((res) => setOptionNoSpp(res.data))
.catch((err) => console.log(err))
}, [])
useEffect(() => {
DetailSPPGet({ no_spp: formik.values.no_spp }).then((res) => {
if (res.data.akun.length > 0) {
setJenisSPP(res.data.jenis_spp)
setUraianSPP(res.data.uraian)
formik.setFieldValue('tgl_spp', new Date(res.data.tanggal_spp))
setOptionAkun(res.data.akun)
} else {
let params = {}
params.draw = 0
setJenisSPP('')
setUraianSPP('')
formik.setFieldValue('tgl_spp', new Date())
AkunList(params).then((res) => {
setOptionAkun(res.data)
})
}
})
}, [])
const formikDetailTagihan = useFormik({
initialValues: {
tagihan_id: '',
detail_tagihan: [],
dataIndex: 0, //* For select data index
},
validate: (data) => {
let errorsArray = []
let errors = {}
if (!data.detail_tagihan[data.dataIndex]) {
errors.jenis_belanja = 'Jenis Belanja is required.'
errors.jenis_kegiatan = 'Jenis Kegiatan is required.'
errors.akun = 'Akun is required.'
}
if (data.detail_tagihan[data.dataIndex] && data.detail_tagihan[data.dataIndex].jenis_belanja === '') {
errors.jenis_belanja = 'Jenis Belanja is required dong.'
}
if (data.detail_tagihan[data.dataIndex] && !data.detail_tagihan[data.dataIndex].jenis_kegiatan) {
errors.jenis_kegiatan = 'Jenis Kegiatan is required.'
}
if (data.detail_tagihan[data.dataIndex] && !data.detail_tagihan[data.dataIndex].akun_id) {
errors.akun = 'Akun is required.'
}
if (Object.keys(errors).length > 0) {
errorsArray.push({ dataIndex: countJenisTagihan - 1, errors })
}
return errorsArray
},
onSubmit: (data) => {
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', true)
/**
* *Filter data detail tagihan follow the button kirim
*/
const filterData = data.detail_tagihan.find((item, i) => i === data.dataIndex)
/**
* *Create new format data detail tagihan reference to API
*/
const dataKirim = {
tagihan_id: data.tagihan_id,
detail_tagihan: [filterData],
}
TagihanAddDetail(dataKirim)
.then((res) => {
if (res.status === 'ok') {
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
if (statusDraft) {
router.push('/pengajuan-tagihan')
}
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].status', true)
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
} else {
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
}
})
.catch((err) => {
console.log(err)
toast.current.show({
severity: 'error',
detail: 'Error saat pembuatan data/file belum diupload',
closable: false,
})
/**
* *Add status to detail tagihan object for check if it is sended or not
*/
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].status', false)
formikDetailTagihan.setFieldValue('detail_tagihan[' + data.dataIndex + '].loading', false)
})
},
validateOnChange: false,
})
/**
* *Message error for formik
*/
const isFieldValid = (field) => !!(formik.touched[field] && formik.errors[field])
const errorFieldMessage = (field) => {
return (
isFieldValid(field) && (
<small className='p-error' style={{ fontSize: '11px' }}>
{formik.errors[field]}
</small>
)
)
}
/**
* *Message error for formik detail tagihan
*/
const isFieldValidDetail = (index, field) =>
formikDetailTagihan.errors[0] &&
formikDetailTagihan.errors[0].dataIndex === index &&
formikDetailTagihan.errors[0].errors[field]
const errorFieldMessageDetail = (index, field) => {
return (
isFieldValidDetail(index, field) && (
<small className='p-error' style={{ fontSize: '11px' }}>
{formikDetailTagihan.errors[0].errors[field]}
</small>
)
)
}
/**
* *Card Detail Tagihan
*/
const headerTemplate = (index) => {
const handleDelete = (index) => {
formikDetailTagihan.setValues({
...formikDetailTagihan.values,
detail_tagihan: formikDetailTagihan.values.detail_tagihan.filter((item, i) => i !== index),
})
setCountJenisTagihan(countJenisTagihan - 1)
}
return (
<div className='flex justify-between items-center w-full'>
<p>Tagihan {index + 1}</p>
<Button
type='button'
icon='pi pi-times'
className='p-button-rounded p-button-danger p-button-text button-close-tagihan'
aria-label='Cancel'
onClick={() => handleDelete(index)}
title='Delete tagihan'
/>
</div>
)
}
/**
* *Dropdown list jenis kegiatan
* *When jenis kegiatan change hit API to get list syarat
*/
const changeJenisKegiatan = (event, index) => {
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].syarat`, [])
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].jenis_kegiatan`, event.value)
PersyaratanSearch({
jenis_tagihan_id: formik.values.jenis_tagihan,
jenis_belanja_id: formikDetailTagihan.values.detail_tagihan[index].jenis_belanja,
jenis_kegiatan_id: event.value,
})
.then((res) => {
if (res.data) {
res.data.map((item, index2) => {
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].syarat[${index2}].syarat_id`, item.syarat_id)
formikDetailTagihan.setFieldValue(
`detail_tagihan[${index}].syarat[${index2}].syarat_dokumen`,
item.syarat_dokumen
)
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].syarat[${index2}].keterangan`, item.keterangan)
})
} else {
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].syarat`, [])
toast.current.show({
severity: 'info',
detail: 'Data syarat kosong',
closable: false,
})
}
})
.catch(() => {
toast.current.show({
severity: 'error',
detail: 'Error while get data',
closable: false,
})
})
}
/**
* *Change Jenis Belanja
* *Reset form detail tagihan
*/
const changeJenisBelanja = (event, index) => {
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].syarat`, [])
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].jenis_belanja`, event.value)
/**
* SHOW LIST JENIS KEGIATAN WHERE JENIS_BELANJA_ID
*/
JenisKegiatanList({ jenis_belanja_id: event.value, draw: 0 })
.then((res) => setOptionJenisKegiatan(res.data))
.catch((err) => console.log(err))
}
/**
* *End of Dropdown list jenis kegiatan
*/
/**
* *Form Detail Tagihan
* *Button Submit and draft
*/
const handleSubmitDetailTagihan = (e, { draft, index }) => {
e.preventDefault()
setStatusDraft(draft)
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].draft`, draft)
formikDetailTagihan.handleSubmit()
}
const tagihanList = []
const tagihan = (index) => {
const customBase64Uploader = async (event) => {
// convert file to base64 encoded
const file = event.files[0]
if (file) {
if (file.type === 'application/pdf') {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = function () {
const base64data = reader.result
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file`, base64data)
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file_name`, file.name)
}
toast.current.show({
severity: 'success',
detail: 'File berhasil diupload',
closable: false,
})
} else {
event.options.clear()
toast.current.show({
severity: 'error',
detail: 'Invalid format file',
closable: false,
})
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file`, null)
formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file_name`, null)
}
}
}
return (
<AccordionTab key={index} headerTemplate={headerTemplate(index)} className={`accordion-tab-${index}`}>
<BlockUI
blocked={
formikDetailTagihan.values.detail_tagihan[index] !== undefined &&
formikDetailTagihan.values.detail_tagihan[index].status
? true
: false
}
className='block-ui-custom cursor-not-allowed'
>
<div className='p-fluid grid grid-cols-2 gap-3'>
{/* JENIS BELANJA */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Belanja</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${index}].jenis_belanja`}
options={optionJenisBelanja}
optionLabel='nama'
optionValue='jenis_id'
value={
formikDetailTagihan.values.detail_tagihan[index] !== undefined
? formikDetailTagihan.values.detail_tagihan[index].jenis_belanja
: formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].jenis_belanja`, '')
}
onChange={(e) => changeJenisBelanja(e, index)}
showClear={formikDetailTagihan.values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='200px'
placeholder='Select Jenis Belanja'
/>
{errorFieldMessageDetail(index, 'jenis_belanja')}
</div>
{/* JENIS KEGIATAN */}
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Kegiatan</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name={`detail_tagihan[${index}].jenis_kegiatan`}
options={optionJenisKegiatan}
optionLabel='nama'
optionValue='jenis_id'
value={
formikDetailTagihan.values.detail_tagihan[index] !== undefined
? formikDetailTagihan.values.detail_tagihan[index].jenis_kegiatan
: formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].jenis_kegiatan`, '')
}
onChange={(e) => changeJenisKegiatan(e, index)}
showClear={formikDetailTagihan.values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='200px'
placeholder='Select Jenis Kegiatan'
filter
/>
{errorFieldMessageDetail(index, 'jenis_kegiatan')}
</div>
</div>
{/* SYARAT */}
<div className='p-fluid grid grid-cols-2 gap-3'>
<div className='md:col-4 my-3'>
<LabelInput>Syarat</LabelInput>
<div className='relative mt-2 flex overflow-auto h-96'>
<table className='w-full text-sm text-left '>
<thead className='text-xs uppercase bg-gray-100 sticky top-0'>
<tr>
<th scope='col' className='py-3 px-6'>
Persyaratan
</th>
<th scope='col' className='py-3 px-6'>
Keterangan
</th>
</tr>
</thead>
<tbody>
{formikDetailTagihan.values.detail_tagihan[index] &&
formikDetailTagihan.values.detail_tagihan[index].syarat &&
formikDetailTagihan.values.detail_tagihan[index].syarat.map((item, index2) => (
<tr className='bg-white border-b' key={index2}>
<td scope='row' className='py-4 px-6'>
{item.syarat_dokumen}
</td>
<td scope='row' className='py-4 px-6'>
{item.keterangan}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className='upload-all my-3'>
<LabelInput>Upload Syarat</LabelInput>
<div className='py-4 px-6'>
<FileUpload
name='upload-syarat'
url={'#'}
accept='.pdf'
// maxFileSize={1000000}
emptyTemplate={<p className='m-0'>Drag and drop files to here to upload.</p>}
customUpload
uploadHandler={(e) => customBase64Uploader(e)}
onClear={() => formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file`, false)}
onRemove={() => formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].file`, false)}
uploadOptions={{
className:
formikDetailTagihan.values.detail_tagihan[index] !== undefined &&
'file' in formikDetailTagihan.values.detail_tagihan[index]
? 'p-disabled'
: '',
}}
cancelOptions={{
className:
formikDetailTagihan.values.detail_tagihan[index] !== undefined &&
'file' in formikDetailTagihan.values.detail_tagihan[index]
? 'p-disabled'
: '',
}}
/>
</div>
</div>
</div>
{/* AKUN */}
<div className='p-fluid'>
{optionNoSpp.length > 0 ? (
<div className='field col-6 md:col-4 my-3 w-1/2'>
<LabelInput>Akun</LabelInput>
<Dropdown
panelClassName='p-dropdown-form dd-panel-custom'
name={`detail_tagihan[${index}].akun_id`}
options={optionAkun}
// optionLabel={(item) => `${item.KODE_AKUN} - ${item.URAIAN}`}
// optionValue='KODE_AKUN'
value={
formikDetailTagihan.values.detail_tagihan[index] !== undefined
? formikDetailTagihan.values.detail_tagihan[index].akun_id
: formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].akun_id`, '')
}
onChange={formikDetailTagihan.handleChange}
showClear={formikDetailTagihan.values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='300px'
placeholder='Select Akun'
filter
maxLength={100}
/>
{errorFieldMessageDetail(index, 'akun')}
</div>
) : (
<div className='field col-6 md:col-4 my-3 w-1/2'>
<LabelInput>Akun</LabelInput>
<Dropdown
panelClassName='p-dropdown-form dd-panel-custom'
name={`detail_tagihan[${index}].akun_id`}
options={optionAkun}
optionLabel={(item) => `${item.akun_id} - ${item.nama}`}
optionValue='akun_id'
value={
formikDetailTagihan.values.detail_tagihan[index] !== undefined
? formikDetailTagihan.values.detail_tagihan[index].akun_id
: formikDetailTagihan.setFieldValue(`detail_tagihan[${index}].akun_id`, '')
}
onChange={formikDetailTagihan.handleChange}
showClear={formikDetailTagihan.values.detail_tagihan[index] !== undefined ? true : false}
scrollHeight='300px'
placeholder='Select Akun'
filter
maxLength={100}
/>
{errorFieldMessageDetail(index, 'akun')}
</div>
)}
<div className='field col-12 md:col-4 my-3'>
<LabelInput>Keperluan</LabelInput>
<InputTextarea
name={`detail_tagihan[${index}].keperluan`}
rows={5}
cols={30}
value={
formikDetailTagihan.values.detail_tagihan[index] !== undefined
? formikDetailTagihan.values.detail_tagihan[index].keperluan
: ''
}
onChange={formikDetailTagihan.handleChange}
placeholder='Masukkan detail keperluan'
/>
</div>
</div>
</BlockUI>
{/* BUTTON DRAFT, SIMPAN AND MESSAGE */}
<div className='flex justify-center gap-2 mt-3'>
{formikDetailTagihan.values.detail_tagihan[index] !== undefined &&
formikDetailTagihan.values.detail_tagihan[index].status === true ? (
<div className='flex flex-col'>
<label className='p-2 bg-green-300 rounded shadow text-center'>
<i className='pi pi-check'></i> Tagihan Berhasil dikirim/draft
</label>
<small className='text-red-400'>Tagihan ini hanya bisa didelete, tidak bisa diubah kembali!</small>
</div>
) : (
<>
<Button
type='submit'
label='Draft'
icon='pi pi-save'
className='p-button-sm p-button-info'
onClick={(e) => handleSubmitDetailTagihan(e, { draft: true, index })}
loading={
formikDetailTagihan.values.detail_tagihan[index] &&
formikDetailTagihan.values.detail_tagihan[index].loading
? formikDetailTagihan.values.detail_tagihan[index].loading
: false
}
/>
<Button
type='submit'
label='Simpan'
icon='pi pi-send'
className='p-button-sm p-button-success'
onClick={(e) => handleSubmitDetailTagihan(e, { draft: false, index })}
loading={
formikDetailTagihan.values.detail_tagihan[index] &&
formikDetailTagihan.values.detail_tagihan[index].loading
? formikDetailTagihan.values.detail_tagihan[index].loading
: false
}
/>
</>
)}
</div>
</AccordionTab>
)
}
for (let index = 0; index < countJenisTagihan; index++) {
tagihanList.push(tagihan(index))
}
/**
* *End Detail Tagihan
* *End Card Detail Tagihan
*/
/**
* *Breadcrumb
*/
const items = [{ label: 'Pengajuan Tagihan', url: '/pengajuan-tagihan' }, { label: 'Form' }]
const home = { icon: 'pi pi-home', url: '/dashboard' }
const handleTambahTagihan = (e) => {
e.preventDefault()
Swal.fire({
title: 'Apakah data yang diinputkan sudah benar benar?',
// text: "Kamu tidak bisa pakai !",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, benar!',
cancelButtonText: 'Tidak',
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Pastikan kembali!',
text: 'Kamu tidak bisa pakai data ini kembali!',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, benar!',
cancelButtonText: 'Tidak',
}).then((result) => {
if (result.isConfirmed) {
formikDetailTagihan.setFieldValue('dataIndex', countJenisTagihan)
formik.handleSubmit()
}
})
}
})
}
const handleBtnKirim = () => {
Swal.fire({
title: 'Apakah data diatas sudah benar semua?',
text: 'Tagihan yang sudah dikirim tidak bisa diubah kembali',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, kirim!',
}).then((result) => {
if (result.isConfirmed) {
setKirimLoading(true)
TagihanSubmit({ ref_id: tagihanID })
.then((res) => {
if (res.status == 'ok') {
setKirimLoading(false)
Swal.fire({
title: 'Success',
text: 'Tagihan berhasil dikirim',
icon: 'success',
timer: 2000,
timerProgressBar: true,
}).then(() => {
router.push('/pengajuan-tagihan')
})
} else {
setKirimLoading(false)
}
})
.catch((err) => {
console.log(err)
setKirimLoading(false)
})
}
})
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Form Pengajuan Tagihan</title>
</Head>
<Belakang>
<BreadCrumb model={items} home={home} />
<div className='mb-3'></div>
<Judul>Form Pengajuan Tagihan</Judul>
<Card>
<form onSubmit={formik.handleSubmit}>
<div className='p-fluid grid grid-cols-2 gap-3'>
<div className='field col-12 md:col-4'>
<LabelInput>No SPP</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name='no_spp'
options={optionNoSpp}
value={formik.values.no_spp}
onChange={formik.handleChange}
showClear={formik.values.no_spp ? true : false}
scrollHeight='200px'
placeholder='Select No SPP'
filter
editable
/>
{errorFieldMessage('no_spp')}
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Tanggal SPP</LabelInput>
<Calendar
name='tgl_spp'
value={formik.values.tgl_spp}
onChange={formik.handleChange}
showButtonBar
dateFormat='dd MM yy'
disabled={countJenisTagihan > 0 ? true : false}
placeholder='Masukkan tanggal SPP'
icon='pi pi-calendar'
showIcon
/>
{errorFieldMessage('tgl_spp')}
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Jenis Tagihan</LabelInput>
<Dropdown
panelClassName='p-dropdown-form'
name='jenis_tagihan'
options={optionJenisTagihan}
optionLabel='nama'
optionValue='jenis_id'
value={formik.values.jenis_tagihan}
onChange={(e) => {
formik.setFieldValue('jenis_tagihan', e.value)
e.value === 'REF-a8be274d-bfe3-4875-b334-2e32bc7e1fca' ? setItsGU(true) : setItsGU(false)
}}
showClear={formik.values.jenis_tagihan ? true : false}
scrollHeight='200px'
placeholder='Select Jenis Tagihan'
disabled={countJenisTagihan > 0 ? true : false}
/>
{errorFieldMessage('jenis_tagihan')}
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Jenis SPP</LabelInput>
<InputText disabled value={jenisSPP} />
</div>
<div className='field col-12 md:col-4'>
<LabelInput>Uraian</LabelInput>
<InputTextarea rows={5} cols={30} value={uraianSPP} disabled />
</div>
{itsGU && (
<div className='field col-12 md:col-4 flex flex-col gap-y-2'>
<div className='field col-12 md:col-4 flex items-center gap-x-1'>
<Checkbox
inputId='ingredient1'
name='pizza'
value='Cheese'
checked={checkedGUGabungan}
onChange={(e) => {
setCheckedGUGabungan(e.checked)
!e.checked && formik.setFieldValue('spp_gabungan', [])
}}
/>
<LabelInput>GU Gabungan</LabelInput>
<span className='text-xs font-semibold tracking-wide'>*tekan enter pernomor spp</span>
</div>
{checkedGUGabungan && (
<div className=''>
<LabelInput>No SPP Gabungan</LabelInput>
<Chips name='spp_gabungan' value={formik.values.spp_gabungan} onChange={formik.handleChange} />
</div>
)}
</div>
)}
</div>
</form>
<form onSubmit={formikDetailTagihan.handleSubmit}>
<Accordion activeIndex={0} className='mt-5 accordion-custom'>
{tagihanList}
</Accordion>
</form>
{countJenisTagihan === 0 && formikDetailTagihan.values.detail_tagihan[countJenisTagihan] === undefined ? (
<div className='flex justify-end gap-2 mt-3'>
<Button
label='Kirim'
icon={`pi ${kirimLoading ? 'pi-spin pi-spinner' : 'pi-send'}`}
className='p-button-sm p-button-info'
onClick={handleBtnKirim}
disabled={true}
/>
<Button
type='submit'
label='Tambah Tagihan'
icon='pi pi-plus'
className='p-button-sm p-button-primary'
onClick={handleTambahTagihan}
disabled={false}
/>
</div>
) : (
<div className='flex justify-end gap-2 mt-3'>
<Button
label='Kirim'
icon={`pi ${kirimLoading ? 'pi-spin pi-spinner' : 'pi-send'}`}
className='p-button-sm p-button-info'
onClick={handleBtnKirim}
disabled={
formikDetailTagihan.values.detail_tagihan[countJenisTagihan - 1] !== undefined &&
formikDetailTagihan.values.detail_tagihan[countJenisTagihan - 1].status === true
? false
: true
}
/>
<Button
type='submit'
label='Tambah Tagihan'
icon='pi pi-plus'
className='p-button-sm p-button-primary'
onClick={handleTambahTagihan}
disabled={
formikDetailTagihan.values.detail_tagihan[countJenisTagihan - 1] !== undefined &&
formikDetailTagihan.values.detail_tagihan[countJenisTagihan - 1].status === true
? false
: true
}
/>
</div>
)}
</Card>
</Belakang>
</>
)
}
export const getServerSideProps = async (ctx) => {
let token = ctx.req.cookies.token
const optionJenisTagihan = await fetchAPI('jenis-tagihan/list', token)
const optionJenisBelanja = await fetchAPI('jenis-belanja/list', token)
return {
props: {
optionJenisTagihan,
optionJenisBelanja,
},
}
}
+296
View File
@@ -0,0 +1,296 @@
import { DatatablePrimeV2 } from '@/components/Datatables-v2'
import { Label } from '@/components/Label'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { TagihanDelete, TagihanList } from '@/services/pengajuan-tagihan/tagihan-service'
import { JenisTagihanList } from '@/services/referensi/jenisTagihan-service'
import { Formik } from 'formik'
import moment from 'moment'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { Button } from 'primereact/button'
import { Calendar } from 'primereact/calendar'
import { Card } from 'primereact/card'
import { Column } from 'primereact/column'
import { DataTable } from 'primereact/datatable'
import { Dropdown } from 'primereact/dropdown'
import { InputText } from 'primereact/inputtext'
import { Toast } from 'primereact/toast'
import React, { useEffect, useRef, useState } from 'react'
export default function PengajuanTagihan() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogDelete, setDialogDelete] = useState({ visible: false })
const [expandedRows, setExpandedRows] = useState(null)
const [ddJenisTagihan, setDdJenisTagihan] = useState([])
const [search, setSearch] = useState({})
const router = useRouter()
const [loading, setLoading] = useState(false)
const [refresh, setRefresh] = useState(0)
// DATATABLE
const [sort, setSort] = useState([])
const [orderDir, setOrderDir] = useState('desc')
const [orderCol, setOrderCol] = useState(2)
const [first, setFirst] = useState(0)
const [page, setPage] = useState(0)
const [length, setLength] = useState(10)
const [totalRecords, setTotalRecords] = useState(0)
useEffect(() => {
JenisTagihanList().then((res) => setDdJenisTagihan(res.data))
}, [])
useEffect(() => {
setLoading(true)
let params = {
draw: page,
start: first,
length: length,
order_col: orderCol,
order_dir: orderDir,
}
const { no_spp, jenis_tagihan, tanggal_input } = search
params.no_spp = no_spp ? no_spp : ''
params.jenis_tagihan = jenis_tagihan ? jenis_tagihan : ''
params.tanggal_input = tanggal_input ? moment(tanggal_input).format('YYYY-MM-DD') : ''
TagihanList(params).then((res) => {
setData(res.data)
setLoading(false)
setTotalRecords(
res.recordsFiltered === 1 ? 1 : res.recordsFiltered < 1 ? res.recordsFiltered : res.recordsFiltered
)
})
}, [page, first, length, orderCol, orderDir, refresh, search])
const editTagihan = (data) => {
if (data.tanggal_kirim === '-' && data.detail.length > 0) {
router.push({ pathname: '/pengajuan-tagihan/draft/[draft]', query: { draft: data.tagihan_id } })
} else if (data.tanggal_kirim === '-') {
router.push({ pathname: '/pengajuan-tagihan/force-close/[force]', query: { force: data.tagihan_id } })
} else {
router.push({ pathname: '/pengajuan-tagihan/ditolak/[ditolak]', query: { ditolak: data.tagihan_id } })
}
}
const deleteTagihan = () => {
TagihanDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button
icon='pi pi-pencil'
className='p-button-sm p-button-rounded'
onClick={() => editTagihan(rowData)}
disabled={rowData.tanggal_kirim === '-' || rowData.editable || rowData.penolakan ? false : true}
/>
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ ref_id: rowData.tagihan_id, visible: true })}
disabled={rowData.tanggal_kirim === '-' || rowData.editable ? false : true}
/>
</div>
)
}
const bodyTanggal = (rowData) => {
return rowData.tanggal_spp !== '-' ? (
moment(rowData.tanggal_spp).format('DD MMMM YYYY')
) : (
<Label type={'warning'}>Belum dikirim</Label>
)
}
const datatableDetail = (data) => {
return (
<DataTable value={data} className='mx-20'>
<Column field='akun' header='Akun'></Column>
<Column field='keperluan' header='Keperluan'></Column>
<Column field='nomor_tagihan' header='Nomor Tagihan'></Column>
</DataTable>
)
}
const expandRow = {
expandedRows: expandedRows,
onRowToggle: (e) => setExpandedRows(e.data),
responsiveLayout: 'scroll',
rowExpansionTemplate: (e) => datatableDetail(e.detail),
dataKey: 'no_spp',
}
const Pencarian = (
<React.Fragment>
<Formik
initialValues={{ no_spp: '', jenis_tagihan: '', tanggal_input: '' }}
onSubmit={(values) => {
const tempTanggalInput = values.tanggal_input
values.tanggal_input &&
Object.assign(values, { tanggal_input: moment(values.tanggal_input).format('YYYY-MM-D') })
setSearch(values)
values.tanggal_input = tempTanggalInput
}}
>
{({ values, handleChange, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div className='flex gap-x-5 items-center justify-center'>
<div className='flex flex-col'>
<label htmlFor='no_spp'>No SPP</label>
<InputText
id='no_spp'
name='no_spp'
value={values.no_spp}
onChange={handleChange}
placeholder='Masukkan Nomor SPP'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Jenis Tagihan</label>
<Dropdown
name='jenis_tagihan'
value={values.jenis_tagihan}
optionValue='jenis_id'
optionLabel='nama'
options={ddJenisTagihan}
onChange={handleChange}
placeholder='Select Jenis Tagihan'
showClear={values.jenis_tagihan ? true : false}
className='w-52'
/>
</div>
<div className='flex flex-col'>
<label htmlFor='no_spp'>Tanggal Input</label>
<Calendar
name='tanggal_input'
value={values.tanggal_input}
onChange={handleChange}
placeholder='e.g. 17 Agustus 1945'
dateFormat='dd MM yy'
showButtonBar
></Calendar>
</div>
<div className='flex flex-col'>
<label htmlFor=''>&nbsp;</label>
<Button type='submit' label='Cari' className='p-button-sm p-button-success' icon='pi pi-search' />
</div>
</div>
</form>
)
}}
</Formik>
</React.Fragment>
)
const bodyStatus = (rowData) => {
let status
if (rowData.tahapan === 6) {
status = <Label type={'success'}>Selesai</Label>
} else if (rowData.penolakan === true || rowData.pernah_ditolak === true) {
status = <Label type={'danger'}>Ditolak/pernah ditolak</Label>
} else {
status = '-'
}
return status
}
const allowExpansion = (rowData) => {
return rowData.detail.length > 0
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Pengajuan Tagihan</title>
</Head>
<Belakang>
<Judul>Pengajuan Tagihan</Judul>
<Card className='card-search my-3'>{Pencarian}</Card>
<div className='flex justify-end mb-3'>
<Button
className='p-button-sm'
label={'Tambah Tagihan'}
onClick={() => {
router.push('/pengajuan-tagihan/form')
}}
/>
</div>
<DatatablePrimeV2
data={data}
dialogDelete={dialogDelete}
setDialogDelete={setDialogDelete}
deleteTagihan={deleteTagihan}
first={first}
length={length}
loading={loading}
onSort={(e) => {
setSort(e.multiSortMeta)
if (e.multiSortMeta.length > 0) {
switch (e.multiSortMeta[0].field) {
case 'no_spp':
setOrderCol(0)
break
case 'nama_tagihan':
setOrderCol(1)
break
case 'tanggal':
setOrderCol(2)
break
}
switch (e.multiSortMeta[0].order) {
case -1:
setOrderDir('desc')
break
case 1:
setOrderDir('asc')
break
}
}
}}
setFirst={setFirst}
setLength={setLength}
setPage={setPage}
sort={sort}
totalRecords={totalRecords}
page={page}
expandRow={expandRow}
>
<Column expander={allowExpansion} style={{ width: '50px' }} />
<Column field='no_spp' header='Nomor SPP' sortable></Column>
<Column field='nama_tagihan' header='Jenis Tagihan' sortable></Column>
<Column field='tanggal_spp' header='Tanggal SPP' body={bodyTanggal} sortable></Column>
<Column header='Status' body={bodyStatus}></Column>
<Column header='Action' body={actionBodyTemplate}></Column>
</DatatablePrimeV2>
</Belakang>
</>
)
}
+20
View File
@@ -0,0 +1,20 @@
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import Head from 'next/head'
import Image from 'next/image'
export default function Peraturan() {
return (
<>
<Head>
<title>Peraturan</title>
</Head>
<Belakang>
<Judul>Peraturan</Judul>
<div className='flex justify-center'>
<Image src={'/img/coming-soon.png'} alt='coming-soon' width={600} height={600} />
</div>
</Belakang>
</>
)
}
+122
View File
@@ -0,0 +1,122 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormAkun from '@/components/Form/Akun'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { AkunDelete, AkunGetOne, AkunList } from '@/services/referensi/akun-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function Akun() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dataDrawPrev, setDataDrawPrev] = useState([])
const [dataDrawNext, setDataDrawNext] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
const [draw, setDraw] = useState(1)
useEffect(() => {
let params = {}
params.draw = draw
search !== null && search !== '' ? (params.search = search) : ''
AkunList(params).then((res) => setData(res.data))
if (draw > 1) {
params.draw = draw > 1 ? draw - 1 : draw
AkunList(params).then((res) => setDataDrawPrev(res.data))
}
params.draw = draw + 1
AkunList(params).then((res) => setDataDrawNext(res.data))
}, [refresh, search, draw])
const editAkun = (data) => {
AkunGetOne({ akun_id: data.akun_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editAkun(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ akun_id: rowData.akun_id, visible: true })}
/>
</div>
)
}
const deleteAkun = () => {
AkunDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Akun</title>
</Head>
<Belakang>
<Judul>Akun</Judul>
<DatatablePrime
data={data}
dataDrawPrev={dataDrawPrev}
dataDrawNext={dataDrawNext}
draw={draw}
setDraw={setDraw}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormAkun}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='akun_id' header='Nomor Akun' sortable></Column>
<Column field='nama' header='Nama Akun' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteAkun} />
)}
</>
)
}
+108
View File
@@ -0,0 +1,108 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormJenisBelanja from '@/components/Form/JenisBelanja'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { JenisBelanjaDelete, JenisBelanjaGetOne, JenisBelanjaList } from '@/services/referensi/jenisBelanja-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function JenisBelanja() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = {}
search !== null && search !== '' ? (params.search = search) : ''
JenisBelanjaList(params).then((res) => setData(res.data))
}, [refresh, search])
const editJenisBelanja = (data) => {
JenisBelanjaGetOne({ ref_id: data.jenis_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteJenisBelanja = () => {
JenisBelanjaDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button
icon='pi pi-pencil'
className='p-button-sm p-button-rounded'
onClick={() => editJenisBelanja(rowData)}
/>
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded'
onClick={() => setDialogDelete({ ref_id: rowData.jenis_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Jenis Belanja</title>
</Head>
<Belakang>
<Judul>Jenis Belanja</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormJenisBelanja}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Nama' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteJenisBelanja} />
)}
</>
)
}
+129
View File
@@ -0,0 +1,129 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormJenisKegiatan from '@/components/Form/JenisKegiatan'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { JenisKegiatanDelete, JenisKegiatanGetOne, JenisKegiatanList } from '@/services/referensi/jenisKegiatan-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function JenisKegiatan() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
const [dataDrawPrev, setDataDrawPrev] = useState([])
const [dataDrawNext, setDataDrawNext] = useState([])
const [draw, setDraw] = useState(1)
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
params.draw = draw
JenisKegiatanList(params).then((res) => setData(res.data))
if (draw > 1) {
params.draw = draw > 1 ? draw - 1 : draw
JenisKegiatanList(params).then((res) => setDataDrawPrev(res.data))
}
params.draw = draw + 1
JenisKegiatanList(params).then((res) => setDataDrawNext(res.data))
}, [search, refresh, draw])
const editJenisKegiatan = (data) => {
JenisKegiatanGetOne({ ref_id: data.jenis_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteJenisKegiatan = () => {
JenisKegiatanDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button
icon='pi pi-pencil'
className='p-button-sm p-button-rounded'
onClick={() => editJenisKegiatan(rowData)}
/>
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ ref_id: rowData.jenis_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Jenis Kegiatan</title>
</Head>
<Belakang>
<Judul>Jenis Kegiatan</Judul>
<DatatablePrime
data={data}
dataDrawPrev={dataDrawPrev}
dataDrawNext={dataDrawNext}
draw={draw}
setDraw={setDraw}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormJenisKegiatan}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Jenis Kegiatan' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteJenisKegiatan} />
)}
</>
)
}
+114
View File
@@ -0,0 +1,114 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormJenisTagihan from '@/components/Form/JenisTagihan'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { JenisTagihanDelete, JenisTagihanGetOne, JenisTagihanList } from '@/services/referensi/jenisTagihan-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function JenisTagihan() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
JenisTagihanList(params).then((res) => setData(res.data))
}, [refresh, search])
const editJenisTagihan = (data) => {
JenisTagihanGetOne({ ref_id: data.jenis_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteJenisTagihan = () => {
JenisTagihanDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button
icon='pi pi-pencil'
className='p-button-sm p-button-rounded'
onClick={() => editJenisTagihan(rowData)}
/>
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded'
onClick={() => setDialogDelete({ ref_id: rowData.jenis_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Jenis Tagihan</title>
</Head>
<Belakang>
<Judul>Jenis Tagihan</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormJenisTagihan}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Nama' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteJenisTagihan} />
)}
</>
)
}
+231
View File
@@ -0,0 +1,231 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormPersyaratan from '@/components/Form/Persyaratan'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { PersyaratanDelete, PersyaratanGetOne, PersyaratanList } from '@/services/referensi/persyaratan-service'
import { Form, Formik } from 'formik'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Dialog } from 'primereact/dialog'
import { Dropdown } from 'primereact/dropdown'
// import { Dropdown } from 'primereact/dropdown'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function Persyaratan() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
const [dialogUpload, setDialogUpload] = useState(false)
const [dataDrawPrev, setDataDrawPrev] = useState([])
const [dataDrawNext, setDataDrawNext] = useState([])
const [draw, setDraw] = useState(1)
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
params.draw = draw
PersyaratanList(params).then((res) => setData(res.data))
if (draw > 1) {
params.draw = draw > 1 ? draw - 1 : draw
PersyaratanList(params).then((res) => setDataDrawPrev(res.data))
}
params.draw = draw + 1
PersyaratanList(params).then((res) => setDataDrawNext(res.data))
}, [search, refresh, draw])
const editPersyaratan = (data) => {
PersyaratanGetOne({ syarat_id: data.syarat_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deletePersyaratan = () => {
PersyaratanDelete({ syarat_id: dialogDelete.syarat_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editPersyaratan(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ syarat_id: rowData.syarat_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Persyaratan</title>
</Head>
<Belakang>
<Judul>Persyaratan</Judul>
<DatatablePrime
data={data}
dataDrawPrev={dataDrawPrev}
dataDrawNext={dataDrawNext}
draw={draw}
setDraw={setDraw}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormPersyaratan}
buttonUpload={true}
buttonUploadLabel='Upload syarat'
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
dialogUpload={dialogUpload}
setDialogUpload={setDialogUpload}
>
<Column field='nama_tagihan' header='Nama Tagihan' sortable></Column>
<Column field='nama_belanja' header='Nama Belanja' sortable></Column>
<Column field='nama_kegiatan' header='Nama Kegiatan' sortable></Column>
<Column field='syarat_dokumen' header='Syarat Dokumen' sortable></Column>
<Column field='keterangan' header='Keterangan' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deletePersyaratan} />
)}
<Dialog
header='Upload File Persyaratan'
visible={dialogUpload}
style={{ width: '50vw' }}
onHide={() => setDialogUpload(false)}
>
<Formik
initialValues={{
truncate: false,
jenis_tagihan: '',
fileupload: '',
}}
onSubmit={(values) => {
console.log(values)
}}
>
{({ values, handleChange, setFieldValue }) => {
const onBrowseFile = async (event) => {
const files = event.target.files
if (files) {
if (files[0].type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
setFieldValue('fileupload', files[0].name)
} else {
event.target.value = ''
toast.current.show({
severity: 'error',
detail: 'Invalid format file',
closable: false,
})
}
}
}
return (
<Form>
<div className='p-fluid grid grid-cols-2 gap-2'>
<div className=''>
<label className='block'>File</label>
{values.fileupload ? (
<div className=''>
<input
id='files'
type='file'
onChange={(e) => onBrowseFile(e)}
accept='.xlsx'
style={{ display: 'none' }}
/>
<label htmlFor='files' className='bg-blue-300 p-2 rounded cursor-pointer'>
<i className='pi pi-file-pdf' /> {values.fileupload}
</label>
</div>
) : (
<>
<input id='files' type='file' onChange={(e) => onBrowseFile(e)} accept='.xlsx' />
</>
)}
</div>
<div className=''>
<label className='block'>Truncate</label>
<Dropdown
name='truncate'
optionLabel='name'
optionValue='value'
value={values.truncate}
options={[
{ name: 'Yes', value: true },
{ name: 'No', value: false },
]}
onChange={handleChange}
placeholder='Select a City'
/>
</div>
<div className=''>
<label className='block'>Jenis Tagihan</label>
<Dropdown
name='truncate'
optionLabel='name'
optionValue='value'
value={values.truncate}
options={[
{ name: 'Yes', value: true },
{ name: 'No', value: false },
]}
onChange={handleChange}
placeholder='Select a City'
/>
</div>
</div>
</Form>
)
}}
</Formik>
</Dialog>
</>
)
}
+112
View File
@@ -0,0 +1,112 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormUnitKerja from '@/components/Form/UnitKerja'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { UnitKerjaDelete, UnitKerjaGetOne, UnitKerjaList } from '@/services/referensi/unitKerja-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function UnitKerja() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = { draw: 0 }
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
UnitKerjaList(params).then((res) => setData(res.data))
}, [search, refresh])
const editUnitKerja = (data) => {
UnitKerjaGetOne({ ref_id: data.ref_id }).then((res) => {
if (res.status === 'ok') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteUnitKerja = () => {
UnitKerjaDelete({ ref_id: dialogDelete.ref_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editUnitKerja(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ ref_id: rowData.ref_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Unit Kerja</title>
</Head>
<Belakang>
<Judul>Unit Kerja</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormUnitKerja}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='unit_id' header='Kode Unit' sortable></Column>
<Column field='nama' header='Unit Kerja' sortable></Column>
<Column field='bobot' header='Bobot' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteUnitKerja} />
)}
</>
)
}
+120
View File
@@ -0,0 +1,120 @@
import { DatatablePrime } from '@/components/Datatables'
import { DialogDelete } from '@/components/Dialog'
import FormVerifikator from '@/components/Form/Verifikator'
import { Belakang } from '@/components/Layouts'
import { Judul } from '@/components/TextCustom'
import { VerifikatorDelete, VerifikatorGetOne, VerifikatorList } from '@/services/referensi/verifikator-service'
import Head from 'next/head'
import { Button } from 'primereact/button'
import { Column } from 'primereact/column'
import { Toast } from 'primereact/toast'
import { useEffect, useRef, useState } from 'react'
export default function Verifikator() {
const toast = useRef(null)
const [data, setData] = useState([])
const [dialogForm, setDialogForm] = useState(false)
const [dataEdit, setDataEdit] = useState([])
const [refresh, setRefresh] = useState(0)
const [search, setSearch] = useState('')
const [dialogDelete, setDialogDelete] = useState({})
useEffect(() => {
let params = {}
if (search !== null && search !== '') {
params.search = search
} else {
params.search = ''
}
VerifikatorList(params)
.then((res) => {
if (res.status === 'success') {
setData(res.data)
} else {
console.log('Verifikator list error', res)
}
})
.catch((err) => console.log(err))
}, [search, refresh])
const editVerifikator = (data) => {
VerifikatorGetOne({ user_id: data.user_id }).then((res) => {
if (res.status === 'success') {
setDataEdit(res.data)
setDialogForm(true)
} else {
console.log(res.message)
}
})
}
const deleteVerifikator = () => {
VerifikatorDelete({ user_id: dialogDelete.user_id }).then((res) => {
if (res.status === 'success') {
setRefresh(Math.random)
setDialogDelete({ visible: false })
toast.current.show({
severity: 'success',
detail: res.message,
closable: false,
})
} else {
setDialogDelete({ visible: false })
toast.current.show({
severity: 'error',
detail: res.message,
closable: false,
})
}
})
}
const actionBodyTemplate = (rowData) => {
return (
<div className='flex gap-x-2'>
<Button icon='pi pi-pencil' className='p-button-sm p-button-rounded' onClick={() => editVerifikator(rowData)} />
<Button
icon='pi pi-trash'
className='p-button-sm p-button-rounded p-button-danger'
onClick={() => setDialogDelete({ user_id: rowData.user_id, visible: true })}
/>
</div>
)
}
return (
<>
<Toast ref={toast} />
<Head>
<title>Verifikator</title>
</Head>
<Belakang>
<Judul>Verifikator</Judul>
<DatatablePrime
data={data}
setSearch={setSearch}
dialogForm={dialogForm}
setDialogForm={setDialogForm}
Form={FormVerifikator}
buttonAdd={true}
buttonAddLabel='Tambah data'
dataEdit={dataEdit}
setDataEdit={setDataEdit}
toast={toast}
refresh={setRefresh}
>
<Column field='nama' header='Nama' sortable></Column>
<Column field='unit' header='Unit Kerja' sortable></Column>
<Column field='bobot' header='Bobot' sortable></Column>
<Column header='Action' body={actionBodyTemplate} exportable={false}></Column>
</DatatablePrime>
</Belakang>
{dialogDelete.visible === true && (
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteVerifikator} />
)}
</>
)
}