Initial commit, repo migration
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = crlf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
||||||
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
NEXT_PUBLIC_BACKEND_API=
|
||||||
|
NEXT_PUBLIC_API_FILE=
|
||||||
9
.eslintrc.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next", "plugin:react/recommended", "next/core-web-vitals", "eslint:recommended"],
|
||||||
|
"globals": {
|
||||||
|
"React": "readonly"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": ["error", { "args": "after-used", "argsIgnorePattern": "^_" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# dev
|
||||||
|
*.md
|
||||||
11
.prettierrc.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module.exports = {
|
||||||
|
trailingComma: 'es5',
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
printWidth: 120,
|
||||||
|
useTabs: true,
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: 'always',
|
||||||
|
}
|
||||||
59
components/CClock/Clock.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Number } from './Number'
|
||||||
|
import { Word } from './Word'
|
||||||
|
|
||||||
|
const days = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']
|
||||||
|
|
||||||
|
export const Clock = ({ h24 = true }) => {
|
||||||
|
const [hour, setHour] = useState(0)
|
||||||
|
const [minute, setMinute] = useState(0)
|
||||||
|
const [second, setSecond] = useState(0)
|
||||||
|
const [day, setDay] = useState(0)
|
||||||
|
const [pm, setPm] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const update = () => {
|
||||||
|
const date = new Date()
|
||||||
|
let hour = date.getHours()
|
||||||
|
if (!h24) {
|
||||||
|
hour = hour % 12 || 12
|
||||||
|
}
|
||||||
|
setHour(hour)
|
||||||
|
setMinute(date.getMinutes())
|
||||||
|
setSecond(date.getSeconds())
|
||||||
|
setDay(date.getDay())
|
||||||
|
setPm(date.getHours() >= 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
update()
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [h24])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='clock shadow border m-2 rounded-lg flex items-center flex-col overflow-hidden px-4 py-7'>
|
||||||
|
<div className='calendar flex gap-4 2xl:text-3xl text-xl mb-3'>
|
||||||
|
{days.map((value, index) => (
|
||||||
|
<Word key={value} value={value} hidden={index != day} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='clock-row flex gap-4 items-center'>
|
||||||
|
<div className='hour 2xl:text-7xl text-5xl'>
|
||||||
|
<Number value={hour} />
|
||||||
|
<Word value={':'} />
|
||||||
|
<Number value={minute} />
|
||||||
|
<Word value={':'} />
|
||||||
|
<Number value={second} />
|
||||||
|
</div>
|
||||||
|
<div className='ampm 2xl:text-5xl text-4xl'>
|
||||||
|
<Word value={'AM'} hidden={pm} />
|
||||||
|
<Word value={'PM'} hidden={!pm} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
components/CClock/Number.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const Number = ({ value = 0 }) => {
|
||||||
|
const result = String(value).padStart(2, '0')
|
||||||
|
return (
|
||||||
|
<div className='digital'>
|
||||||
|
<p>88</p>
|
||||||
|
<p>{result}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
components/CClock/Word.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const Word = ({ value, hidden = false }) => {
|
||||||
|
const getStyle = () => {
|
||||||
|
return {
|
||||||
|
visibility: hidden ? 'hidden' : 'visible',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='digital'>
|
||||||
|
<p>{value}</p>
|
||||||
|
<p style={getStyle()}>{value}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
36
components/CardCustom.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
export const CardCustom = ({ header, content, type = 'success' }) => {
|
||||||
|
let backgroundColor
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
backgroundColor = 'bg-green-100'
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'secondary':
|
||||||
|
backgroundColor = 'bg-gray-100'
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'biru':
|
||||||
|
backgroundColor = 'bg-biru'
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
backgroundColor = 'bg-green-100'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='border text-center'>
|
||||||
|
<p className={`font-semibold py-1 ${backgroundColor}`}>{header}</p>
|
||||||
|
<p className='p-1'>{content}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CardFilterLaporan = ({ header, children }) => {
|
||||||
|
return (
|
||||||
|
<div className='border rounded shadow mb-5'>
|
||||||
|
<p className={`font-semibold p-1 bg-orange3 text-center text-xl`}>{header}</p>
|
||||||
|
<div className='flex justify-center p-3'>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
97
components/Datatables-v2.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { DataTable } from 'primereact/datatable'
|
||||||
|
import { Dropdown } from 'primereact/dropdown'
|
||||||
|
import { Paginator } from 'primereact/paginator'
|
||||||
|
import { DialogDashboard, DialogDelete } from './Dialog'
|
||||||
|
|
||||||
|
export const DatatablePrimeV2 = ({
|
||||||
|
children,
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
sort,
|
||||||
|
first,
|
||||||
|
setLength,
|
||||||
|
totalRecords,
|
||||||
|
setPage,
|
||||||
|
setFirst,
|
||||||
|
length,
|
||||||
|
onSort,
|
||||||
|
dataDialog = { no_spp: false },
|
||||||
|
displayDialog,
|
||||||
|
setDisplayDialog,
|
||||||
|
dialogDelete = { visible: false },
|
||||||
|
setDialogDelete,
|
||||||
|
deleteTagihan,
|
||||||
|
page,
|
||||||
|
expandRow,
|
||||||
|
}) => {
|
||||||
|
const onPageChange = (event) => {
|
||||||
|
setPage(event.page)
|
||||||
|
setFirst(event.first)
|
||||||
|
}
|
||||||
|
|
||||||
|
const templatePaginator = {
|
||||||
|
layout: 'RowsPerPageDropdown FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport',
|
||||||
|
RowsPerPageDropdown: (options) => {
|
||||||
|
const dropdownOptions = [
|
||||||
|
{ label: 10, value: 10 },
|
||||||
|
{ label: 50, value: 50 },
|
||||||
|
{ label: 100, value: 100 },
|
||||||
|
{ label: 'All', value: options.totalRecords },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
value={options.value}
|
||||||
|
options={dropdownOptions}
|
||||||
|
onChange={(e) => {
|
||||||
|
options.onChange(e)
|
||||||
|
setLength(e.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
CurrentPageReport: (options) => {
|
||||||
|
return (
|
||||||
|
<span className='w-fit ml-4' style={{ color: 'var(--text-color)', userSelect: 'none', textAlign: 'center' }}>
|
||||||
|
{options.first} - {options.last} of {options.totalRecords}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DataTable
|
||||||
|
value={data}
|
||||||
|
size='small'
|
||||||
|
className='dt-dashboard'
|
||||||
|
stripedRows
|
||||||
|
loading={loading}
|
||||||
|
sortMode='multiple'
|
||||||
|
removableSort={true}
|
||||||
|
multiSortMeta={sort}
|
||||||
|
onSort={onSort}
|
||||||
|
{...expandRow}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DataTable>
|
||||||
|
{(data.length > 5 || page > 0) && (
|
||||||
|
<Paginator
|
||||||
|
first={first}
|
||||||
|
rows={length}
|
||||||
|
template={templatePaginator}
|
||||||
|
totalRecords={totalRecords}
|
||||||
|
onPageChange={onPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{dataDialog.no_spp && (
|
||||||
|
<DialogDashboard data={dataDialog} displayDialog={displayDialog} setDisplayDialog={setDisplayDialog} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{dialogDelete.visible === true && (
|
||||||
|
<DialogDelete data={dialogDelete} setDelete={setDialogDelete} onCallback={deleteTagihan} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
191
components/Datatables.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Card } from 'primereact/card'
|
||||||
|
import { DataTable } from 'primereact/datatable'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
import { Paginator } from 'primereact/paginator'
|
||||||
|
import { Toolbar } from 'primereact/toolbar'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CUSTOM DATATABLES PRIME REACT
|
||||||
|
* @children column-column data
|
||||||
|
* @data isi dari datatables
|
||||||
|
* @dataDrawPrev untuk cek apakah ada data sebelumnya
|
||||||
|
* @dataDrawNext untuk cek apakah ada data setelahnya
|
||||||
|
* @draw untuk merubah draw/page datatables
|
||||||
|
* @setDraw untuk merubah draw/page datatables
|
||||||
|
* @buttonUpload button untuk upload data
|
||||||
|
* @buttonUploadLabel label button add
|
||||||
|
* @buttonAdd button untuk add data
|
||||||
|
* @buttonAddLabel label button add
|
||||||
|
* @dialogForm usestate untuk open/close dialog/modal form
|
||||||
|
* @setDialogForm set dialog form modal untuk set open/close
|
||||||
|
* @Form form modal dynamic
|
||||||
|
* @dataEdit state yang menyimpan data yang akan di edit
|
||||||
|
* @setDataEdit set state data edit
|
||||||
|
* @refresh state untuk refresh datatables
|
||||||
|
* @toast reference untuk toast
|
||||||
|
* @setSearch set state untuk pencarian pada datatables
|
||||||
|
* @buttonAddCustom untuk add pengajuan tagihan
|
||||||
|
* @buttonAddCustomLabel label button add custom
|
||||||
|
* @expandRow untuk detail row
|
||||||
|
* @fiturSearch untuk tampil kolom search
|
||||||
|
*/
|
||||||
|
export const DatatablePrime = ({
|
||||||
|
children,
|
||||||
|
data,
|
||||||
|
dataDrawPrev = 0,
|
||||||
|
dataDrawNext = 0,
|
||||||
|
draw,
|
||||||
|
setDraw,
|
||||||
|
buttonUpload,
|
||||||
|
buttonUploadLabel,
|
||||||
|
buttonAdd,
|
||||||
|
buttonAddLabel,
|
||||||
|
dialogForm,
|
||||||
|
setDialogForm,
|
||||||
|
setDialogUpload,
|
||||||
|
Form,
|
||||||
|
dataEdit,
|
||||||
|
setDataEdit,
|
||||||
|
refresh,
|
||||||
|
toast,
|
||||||
|
setSearch,
|
||||||
|
buttonAddCustom,
|
||||||
|
buttonAddCustomLabel,
|
||||||
|
linkCustom,
|
||||||
|
expandRow,
|
||||||
|
fiturSearch = true,
|
||||||
|
showHeader = true,
|
||||||
|
}) => {
|
||||||
|
const first = 0
|
||||||
|
const rows = 30
|
||||||
|
const router = useRouter()
|
||||||
|
const keyUpSearch = (data) => {
|
||||||
|
setSearch(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftToolbarTemplate = () => {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightToolbarTemplate = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='flex gap-x-3'>
|
||||||
|
{buttonUpload && (
|
||||||
|
<Button
|
||||||
|
className='p-button-sm'
|
||||||
|
label={buttonUploadLabel}
|
||||||
|
onClick={() => {
|
||||||
|
setDialogUpload(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{buttonAdd && (
|
||||||
|
<Button
|
||||||
|
className='p-button-sm p-button-success'
|
||||||
|
label={buttonAddLabel}
|
||||||
|
onClick={() => {
|
||||||
|
setDialogForm(true), setDataEdit([])
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{buttonAddCustom && (
|
||||||
|
<Button
|
||||||
|
className='p-button-sm'
|
||||||
|
label={buttonAddCustomLabel}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(linkCustom)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dtHeader = () => {
|
||||||
|
return (
|
||||||
|
<form className='flex justify-end'>
|
||||||
|
<div className='flex gap-x-2'>
|
||||||
|
{fiturSearch && (
|
||||||
|
<div className='p-inputgroup'>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm dt-search'
|
||||||
|
onChange={(e) => keyUpSearch(e.target.value)}
|
||||||
|
placeholder='Search'
|
||||||
|
/>
|
||||||
|
<span className='p-inputgroup-addon'>
|
||||||
|
<i className='pi pi-search' />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const templatePaginator = {
|
||||||
|
layout: 'PrevPageLink NextPageLink',
|
||||||
|
PrevPageLink: () => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='p-paginator-prev p-paginator-element p-link'
|
||||||
|
onClick={() => setDraw(draw > 1 ? draw - 1 : draw)}
|
||||||
|
disabled={dataDrawPrev.length > 0 ? false : true}
|
||||||
|
>
|
||||||
|
<span className='p-3'>Previous</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
NextPageLink: () => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='p-paginator-next p-paginator-element p-link'
|
||||||
|
onClick={() => setDraw(draw + 1)}
|
||||||
|
disabled={dataDrawNext.length > 0 ? false : true}
|
||||||
|
>
|
||||||
|
<span className='p-3'>Next</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card className='dt-card'>
|
||||||
|
<Toolbar className='dt-toolbar' left={leftToolbarTemplate} right={rightToolbarTemplate} />
|
||||||
|
<DataTable
|
||||||
|
value={data}
|
||||||
|
className='dt-prime'
|
||||||
|
stripedRows
|
||||||
|
scrollable
|
||||||
|
scrollHeight='40rem'
|
||||||
|
size='small'
|
||||||
|
header={showHeader && dtHeader}
|
||||||
|
{...expandRow}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DataTable>
|
||||||
|
|
||||||
|
{(data.length === 30 || draw > 1) && (
|
||||||
|
<Paginator first={first} rows={rows} template={templatePaginator}></Paginator>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{dialogForm && (
|
||||||
|
<Form
|
||||||
|
dialogForm={dialogForm}
|
||||||
|
setDialogForm={setDialogForm}
|
||||||
|
dataEdit={dataEdit}
|
||||||
|
setDataEdit={setDataEdit}
|
||||||
|
refresh={refresh}
|
||||||
|
toast={toast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
197
components/Dialog.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { TabPanel, TabView } from 'primereact/tabview'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export const DialogDelete = ({ data, setDelete, onCallback }) => {
|
||||||
|
const modelFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => setDelete({ visible: false })}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Delete' className='p-button-sm p-button-primary' onClick={onCallback} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header='Delete Data'
|
||||||
|
className='p-dialog-delete'
|
||||||
|
visible={data.visible}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '25vw' }}
|
||||||
|
footer={modelFooter}
|
||||||
|
>
|
||||||
|
<p>Are you sure you want to delete this data?</p>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogDashboard = ({ data, displayDialog, setDisplayDialog }) => {
|
||||||
|
const [dataFilter, setDataFilter] = useState([{ NO_SP2D: '-', TGL_SPM: '', NO_SPM: '-', TGL_SP2D: '' }])
|
||||||
|
|
||||||
|
// NOMER SPP
|
||||||
|
useEffect(() => {
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: process.env.NEXT_PUBLIC_API_SAKTI_REALISASI,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ` + process.env.NEXT_PUBLIC_TOKEN_SAKTI,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
let dataFilter = response.data.filter((dataResponse) => dataResponse.NO_SPP === data.no_spp)
|
||||||
|
if (dataFilter.length > 0) {
|
||||||
|
setDataFilter(dataFilter)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}, [data.no_spp])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
header='Histori Pengajuan'
|
||||||
|
visible={displayDialog}
|
||||||
|
onHide={() => setDisplayDialog(false)}
|
||||||
|
headerClassName='header-history'
|
||||||
|
className='dialog-history 2xl:w-[90vw] lg:w-[80vw]'
|
||||||
|
draggable={false}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 gap-x-3 border-b-2 pb-7 mb-3'>
|
||||||
|
<table className='h-fit border rounded'>
|
||||||
|
<tbody>
|
||||||
|
<tr className=''>
|
||||||
|
<td className='border-b px-3 py-2 w-44 font-semibold'>Unit PPK</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>{data.nama_unit}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>No SPP</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>{data.no_spp}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>Kategori</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>{data.nama_tagihan}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>No SP2D</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>{dataFilter[0].NO_SP2D}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>Tanggal SP2D</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>
|
||||||
|
{dataFilter[0].TGL_SP2D ? moment(dataFilter[0].TGL_SP2D).format('DD MMMM YYYY') : '-'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>No SPM</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>{dataFilter[0].NO_SPM}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='border-b px-3 py-2 font-semibold'>Tanggal SPM</td>
|
||||||
|
<td className='border-b px-3 py-2 w-1'>:</td>
|
||||||
|
<td className='border-b px-3 py-2'>
|
||||||
|
{dataFilter[0].TGL_SPM ? moment(dataFilter[0].TGL_SPM).format('DD MMMM YYYY') : '-'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div className='block h-72 overflow-y-scroll'>
|
||||||
|
<table style={{ width: '100%' }}>
|
||||||
|
<thead className='bg-green-200'>
|
||||||
|
<tr>
|
||||||
|
<th className='border px-3 py-2 font-semibold'>#</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold'>Tanggal</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold'>Keterangan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.history.map((value, index) => {
|
||||||
|
return (
|
||||||
|
<tr key={index}>
|
||||||
|
<td className='border px-3 py-2'>{index + 1}</td>
|
||||||
|
<td className='border px-3 py-2'>{moment(value.tanggal).format('DD MMM YYYY - HH:mm')}</td>
|
||||||
|
<td className={`border px-3 py-2 ${value.status === 'Berkas dikembalikan' && 'text-red-500'}`}>
|
||||||
|
{value.status}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className='text-xl font-semibold mb-1'>Catatan</p>
|
||||||
|
<TabView>
|
||||||
|
{data.details.map((value, index) => {
|
||||||
|
return (
|
||||||
|
<TabPanel header={`Tagihan ${index + 1}`} key={index}>
|
||||||
|
<table className='w-full mb-7'>
|
||||||
|
<thead className='bg-red-200'>
|
||||||
|
<tr>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>No Tagihan</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Akun</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Untuk Keperluan</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Status Tagihan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{value.nomor_tagihan}</td>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{value.akun_name}</td>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{value.keperluan}</td>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>-</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<TabView>
|
||||||
|
{value.syarat.map((valueSyarat, indexSyarat) => {
|
||||||
|
return (
|
||||||
|
<TabPanel header={valueSyarat.nama} key={indexSyarat}>
|
||||||
|
<table className='w-full'>
|
||||||
|
<thead className='bg-blue-200'>
|
||||||
|
<tr>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Catatan Verif</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Catatan Bendahara</th>
|
||||||
|
<th className='border px-3 py-2 font-semibold lg:text-sm'>Catatan SPM</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{valueSyarat.verif_note}</td>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{valueSyarat.bendahara_note}</td>
|
||||||
|
<td className='border px-3 py-2 lg:text-sm'>{valueSyarat.spm_note}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</TabPanel>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TabView>
|
||||||
|
</TabPanel>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TabView>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
134
components/Form/Akun.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { AkunCreate, AkunUpdate } from '@/services/referensi/akun-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormAkun({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
akun_id: dataEdit.akun_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.akun_id) errors.akun_id = 'Error akun is required.'
|
||||||
|
if (!data.nama) errors.nama = 'Error name is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.akun_id) {
|
||||||
|
AkunUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
AkunCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.akun_id ? 'Update Akun' : 'Add Akun'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-3'>
|
||||||
|
<label className='block'>Nomor Akun</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='akun_id'
|
||||||
|
value={formik.values.akun_id}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Nomor Akun'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('akun_id')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama Akun</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Nama Akun'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
149
components/Form/JamKerja.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { JamKerjaCreate } from '@/services/manajemen/jam-kerja-service'
|
||||||
|
import { Formik } from 'formik'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Calendar } from 'primereact/calendar'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { MultiSelect } from 'primereact/multiselect'
|
||||||
|
|
||||||
|
const dbHari = [
|
||||||
|
{ name: 'Senin', code: 'senin' },
|
||||||
|
{ name: 'Selasa', code: 'selasa' },
|
||||||
|
{ name: 'Rabu', code: 'rabu' },
|
||||||
|
{ name: 'Kamis', code: 'kamis' },
|
||||||
|
{ name: 'Jumat', code: 'jumat' },
|
||||||
|
{ name: 'Sabtu', code: 'sabtu' },
|
||||||
|
{ name: 'Minggu', code: 'minggu' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function FormJamKerja({ dialogForm, setDialogForm, dataEdit, toast, refresh }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
user_id: dataEdit.user_id || '',
|
||||||
|
hari: dataEdit.hari || null,
|
||||||
|
jam_masuk: dataEdit.jam_masuk || null,
|
||||||
|
jam_keluar: dataEdit.jam_keluar || null,
|
||||||
|
}}
|
||||||
|
onSubmit={(data, { setSubmitting }) => {
|
||||||
|
Object.assign(data, {
|
||||||
|
jam_masuk: moment(data.jam_masuk).format('HH:mm'),
|
||||||
|
jam_keluar: moment(data.jam_keluar).format('HH:mm'),
|
||||||
|
})
|
||||||
|
let time = data.jam_masuk + '-' + data.jam_keluar
|
||||||
|
let days = ''
|
||||||
|
let panjang_array = data.hari.length
|
||||||
|
data.hari.map((values, i) => {
|
||||||
|
let plus1 = i + 1
|
||||||
|
let koma = plus1 < panjang_array ? ',' : ''
|
||||||
|
days += values.code + koma
|
||||||
|
})
|
||||||
|
|
||||||
|
let dataKirim = { days, time }
|
||||||
|
JamKerjaCreate(dataKirim).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
setDialogForm(false)
|
||||||
|
refresh(Math.random)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
refresh(Math.random)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setSubmitting(false)
|
||||||
|
}}
|
||||||
|
validateOnChange={false}
|
||||||
|
>
|
||||||
|
{({ values, handleChange, handleSubmit, isSubmitting, resetForm }) => {
|
||||||
|
const dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Save'
|
||||||
|
className='p-button-sm p-button-primary'
|
||||||
|
onClick={handleSubmit}
|
||||||
|
loading={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
header={values.user_id ? 'Update Jam Kerja' : 'Add Jam Kerja'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Pilih Hari</label>
|
||||||
|
<MultiSelect
|
||||||
|
id='hari'
|
||||||
|
value={values.hari}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={dbHari}
|
||||||
|
optionLabel='name'
|
||||||
|
placeholder='Select Hari'
|
||||||
|
maxSelectedLabels={3}
|
||||||
|
className='w-full md:w-20rem'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-2 gap-3'>
|
||||||
|
<div className='mb-2'>
|
||||||
|
<label className='block'>Jam Masuk</label>
|
||||||
|
<Calendar
|
||||||
|
id='jam_masuk'
|
||||||
|
value={values.jam_masuk}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='Masukkan Jam Masuk'
|
||||||
|
timeOnly
|
||||||
|
hourFormat='24'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='mb-2'>
|
||||||
|
<label className='block'>Jam Keluar</label>
|
||||||
|
<Calendar
|
||||||
|
id='jam_keluar'
|
||||||
|
value={values.jam_keluar}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='Masukkan Jam Keluar'
|
||||||
|
timeOnly
|
||||||
|
hourFormat='24'
|
||||||
|
minDate={values.jam_masuk}
|
||||||
|
disabled={values.jam_masuk ? false : true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
122
components/Form/JenisBelanja.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { JenisBelanjaCreate, JenisBelanjaUpdate } from '@/services/referensi/jenisBelanja-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormJenisBelanja({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.jenis_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.ref_id) {
|
||||||
|
JenisBelanjaUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
JenisBelanjaCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Jenis Belanja' : 'Add Jenis Belanja'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama jenis belanja'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
149
components/Form/JenisKegiatan.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { JenisBelanjaList } from '@/services/referensi/jenisBelanja-service'
|
||||||
|
import { JenisKegiatanCreate, JenisKegiatanUpdate } from '@/services/referensi/jenisKegiatan-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { Dropdown } from 'primereact/dropdown'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function FormJenisKegiatan({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const [optionJenisBelanja, setOptionJenisBelanja] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
JenisBelanjaList({ draw: 0 }).then((res) => setOptionJenisBelanja(res.data))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.jenis_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
jenis_belanja_id: dataEdit.jenis_belanja_id || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
if (!data.jenis_belanja_id) errors.jenis_belanja_id = 'Jenis Belanja is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.ref_id) {
|
||||||
|
JenisKegiatanUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
JenisKegiatanCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Jenis Kegiatan' : 'Add Jenis Kegiatan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid gap-y-3'>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama jenis kegiatan'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Jenis Belanja</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='jenis_belanja_id'
|
||||||
|
options={optionJenisBelanja}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='jenis_id'
|
||||||
|
value={formik.values.jenis_belanja_id}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
showClear={formik.values.jenis_belanja_id ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select Jenis Belanja'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('jenis_belanja_id')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
122
components/Form/JenisTagihan.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { JenisTagihanCreate, JenisTagihanUpdate } from '@/services/referensi/jenisTagihan-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormJenisTagihan({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.jenis_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.ref_id) {
|
||||||
|
JenisTagihanUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
JenisTagihanCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Jenis Tagihan' : 'Add Jenis Tagihan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama jenis tagihan'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
236
components/Form/Persyaratan.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import { JenisBelanjaList } from '@/services/referensi/jenisBelanja-service'
|
||||||
|
import { JenisKegiatanList } from '@/services/referensi/jenisKegiatan-service'
|
||||||
|
import { JenisTagihanList } from '@/services/referensi/jenisTagihan-service'
|
||||||
|
import { PersyaratanCreate, PersyaratanUpdate } from '@/services/referensi/persyaratan-service'
|
||||||
|
import { FieldArray, Formik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { Dropdown } from 'primereact/dropdown'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
import { InputTextarea } from 'primereact/inputtextarea'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function FormPersyaratan({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const [optionJenisTagihan, setOptionJenisTagihan] = useState([])
|
||||||
|
const [optionJenisBelanja, setOptionJenisBelanja] = useState([])
|
||||||
|
const [optionJenisKegiatan, setOptionJenisKegiatan] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
JenisTagihanList({ draw: 0 }).then((res) => setOptionJenisTagihan(res.data))
|
||||||
|
JenisBelanjaList({ draw: 0 }).then((res) => setOptionJenisBelanja(res.data))
|
||||||
|
JenisKegiatanList({ draw: 0 }).then((res) => setOptionJenisKegiatan(res.data))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
syarat_id: dataEdit.syarat_id || '',
|
||||||
|
jenis_tagihan: dataEdit.jenis_tagihan_id || '',
|
||||||
|
jenis_belanja: dataEdit.jenis_belanja_id || '',
|
||||||
|
jenis_kegiatan: dataEdit.jenis_kegiatan_id || '',
|
||||||
|
syarat: [{}],
|
||||||
|
}}
|
||||||
|
validate={(data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.jenis_tagihan) errors.jenis_tagihan = 'Jenis Tagihan is required.'
|
||||||
|
if (!data.jenis_belanja) errors.jenis_belanja = 'Jenis Belanja is required.'
|
||||||
|
if (!data.jenis_kegiatan) errors.jenis_kegiatan = 'Jenis Kegiatan is required.'
|
||||||
|
// if (!data.syarat) errors.syarat = 'Syarat Dokumen is required.'
|
||||||
|
// if (!data.keterangan) errors.keterangan = 'Keterangan is required.'
|
||||||
|
return errors
|
||||||
|
}}
|
||||||
|
onSubmit={(data) => {
|
||||||
|
if (data.syarat_id) {
|
||||||
|
PersyaratanUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
PersyaratanCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
validateOnChange={false}
|
||||||
|
>
|
||||||
|
{({ values, errors, handleChange, handleSubmit, isSubmitting, resetForm }) => {
|
||||||
|
const dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Save'
|
||||||
|
className='p-button-sm p-button-primary'
|
||||||
|
onClick={handleSubmit}
|
||||||
|
loading={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
header={values.syarat_id ? 'Update Persyaratan' : 'Add Persyaratan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '45vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className='p-fluid grid gap-y-2'>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Jenis Tagihan</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='jenis_tagihan'
|
||||||
|
options={optionJenisTagihan}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='jenis_id'
|
||||||
|
value={values.jenis_tagihan}
|
||||||
|
onChange={handleChange}
|
||||||
|
showClear={values.jenis_tagihan ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select Jenis Tagihan'
|
||||||
|
/>
|
||||||
|
{errors.jenis_tagihan && <div className='text-red-500 text-xs'>{errors.jenis_tagihan}</div>}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Jenis Belanja</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='jenis_belanja'
|
||||||
|
options={optionJenisBelanja}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='jenis_id'
|
||||||
|
value={values.jenis_belanja}
|
||||||
|
onChange={handleChange}
|
||||||
|
showClear={values.jenis_belanja ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select Jenis Belanja'
|
||||||
|
/>
|
||||||
|
{errors.jenis_belanja && <div className='text-red-500 text-xs'>{errors.jenis_belanja}</div>}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Jenis Kegiatan</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='jenis_kegiatan'
|
||||||
|
options={optionJenisKegiatan}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='jenis_id'
|
||||||
|
value={values.jenis_kegiatan}
|
||||||
|
onChange={handleChange}
|
||||||
|
showClear={values.jenis_kegiatan ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select Jenis Kegiatan'
|
||||||
|
/>
|
||||||
|
{errors.jenis_kegiatan && <div className='text-red-500 text-xs'>{errors.jenis_kegiatan}</div>}
|
||||||
|
</div>
|
||||||
|
<FieldArray
|
||||||
|
name='syarat'
|
||||||
|
render={(arrayHelpers) => (
|
||||||
|
<div className=''>
|
||||||
|
<label className='block'>Syarat</label>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
{values.syarat.map((syarat, index) => (
|
||||||
|
<div key={index} className='field col-12 flex gap-2 items-center'>
|
||||||
|
<div className='border p-3 rounded shadow my-2 w-full flex flex-col gap-2'>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<label className='block'>Nama Syarat {index + 1}</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name={`syarat.${index}.syarat`}
|
||||||
|
value={values.syarat[index].syarat}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='Masukkan Syarat Dokumen'
|
||||||
|
/>
|
||||||
|
{errors.syarat && errors.syarat[index] && (
|
||||||
|
<div className='text-red-500 text-xs'>{errors.syarat[index].syarat}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<label className='block'>Keterangan Syarat {index + 1}</label>
|
||||||
|
<InputTextarea
|
||||||
|
name={`syarat.${index}.keterangan`}
|
||||||
|
rows={3}
|
||||||
|
cols={30}
|
||||||
|
value={values.syarat[index].keterangan}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder='Masukkan Keterangan'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{index > 0 && (
|
||||||
|
<Button
|
||||||
|
icon='pi pi-times'
|
||||||
|
className='p-button-danger p-button-sm'
|
||||||
|
onClick={() => arrayHelpers.remove(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className='w-fit relative left-1/2 -translate-x-1/2'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
icon='pi pi-plus'
|
||||||
|
className='p-button-sm'
|
||||||
|
label='Tambah Syarat'
|
||||||
|
onClick={() => arrayHelpers.push({ syarat: '', keterangan: '' })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
220
components/Form/Role.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import { RoleCreate, RoleUpdate } from '@/services/manajemen/role-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
import { Tree } from 'primereact/tree'
|
||||||
|
|
||||||
|
export default function FormRole({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
role_id: dataEdit.role_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
access: dataEdit.access || [],
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
if (!data.access) errors.access = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
Object.assign(data, { access: JSON.stringify(data.access) })
|
||||||
|
if (data.role_id) {
|
||||||
|
RoleUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
RoleCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
key: 'dashboard',
|
||||||
|
label: 'Dashboard',
|
||||||
|
icon: 'pi pi-fw pi-home',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'referensi',
|
||||||
|
label: 'Referensi',
|
||||||
|
icon: 'pi pi-fw pi-database',
|
||||||
|
children: [
|
||||||
|
{ key: 'referensi/jenis-tagihan', label: 'Jenis Tagihan' },
|
||||||
|
{ key: 'referensi/jenis-belanja', label: 'Jenis Belanja' },
|
||||||
|
{ key: 'referensi/jenis-kegiatan', label: 'Jenis Kegiatan' },
|
||||||
|
{ key: 'referensi/akun', label: 'Akun' },
|
||||||
|
{ key: 'referensi/persyaratan', label: 'Persyaratan' },
|
||||||
|
{ key: 'referensi/unit-kerja', label: 'Unit Kerja' },
|
||||||
|
{ key: 'referensi/verifikator', label: 'Verifikator' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pengajuan-tagihan',
|
||||||
|
label: 'Pengajuan Tagihan',
|
||||||
|
icon: 'pi pi-fw pi-dollar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'laporan',
|
||||||
|
label: 'Laporan',
|
||||||
|
icon: 'pi pi-fw pi-chart-line',
|
||||||
|
children: [
|
||||||
|
{ key: 'laporan/per-user', label: 'Per User (Verifikator, kasub verif)' },
|
||||||
|
// { key: 'laporan/approval', label: 'Approval (Kabag verif, bendahara, ppspm)' },
|
||||||
|
{ key: 'laporan/rekap-bulanan-per-user', label: 'Rekapitulasi bulanan per user' },
|
||||||
|
{ key: 'laporan/rekap-verif-per-bulan', label: 'Rekapitulasi hasil verifikasi per bulan' },
|
||||||
|
{ key: 'laporan/rekap-per-unit-kerja', label: 'Rekapitulasi per unit kerja' },
|
||||||
|
{ key: 'laporan/rekap-per-jenis-belanja', label: 'Rekapitulasi per jenis belanja (BAS)' },
|
||||||
|
{ key: 'laporan/verif-per-bulan', label: 'Hasil verifikasi per bulan (detail)' },
|
||||||
|
{
|
||||||
|
key: 'laporan/pengendalian-waktu-penyelesaian',
|
||||||
|
label: 'Pengendalian waktu penyelesaian tagihan per unit per bulan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'laporan/pengendalian-waktu-penyelesaian-detail',
|
||||||
|
label: 'Pengendalian waktu penyelesaian tagihan per unit per bulan (detail)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'otorisasi',
|
||||||
|
label: 'Otorisasi',
|
||||||
|
icon: 'pi pi-fw pi-lock',
|
||||||
|
children: [
|
||||||
|
{ key: 'otorisasi/verifikasi', label: 'verifikasi' },
|
||||||
|
{ key: 'otorisasi/rekomendasi', label: 'rekomendasi' },
|
||||||
|
{ key: 'otorisasi/persetujuan', label: 'persetujuan' },
|
||||||
|
{ key: 'otorisasi/pengesahan', label: 'pengesahan' },
|
||||||
|
{ key: 'otorisasi/spm', label: 'spm' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'manajemen',
|
||||||
|
label: 'Manajemen',
|
||||||
|
icon: 'pi pi-fw pi-database',
|
||||||
|
children: [
|
||||||
|
{ key: 'manajemen/user', label: 'user' },
|
||||||
|
{ key: 'manajemen/role', label: 'role' },
|
||||||
|
{ key: 'manajemen/jam-kerja', label: 'jam kerja' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.role_id ? 'Update Role' : 'Add Role'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '60vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='grid grid-cols-2 gap-x-3'>
|
||||||
|
{/* KIRI */}
|
||||||
|
<div className='kiri'>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>List menu</label>
|
||||||
|
<Tree
|
||||||
|
name='access'
|
||||||
|
value={data}
|
||||||
|
selectionMode='checkbox'
|
||||||
|
selectionKeys={formik.values.access}
|
||||||
|
onSelectionChange={(e) => {
|
||||||
|
formik.setFieldValue('access', e.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KANAN */}
|
||||||
|
<div className='kanan'>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama Role</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama role'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
122
components/Form/Tagihan.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { TagihanCreate, TagihanUpdate } from '@/services/pengajuan-tagihan/tagihan-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormTagihan({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.ref_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.ref_id) {
|
||||||
|
TagihanUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
TagihanCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: 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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Tagihan' : 'Add Tagihan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama jenis tagihan'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
148
components/Form/UnitKerja.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { UnitKerjaCreate, UnitKerjaUpdate } from '@/services/referensi/unitKerja-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormUnitKerja({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.ref_id || '',
|
||||||
|
unit_id: dataEdit.unit_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
bobot: dataEdit.bobot || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.unit_id) errors.unit_id = 'Kode Unit is required.'
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
if (!data.bobot) errors.bobot = 'Bobot is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.ref_id) {
|
||||||
|
UnitKerjaUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
UnitKerjaCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const isFieldValid = (field) => !!(formik.errors[field] && formik.touched[field])
|
||||||
|
const errorFieldMessage = (field) => {
|
||||||
|
return (
|
||||||
|
isFieldValid(field) && (
|
||||||
|
<small className='p-error' style={{ fontSize: '11px' }}>
|
||||||
|
{formik.errors[field]}
|
||||||
|
</small>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.unit_id ? 'Update Jenis Tagihan' : 'Add Jenis Tagihan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid gap-y-3'>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Kode Unit</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='unit_id'
|
||||||
|
value={formik.values.unit_id}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan Kode Unit'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('unit_id')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama unit kerja'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Bobot</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='bobot'
|
||||||
|
value={formik.values.bobot}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan bobot'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('bobot')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
267
components/Form/User.js
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import { RoleList } from '@/services/manajemen/role-service'
|
||||||
|
import { UserCreate, UserUpdate } from '@/services/manajemen/user-service'
|
||||||
|
import { UnitKerjaList } from '@/services/referensi/unitKerja-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { Dropdown } from 'primereact/dropdown'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
import { Password } from 'primereact/password'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
export default function FormUser({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const [optionUnitKerja, setOptionUnitKerja] = useState([])
|
||||||
|
const [optionRole, setOptionRole] = useState([])
|
||||||
|
const [showUnitKerja, setShowUnitKerja] = useState(true)
|
||||||
|
|
||||||
|
const SignupSchema = Yup.object().shape({
|
||||||
|
name: Yup.string().min(2, 'Too Short!').max(50, 'Too Long!').required('Nama is required'),
|
||||||
|
username: Yup.string().min(2, 'Too Short!').max(50, 'Too Long!').required('Username is required'),
|
||||||
|
email: Yup.string().email('Invalid email').required('Email is Required'),
|
||||||
|
password: Yup.string().when('old_password', {
|
||||||
|
is: true,
|
||||||
|
then: Yup.string()
|
||||||
|
.required('Password is required')
|
||||||
|
.matches(
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#%&])(?=.{8,})/,
|
||||||
|
'Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character'
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
password_retype: Yup.string().when('old_password', {
|
||||||
|
is: true,
|
||||||
|
then: Yup.string()
|
||||||
|
.oneOf([Yup.ref('password'), null], 'Passwords must match')
|
||||||
|
.required('Password confirm is required'),
|
||||||
|
}),
|
||||||
|
// unit: Yup.string().required('Unit kerja is required'),
|
||||||
|
role: Yup.string().required('Role is required'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
user_id: dataEdit.user_id || '',
|
||||||
|
name: dataEdit.nama || '',
|
||||||
|
username: dataEdit.username || '',
|
||||||
|
email: dataEdit.email || '',
|
||||||
|
password: '',
|
||||||
|
password_retype: '',
|
||||||
|
unit: dataEdit.unit || '',
|
||||||
|
role: dataEdit.user_role || '',
|
||||||
|
},
|
||||||
|
validationSchema: SignupSchema,
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (data.user_id) {
|
||||||
|
UserUpdate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
UserCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
UnitKerjaList({ draw: 0 }).then((res) => setOptionUnitKerja(res.data))
|
||||||
|
RoleList().then((res) => setOptionRole(res.data))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const header = <h6>Pick a password</h6>
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<p className='mt-3'>Suggestions</p>
|
||||||
|
<ul className='pl-2 ml-2 mt-0' style={{ lineHeight: '1.5' }}>
|
||||||
|
<li>At least one lowercase</li>
|
||||||
|
<li>At least one uppercase</li>
|
||||||
|
<li>At least one numeric</li>
|
||||||
|
<li>At least one special case character</li>
|
||||||
|
<li>Minimum 8 characters</li>
|
||||||
|
<li>If edit password not required</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleRole = (e) => {
|
||||||
|
if (e.target.value === 'ROL-a713f345-9b9a-453a-8f77-33fd8335078f') {
|
||||||
|
setShowUnitKerja(false)
|
||||||
|
} else {
|
||||||
|
setShowUnitKerja(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
formik.setFieldValue('role', e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.user_id ? 'Update User' : 'Add User'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='kanan'>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='name'
|
||||||
|
value={formik.values.name}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('name')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Email</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='email'
|
||||||
|
value={formik.values.email}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan email'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('email')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Username</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='username'
|
||||||
|
value={formik.values.username}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan username'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('username')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Password</label>
|
||||||
|
<Password
|
||||||
|
name='password'
|
||||||
|
value={formik.values.password}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
header={header}
|
||||||
|
footer={footer}
|
||||||
|
toggleMask
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('password')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12 mb-2'>
|
||||||
|
<label className='block'>Password Confirm</label>
|
||||||
|
<Password
|
||||||
|
name='password_retype'
|
||||||
|
value={formik.values.password_retype}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
feedback={false}
|
||||||
|
toggleMask
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('password_retype')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Role</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='role'
|
||||||
|
options={optionRole}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='role_id'
|
||||||
|
value={formik.values.role}
|
||||||
|
onChange={(e) => handleRole(e)}
|
||||||
|
showClear={formik.values.role ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select role'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('role')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-4'>
|
||||||
|
<label className='block'>Unit Kerja</label>
|
||||||
|
<Dropdown
|
||||||
|
panelClassName='p-dropdown-form'
|
||||||
|
name='unit'
|
||||||
|
options={optionUnitKerja}
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='unit_id'
|
||||||
|
value={formik.values.unit}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
showClear={formik.values.unit ? true : false}
|
||||||
|
scrollHeight='200px'
|
||||||
|
placeholder='Select unit kerja'
|
||||||
|
disabled={showUnitKerja}
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('unit')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
79
components/Form/Verifikasi.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { InputText } from 'primereact/inputtext'
|
||||||
|
|
||||||
|
export default function FormVerifikasi({ dialogForm, setDialogForm, dataEdit }) {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
ref_id: dataEdit.ref_id || '',
|
||||||
|
nama: dataEdit.nama || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.nama) errors.nama = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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 dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Verifikasi' : 'Add Verifikasi'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid'>
|
||||||
|
<div className='field col-12 md:col-12 mb-0'>
|
||||||
|
<label className='block'>Nama</label>
|
||||||
|
<InputText
|
||||||
|
className='p-inputtext-sm'
|
||||||
|
name='nama'
|
||||||
|
value={formik.values.nama}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Masukkan nama jenis tagihan'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
131
components/Form/Verifikator.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { UserList } from '@/services/manajemen/user-service'
|
||||||
|
import { UnitKerjaList } from '@/services/referensi/unitKerja-service'
|
||||||
|
import { VerifikatorCreate } from '@/services/referensi/verifikator-service'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Button } from 'primereact/button'
|
||||||
|
import { Dialog } from 'primereact/dialog'
|
||||||
|
import { Dropdown } from 'primereact/dropdown'
|
||||||
|
import { MultiSelect } from 'primereact/multiselect'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function FormVerifikator({ dialogForm, setDialogForm, dataEdit, setDataEdit, refresh, toast }) {
|
||||||
|
const [ddVerifikator, setDdVerifikator] = useState([])
|
||||||
|
const [ddUserList, setDdUserList] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
UnitKerjaList({ draw: 0 }).then((res) => {
|
||||||
|
setDdVerifikator(res.data)
|
||||||
|
})
|
||||||
|
UserList({ draw: 0 }).then((res) => {
|
||||||
|
setDdUserList(res.data)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
unit_id: '',
|
||||||
|
user_id: dataEdit.user_id || '',
|
||||||
|
},
|
||||||
|
validate: (data) => {
|
||||||
|
let errors = {}
|
||||||
|
if (!data.unit_id) errors.unit_id = 'Kode Unit is required.'
|
||||||
|
if (!data.user_id) errors.user_id = 'Nama is required.'
|
||||||
|
return errors
|
||||||
|
},
|
||||||
|
onSubmit: (data) => {
|
||||||
|
VerifikatorCreate(data).then((res) => {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
refresh(Math.random)
|
||||||
|
formik.resetForm()
|
||||||
|
setDataEdit([])
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'success',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setDialogForm(false)
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
detail: res.message,
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const isFieldValid = (field) => !!(formik.errors[field] && formik.touched[field])
|
||||||
|
const errorFieldMessage = (field) => {
|
||||||
|
return (
|
||||||
|
isFieldValid(field) && (
|
||||||
|
<small className='p-error' style={{ fontSize: '11px' }}>
|
||||||
|
{formik.errors[field]}
|
||||||
|
</small>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogFooter = (
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
label='Cancel'
|
||||||
|
className='p-button-sm p-button-secondary'
|
||||||
|
onClick={() => {
|
||||||
|
formik.resetForm()
|
||||||
|
setDialogForm(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type='submit' label='Save' className='p-button-sm p-button-primary' onClick={formik.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
header={formik.values.ref_id ? 'Update Jenis Tagihan' : 'Add Jenis Tagihan'}
|
||||||
|
className='p-dialog-form'
|
||||||
|
visible={dialogForm}
|
||||||
|
draggable={false}
|
||||||
|
resizable={false}
|
||||||
|
position={'center'}
|
||||||
|
closable={false}
|
||||||
|
style={{ width: '30vw' }}
|
||||||
|
footer={dialogFooter}
|
||||||
|
>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className='p-fluid grid gap-y-3'>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Nama Verifikator</label>
|
||||||
|
<Dropdown
|
||||||
|
name='user_id'
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='user_id'
|
||||||
|
value={formik.values.user_id}
|
||||||
|
options={ddUserList}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Select nama verifikator'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('nama')}
|
||||||
|
</div>
|
||||||
|
<div className='field col-12 md:col-12'>
|
||||||
|
<label className='block'>Unit</label>
|
||||||
|
<MultiSelect
|
||||||
|
name='unit_id'
|
||||||
|
optionLabel='nama'
|
||||||
|
optionValue='unit_id'
|
||||||
|
value={formik.values.unit_id}
|
||||||
|
options={ddVerifikator}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
placeholder='Select unit'
|
||||||
|
/>
|
||||||
|
{errorFieldMessage('unit_id')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
components/Label.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const Label = ({ children, type }) => {
|
||||||
|
let backgroundColor
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
backgroundColor = 'bg-green-400'
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'danger':
|
||||||
|
backgroundColor = 'bg-red-400'
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'warning':
|
||||||
|
backgroundColor = 'bg-yellow-400'
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
backgroundColor = 'bg-green-400'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className={`border rounded-md px-3 text-sm py-1 ${backgroundColor} animate-pulse`}>{children}</span>
|
||||||
|
}
|
||||||
109
components/Layouts.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { useAuth } from '@/hooks/auth'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import Particles from 'react-tsparticles'
|
||||||
|
import { loadFull } from 'tsparticles'
|
||||||
|
import packageJson from './../package.json'
|
||||||
|
import { Menu } from './Menu'
|
||||||
|
import { Navbar } from './Navbar'
|
||||||
|
|
||||||
|
export const Depan = ({ children }) => {
|
||||||
|
useAuth({ middleware: 'guest', redirectIfAuthenticated: '/dashboard' })
|
||||||
|
|
||||||
|
/** PARTICLES */
|
||||||
|
const particlesInit = useCallback(async (engine) => {
|
||||||
|
await loadFull(engine)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex h-screen'>
|
||||||
|
<div className='kiri w-9/12 relative hidden md:block'>
|
||||||
|
<div className='relative h-screen'>
|
||||||
|
<Image src={'/img/bg-login.jpg'} alt='bg-login' layout='fill' objectFit='cover' priority />
|
||||||
|
</div>
|
||||||
|
<Particles
|
||||||
|
id='tsparticles'
|
||||||
|
init={particlesInit}
|
||||||
|
options={{
|
||||||
|
fpsLimit: 60,
|
||||||
|
interactivity: {
|
||||||
|
events: {
|
||||||
|
onHover: {
|
||||||
|
enable: true,
|
||||||
|
mode: 'grab',
|
||||||
|
},
|
||||||
|
resize: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
particles: {
|
||||||
|
color: {
|
||||||
|
value: '#ffffff',
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
color: '#ffffff',
|
||||||
|
distance: 130,
|
||||||
|
enable: true,
|
||||||
|
opacity: 1,
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
collisions: {
|
||||||
|
enable: true,
|
||||||
|
},
|
||||||
|
move: {
|
||||||
|
directions: 'none',
|
||||||
|
enable: true,
|
||||||
|
outModes: {
|
||||||
|
default: 'bounce',
|
||||||
|
},
|
||||||
|
random: false,
|
||||||
|
speed: 2,
|
||||||
|
straight: false,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
density: {
|
||||||
|
enable: true,
|
||||||
|
area: 800,
|
||||||
|
},
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
value: 0.5,
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
type: 'circle',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
value: { min: 1, max: 5 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detectRetina: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='absolute left-1/2 -translate-x-1/2 bottom-10 text-white flex gap-1 text-xs 2xl:text-base'>
|
||||||
|
<p>Copyright © 2022</p>
|
||||||
|
<p className=''>SISTEM APLIKASI VERIFIKASI ANGGARAN BKN RI</p>
|
||||||
|
</div>
|
||||||
|
<div className='absolute left-1/2 -translate-x-1/2 bottom-6 text-white flex gap-1 text-xs 2xl:text-base'>
|
||||||
|
<p>V{packageJson.version}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='kanan bg-abu md:w-3/12 px-7 relative'>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Belakang = ({ children }) => {
|
||||||
|
useAuth({ middleware: 'auth' })
|
||||||
|
return (
|
||||||
|
<div className=''>
|
||||||
|
<Navbar />
|
||||||
|
<Menu />
|
||||||
|
<div className='content 2xl:mx-72 mx-20 my-10'>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LandingPageLayout = ({ children }) => {
|
||||||
|
return <div className='md:grid md:grid-cols-1 items-center md:px-32 md:gap-3 gap-y-3 py-5 mx-3'>{children}</div>
|
||||||
|
}
|
||||||
323
components/Menu.js
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export const Menu = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { pathname } = router
|
||||||
|
const [menus, setMenus] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMenus(JSON.parse(localStorage.getItem('menus')))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const renderMenu = (target) => {
|
||||||
|
let ready = Object.keys(menus).find((data) => data === target)
|
||||||
|
if (!ready) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='navbar-detail 2xl:px-16 bg-biru2 h-12 flex items-center justify-center shadow'>
|
||||||
|
<div className='menu flex 2xl:gap-x-14 gap-x-10 2xl:text-base text-sm'>
|
||||||
|
<Link href={'/dashboard'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded ${
|
||||||
|
pathname.startsWith('/dashboard') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('dashboard')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-home mr-1' />
|
||||||
|
Dashboard
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded relative dropdown-toggle ${
|
||||||
|
pathname.startsWith('/referensi') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-database mr-1' />
|
||||||
|
Referensi
|
||||||
|
<div className='absolute rounded bg-biru2 w-max min-w-full 2xl:top-8 top-7 -left-0 py-3 z-10 flex flex-col gap-y-1 shadow dropdown-menu'>
|
||||||
|
<Link href={'/referensi/jenis-tagihan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/jenis-tagihan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/jenis-tagihan')}
|
||||||
|
>
|
||||||
|
Jenis Tagihan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/jenis-belanja'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/jenis-belanja' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/jenis-belanja')}
|
||||||
|
>
|
||||||
|
Jenis Belanja
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/jenis-kegiatan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/jenis-kegiatan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/jenis-kegiatan')}
|
||||||
|
>
|
||||||
|
Jenis Kegiatan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/akun'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/akun' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/akun')}
|
||||||
|
>
|
||||||
|
Jenis Pembayaran Kegiatan (Akun)
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/unit-kerja'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/unit-kerja' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/unit-kerja')}
|
||||||
|
>
|
||||||
|
Unit Kerja
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/persyaratan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/persyaratan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/persyaratan')}
|
||||||
|
>
|
||||||
|
Persyaratan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/referensi/verifikator'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/referensi/verifikator' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('referensi/verifikator')}
|
||||||
|
>
|
||||||
|
Verifikator
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link href={'/pengajuan-tagihan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded ${
|
||||||
|
pathname.startsWith('/pengajuan-tagihan') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('pengajuan-tagihan')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-dollar mr-1' />
|
||||||
|
Pengajuan Tagihan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded relative dropdown-toggle ${
|
||||||
|
pathname.startsWith('/otorisasi') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-lock mr-1' />
|
||||||
|
Otorisasi
|
||||||
|
<div className='absolute rounded bg-biru2 w-max min-w-full 2xl:top-8 top-7 -left-0 py-3 z-10 flex flex-col gap-y-1 shadow dropdown-menu'>
|
||||||
|
<Link href={'/otorisasi/verifikasi'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/otorisasi/verifikasi' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi/verifikasi')}
|
||||||
|
>
|
||||||
|
Verifikasi
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/otorisasi/rekomendasi'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/otorisasi/rekomendasi' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi/rekomendasi')}
|
||||||
|
>
|
||||||
|
Rekomendasi
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/otorisasi/persetujuan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/otorisasi/persetujuan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi/persetujuan')}
|
||||||
|
>
|
||||||
|
Persetujuan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/otorisasi/pengesahan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/otorisasi/pengesahan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi/pengesahan')}
|
||||||
|
>
|
||||||
|
Pengesahan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/otorisasi/spm'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/otorisasi/spm' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('otorisasi/spm')}
|
||||||
|
>
|
||||||
|
SPM
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded relative dropdown-toggle ${
|
||||||
|
pathname.startsWith('/laporan') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-chart-line mr-1' />
|
||||||
|
Laporan
|
||||||
|
<div className='absolute rounded bg-biru2 w-max min-w-full 2xl:top-8 top-7 -left-0 py-3 z-10 flex flex-col gap-y-1 shadow dropdown-menu'>
|
||||||
|
<Link href={'/laporan/per-user'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/per-user' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/per-user')}
|
||||||
|
>
|
||||||
|
Per User (Verifikator, kasub verif)
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/approval'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/approval' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/approval')}
|
||||||
|
>
|
||||||
|
Approval (Kabag verif, bendahara, ppspm)
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/rekap-bulanan-per-user'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/rekap-bulanan-per-user' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/rekap-bulanan-per-user')}
|
||||||
|
>
|
||||||
|
Rekapitulasi bulanan per user
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/rekap-verif-per-bulan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/rekap-verif-per-bulan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/rekap-verif-per-bulan')}
|
||||||
|
>
|
||||||
|
Rekapitulasi hasil verifikasi per bulan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/rekap-per-unit-kerja'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/rekap-per-unit-kerja' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/rekap-per-unit-kerja')}
|
||||||
|
>
|
||||||
|
Rekapitulasi per unit kerja
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/rekap-per-jenis-belanja'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/rekap-per-jenis-belanja' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/rekap-per-jenis-belanja')}
|
||||||
|
>
|
||||||
|
Rekapitulasi per jenis belanja (BAS)
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/laporan/verif-per-bulan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/laporan/verif-per-bulan' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('laporan/verif-per-bulan')}
|
||||||
|
>
|
||||||
|
Hasil verifikasi per bulan (detail)
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link href={'/peraturan'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded ${
|
||||||
|
pathname.startsWith('/peraturan') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('peraturan')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-shield mr-1' />
|
||||||
|
Peraturan
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow cursor-pointer px-3 py-1 rounded relative dropdown-toggle ${
|
||||||
|
pathname.startsWith('/manajemen') ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('manajemen')}
|
||||||
|
>
|
||||||
|
<i className='pi pi-database mr-1' />
|
||||||
|
Manajemen
|
||||||
|
<div className='absolute rounded bg-biru2 w-max min-w-full 2xl:top-8 top-7 -left-0 py-3 z-10 flex flex-col gap-y-1 shadow dropdown-menu'>
|
||||||
|
<Link href={'/manajemen/user'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/manajemen/user' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('manajemen/user')}
|
||||||
|
>
|
||||||
|
User
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/manajemen/role'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/manajemen/role' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('manajemen/role')}
|
||||||
|
>
|
||||||
|
Role
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={'/manajemen/jam-kerja'}>
|
||||||
|
<div
|
||||||
|
className={`hover:text-hitam hover:bg-yellow px-3 py-1 ${
|
||||||
|
pathname === '/manajemen/jam-kerja' ? 'bg-yellow' : 'text-white'
|
||||||
|
}`}
|
||||||
|
hidden={!renderMenu('manajemen/jam-kerja')}
|
||||||
|
>
|
||||||
|
Jam Kerja
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
48
components/Navbar.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useAuth } from '@/hooks/auth'
|
||||||
|
import { Avatar } from 'primereact/avatar'
|
||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
import { MdExpandMore } from 'react-icons/md'
|
||||||
|
|
||||||
|
function NavbarMemo() {
|
||||||
|
const { logout } = useAuth({})
|
||||||
|
const [user, setUser] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUser(JSON.parse(localStorage.getItem('user')))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='navbar 2xl:px-24 px-10 flex justify-end items-center border-cream h-[150px]'>
|
||||||
|
<div className='logout z-10 py-3 px-5'>
|
||||||
|
<div className='flex items-center gap-x-3'>
|
||||||
|
<Avatar image={'/img/M.png'} shape='circle' />
|
||||||
|
<div className='text-white'>
|
||||||
|
<p className='font-semibold capitalize'>{user && user.nama_lengkap}</p>
|
||||||
|
<p className='text-xs text-hijau/70'>{user && user.role}</p>
|
||||||
|
</div>
|
||||||
|
<MdExpandMore className='text-xl' />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='absolute bg-white top-24 2xl:right-32 right-20 w-56 p-3 rounded flex flex-col shadow-lg gap-y-3 logout-pop'
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-x-3 shadow-md p-2'>
|
||||||
|
<Avatar image={'/img/M.png'} shape='circle' className='shadow' />
|
||||||
|
<p className='font-semibold capitalize'>{user && user.nama_lengkap}</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-x-3 hover:bg-abu2 cursor-pointer p-2 rounded'>
|
||||||
|
<i className='pi pi-sign-out rounded-full p-2' />
|
||||||
|
<p className='text-sm'>Logout</p>
|
||||||
|
</div>
|
||||||
|
<div className='bg-overlay'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Navbar = memo(NavbarMemo)
|
||||||
15
components/TextCustom.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const Judul = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<p className='judul-content 2xl:text-2xl text-xl border-l-4 border-l-pink pl-2 mb-3 font-bebas tracking-wide'>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubJudul = ({ children, className = '' }) => {
|
||||||
|
return <p className={`judul-content font-semibold ${className}`}>{children}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LabelInput = ({ children }) => {
|
||||||
|
return <label className='font-semibold'>{children}</label>
|
||||||
|
}
|
||||||
7
constant/globalData.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const ddRekomendasi = [
|
||||||
|
{ name: 'Ditolak/dikembalikan' },
|
||||||
|
{ name: 'Disetujui dengan catatan' },
|
||||||
|
{ name: 'Disetujui tanpa catatan' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export { ddRekomendasi }
|
||||||
118
hooks/auth.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { LoginPost, LogoutGet } from '@/services/auth-service'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { destroyCookie, parseCookies, setCookie } from 'nookies'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { UPDATE_NAME } from 'store/userSlice'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
import axios from '../lib/axios'
|
||||||
|
|
||||||
|
export const useAuth = ({ middleware, redirectIfAuthenticated }) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { token } = parseCookies()
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (middleware === 'guest' && redirectIfAuthenticated && token) router.push(redirectIfAuthenticated)
|
||||||
|
if (middleware === 'auth' && !token) logout()
|
||||||
|
}, [token])
|
||||||
|
|
||||||
|
const login = async ({ toast, setLoading, ...props }) => {
|
||||||
|
LoginPost(props)
|
||||||
|
.then((res) => {
|
||||||
|
setLoading(false)
|
||||||
|
if (res.status === 'ok') {
|
||||||
|
setCookie(null, 'token', res.data.jwt, {
|
||||||
|
path: '/',
|
||||||
|
maxAge: res.data.token_expiry,
|
||||||
|
})
|
||||||
|
localStorage.setItem('menus', JSON.stringify(res.data.user_access))
|
||||||
|
localStorage.setItem('user', JSON.stringify(res.data))
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
grecaptcha.reset()
|
||||||
|
toast.current.show({ severity: 'error', summary: 'Error Message', detail: res.message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
grecaptcha.reset()
|
||||||
|
setLoading(false)
|
||||||
|
switch (err.response.status) {
|
||||||
|
case 401:
|
||||||
|
toast.current.show({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error Message',
|
||||||
|
detail: 'Your account used on another device.',
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
toast.current.show({ severity: 'error', summary: 'Error Message', detail: err })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const forgotPassword = async ({ ...props }) => {
|
||||||
|
axios
|
||||||
|
.post('/security/forgotPassword ', props)
|
||||||
|
.then((response) => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Forgot Password',
|
||||||
|
text: response.data.message,
|
||||||
|
width: '25rem',
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
}).then(() => {
|
||||||
|
router.push('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetPassword = async ({ ...props }) => {
|
||||||
|
axios
|
||||||
|
.post('/security/resetPassword ', props)
|
||||||
|
.then((response) => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Reset Password',
|
||||||
|
text: response.data.message,
|
||||||
|
width: '25rem',
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
}).then(() => {
|
||||||
|
router.push('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
LogoutGet()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 'ok') {
|
||||||
|
destroyCookie(null, 'token', { path: '/' })
|
||||||
|
localStorage.removeItem('menus')
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
dispatch(UPDATE_NAME(''))
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// user,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
forgotPassword,
|
||||||
|
resetPassword,
|
||||||
|
}
|
||||||
|
}
|
||||||
13
jsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/constant/*": ["constant/*"],
|
||||||
|
"@/components/*": ["components/*"],
|
||||||
|
"@/services/*": ["services/*"],
|
||||||
|
"@/context/*": ["context/*"],
|
||||||
|
"@/hooks/*": ["hooks/*"],
|
||||||
|
"@/public/*": ["public/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
lib/axios.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { parseCookies } from 'nookies'
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
baseURL: '/api/',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Access-Control-Allow-Headers':
|
||||||
|
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
|
||||||
|
'Access-Control-Allow-Methods': 'GET,DELETE,PATCH,POST,PUT',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create instance
|
||||||
|
const instance = axios.create(defaultOptions)
|
||||||
|
|
||||||
|
instance.defaults.withCredentials = true
|
||||||
|
|
||||||
|
// Set the AUTH token for any request
|
||||||
|
instance.interceptors.request.use(function (config) {
|
||||||
|
const { token } = parseCookies()
|
||||||
|
config.headers.Authorization = token ? `Bearer ${token}` : ''
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
export default instance
|
||||||
25
lib/axiosSakti.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
baseURL: '/sakti/api',
|
||||||
|
headers: {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// Accept: 'application/json',
|
||||||
|
Authorization:
|
||||||
|
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c3IiOiJCQURBTiBLRVBFR0FXQUlBTiBORUdBUkEiLCJ1aWQiOiJCS04iLCJyb2wiOiJ3ZWJzZXJ2aWNlIiwia2RzIjoiS0wwODgiLCJrZGIiOiJLTDA4OCIsImtkdCI6IjIwMjIiLCJpYXQiOjE2NDg1Mzk5ODAsIm5iZiI6MTY0ODUzOTM4MCwia2lkIjoiQktOIn0.9DUVDBDqqf7_tWUWyYCjmoLaIr1F953R_aAUmQy5tkc',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create instance
|
||||||
|
const instance = axios.create(defaultOptions)
|
||||||
|
|
||||||
|
// instance.defaults.withCredentials = true
|
||||||
|
|
||||||
|
// Set the AUTH token for any request
|
||||||
|
// instance.interceptors.request.use(function (config) {
|
||||||
|
// config.headers.Authorization =
|
||||||
|
// 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c3IiOiJCQURBTiBLRVBFR0FXQUlBTiBORUdBUkEiLCJ1aWQiOiJCS04iLCJyb2wiOiJ3ZWJzZXJ2aWNlIiwia2RzIjoiS0wwODgiLCJrZGIiOiJLTDA4OCIsImtkdCI6IjIwMjIiLCJpYXQiOjE2NDg1Mzk5ODAsIm5iZiI6MTY0ODUzOTM4MCwia2lkIjoiQktOIn0.9DUVDBDqqf7_tWUWyYCjmoLaIr1F953R_aAUmQy5tkc'
|
||||||
|
// return config
|
||||||
|
// })
|
||||||
|
|
||||||
|
export default instance
|
||||||
21
lib/fetch.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const API_URL = process.env.NEXT_PUBLIC_BACKEND_API
|
||||||
|
|
||||||
|
export async function fetchAPI(target, token) {
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
Authorization: 'Bearer ' + token,
|
||||||
|
},
|
||||||
|
redirect: 'follow',
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(API_URL + target, requestOptions)
|
||||||
|
|
||||||
|
const json = await res.json()
|
||||||
|
if (json.errors) {
|
||||||
|
console.error(json.errors)
|
||||||
|
throw new Error('Failed to fetch API')
|
||||||
|
}
|
||||||
|
return json.data
|
||||||
|
}
|
||||||
4
middleware.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default function middleware(req) {
|
||||||
|
// req.nextUrl.pathname.startsWith('/dashboard')
|
||||||
|
// console.log('aku middleware')
|
||||||
|
}
|
||||||
15
next.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
swcMinify: false,
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
destination: process.env.NEXT_PUBLIC_BACKEND_API + ':path*',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
41
package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "e-verif",
|
||||||
|
"version": "2.6.14",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start -p 3001",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^1.8.4",
|
||||||
|
"apexcharts": "^3.35.3",
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"formik": "^2.2.9",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"next": "12.2.2",
|
||||||
|
"nookies": "^2.5.2",
|
||||||
|
"primeicons": "^5.0.0",
|
||||||
|
"primereact": "^8.2.0",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-apexcharts": "^1.4.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-google-recaptcha": "^2.1.0",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-redux": "^8.0.2",
|
||||||
|
"react-tsparticles": "^2.1.3",
|
||||||
|
"redux": "^4.2.0",
|
||||||
|
"sweetalert2": "^11.4.33",
|
||||||
|
"tsparticles": "^2.1.3",
|
||||||
|
"yup": "^0.32.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.7",
|
||||||
|
"eslint": "8.19.0",
|
||||||
|
"eslint-config-next": "12.2.2",
|
||||||
|
"postcss": "^8.4.14",
|
||||||
|
"tailwindcss": "^3.1.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
pages/404.js
Normal 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
pages/_app.js
Normal 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
pages/_document.js
Normal 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
pages/dashboard/index.js
Normal 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=''> </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
pages/index.js
Normal 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
pages/laporan/approval/index.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
pages/laporan/pengendalian-waktu-penyelesaian/index.js
Normal 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 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
pages/laporan/per-user/index.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
164
pages/laporan/rekap-bulanan-per-user/index.js
Normal file
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
116
pages/laporan/rekap-per-jenis-belanja/index.js
Normal file
@ -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
pages/laporan/rekap-per-unit-kerja/index.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
118
pages/laporan/rekap-verif-per-bulan/index.js
Normal file
@ -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
pages/laporan/verif-per-bulan/index.js
Normal 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
pages/login/index.js
Normal 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
pages/manajemen/jam-kerja/index.js
Normal 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
pages/manajemen/role/index.js
Normal 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
pages/manajemen/user/index.js
Normal 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
pages/otorisasi/pengesahan/form.js
Normal 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
pages/otorisasi/pengesahan/index.js
Normal 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=''> </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
pages/otorisasi/persetujuan/form.js
Normal 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
pages/otorisasi/persetujuan/index.js
Normal 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=''> </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
pages/otorisasi/rekomendasi/form.js
Normal 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
pages/otorisasi/rekomendasi/index.js
Normal 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=''> </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
pages/otorisasi/spm/form.js
Normal 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
pages/otorisasi/spm/index.js
Normal 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=''> </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
pages/otorisasi/verifikasi/form.js
Normal 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
pages/otorisasi/verifikasi/index.js
Normal 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=''> </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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
359
pages/pengajuan-tagihan/ditolak/[ditolak].js
Normal file
@ -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
pages/pengajuan-tagihan/draft/[draft].js
Normal 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
710
pages/pengajuan-tagihan/force-close/[force].js
Normal file
@ -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
pages/pengajuan-tagihan/form.js
Normal 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
pages/pengajuan-tagihan/index.js
Normal 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=''> </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
pages/peraturan/index.js
Normal 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
pages/referensi/akun/index.js
Normal 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
pages/referensi/jenis-belanja/index.js
Normal 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
pages/referensi/jenis-kegiatan/index.js
Normal 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
pages/referensi/jenis-tagihan/index.js
Normal 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
pages/referensi/persyaratan/index.js
Normal 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
pages/referensi/unit-kerja/index.js
Normal 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
pages/referensi/verifikator/index.js
Normal 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} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
3616
pnpm-lock.yaml
generated
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
public/fonts/BebasNeue.ttf
Normal file
BIN
public/fonts/digital-7.ttf
Normal file
BIN
public/img/M.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/img/bg-login.jpg
Normal file
|
After Width: | Height: | Size: 990 KiB |
BIN
public/img/bg-navbar-1280.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
public/img/bg-navbar-1920.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
public/img/bg-navbar.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/img/coming-soon.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/img/e-verif-title.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/img/e-verif.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
public/img/logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/no_image.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
4
public/vercel.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
15
services/auth-service.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import axios from '../lib/axios'
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoginPost = async (data) => {
|
||||||
|
const response = await axios.post('login', data, headers)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LogoutGet = async () => {
|
||||||
|
const response = await axios.get('logout')
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
8
services/cek-tagihan-service.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import axios from '../lib/axios'
|
||||||
|
|
||||||
|
export const CekTagihan = async (params) => {
|
||||||
|
const response = await axios.get(`tagihan/tracking/${params}`, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
29
services/chart-service.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import axios from '../lib/axios'
|
||||||
|
|
||||||
|
export const Chart1 = async () => {
|
||||||
|
const response = await axios.get('chart/approval', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Chart2 = async () => {
|
||||||
|
const response = await axios.get('chart/kriteria-catatan', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChartJenisKegiatan = async () => {
|
||||||
|
const response = await axios.get('chart/jenis-kegiatan', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Chart4 = async () => {
|
||||||
|
const response = await axios.get('chart/jumlah-dokumen', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
17
services/dashboard-service.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import axios from '../lib/axios'
|
||||||
|
|
||||||
|
export const DashboardP2k = async (params) => {
|
||||||
|
const response = await axios.get(`dashboard/p2k-verif`, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DashboardAll = async (params) => {
|
||||||
|
const response = await axios.get('dashboard/all', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||