Initial commit, repo migration

This commit is contained in:
unknown
2024-05-14 22:19:14 +07:00
commit b51b940ac4
137 changed files with 19393 additions and 0 deletions
+59
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>
}