Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Sign in
Toggle navigation
C
ctas-box
Project overview
Project overview
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Container Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Fachri
ctas-box
Commits
04416926
Commit
04416926
authored
Oct 20, 2025
by
Fachri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bupot-nr new
parent
3b0dd65d
Changes
35
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
4579 additions
and
859 deletions
+4579
-859
src/pages/unifikasi/unifikasiNr.tsx
src/pages/unifikasi/unifikasiNr.tsx
+2
-1
src/sections/bupot-unifikasi/bupot-dn/view/dn-list-view.tsx
src/sections/bupot-unifikasi/bupot-dn/view/dn-list-view.tsx
+0
-76
src/sections/bupot-unifikasi/bupot-nr/components/CustomColumnsButton.tsx
...pot-unifikasi/bupot-nr/components/CustomColumnsButton.tsx
+32
-0
src/sections/bupot-unifikasi/bupot-nr/components/CustomFilterButton.tsx
...upot-unifikasi/bupot-nr/components/CustomFilterButton.tsx
+1015
-0
src/sections/bupot-unifikasi/bupot-nr/components/CustomToolbar.tsx
...ons/bupot-unifikasi/bupot-nr/components/CustomToolbar.tsx
+68
-0
src/sections/bupot-unifikasi/bupot-nr/components/StatusChip.tsx
...ctions/bupot-unifikasi/bupot-nr/components/StatusChip.tsx
+127
-0
src/sections/bupot-unifikasi/bupot-nr/components/dialog/CancelConfirmationDialog.tsx
...i/bupot-nr/components/dialog/CancelConfirmationDialog.tsx
+40
-0
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalCancelDn.tsx
...ot-unifikasi/bupot-nr/components/dialog/ModalCancelDn.tsx
+444
-0
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalCetakPdfDn.tsx
...-unifikasi/bupot-nr/components/dialog/ModalCetakPdfDn.tsx
+107
-0
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalDeleteDn.tsx
...ot-unifikasi/bupot-nr/components/dialog/ModalDeleteDn.tsx
+148
-0
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalUploadDn.tsx
...ot-unifikasi/bupot-nr/components/dialog/ModalUploadDn.tsx
+198
-0
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/DokumenReferensi.tsx
...nifikasi/bupot-nr/components/rekamNr/DokumenReferensi.tsx
+0
-53
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/Identitas.tsx
...bupot-unifikasi/bupot-nr/components/rekamNr/Identitas.tsx
+0
-125
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/PanduanNrRekam.tsx
...-unifikasi/bupot-nr/components/rekamNr/PanduanNrRekam.tsx
+0
-151
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/PphDipotong.tsx
...pot-unifikasi/bupot-nr/components/rekamNr/PphDipotong.tsx
+0
-173
src/sections/bupot-unifikasi/bupot-nr/components/toolbarCancel.tsx
...ons/bupot-unifikasi/bupot-nr/components/toolbarCancel.tsx
+40
-0
src/sections/bupot-unifikasi/bupot-nr/constant/index.tsx
src/sections/bupot-unifikasi/bupot-nr/constant/index.tsx
+668
-24
src/sections/bupot-unifikasi/bupot-nr/constant/queryKey.tsx
src/sections/bupot-unifikasi/bupot-nr/constant/queryKey.tsx
+16
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useAdvancedFilterDn.tsx
...ns/bupot-unifikasi/bupot-nr/hooks/useAdvancedFilterDn.tsx
+362
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useCancelDn.tsx
src/sections/bupot-unifikasi/bupot-nr/hooks/useCancelDn.tsx
+12
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useCetakPdfDn.tsx
...sections/bupot-unifikasi/bupot-nr/hooks/useCetakPdfDn.tsx
+11
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useDeleteDn.tsx
src/sections/bupot-unifikasi/bupot-nr/hooks/useDeleteDn.tsx
+12
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useGetDn.tsx
src/sections/bupot-unifikasi/bupot-nr/hooks/useGetDn.tsx
+278
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useGetKodeObjekPajak.tsx
...s/bupot-unifikasi/bupot-nr/hooks/useGetKodeObjekPajak.tsx
+12
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/usePphDipotong.tsx
...ections/bupot-unifikasi/bupot-nr/hooks/usePphDipotong.tsx
+77
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useSaveDn.tsx
src/sections/bupot-unifikasi/bupot-nr/hooks/useSaveDn.tsx
+102
-0
src/sections/bupot-unifikasi/bupot-nr/hooks/useUpload.tsx
src/sections/bupot-unifikasi/bupot-nr/hooks/useUpload.tsx
+12
-0
src/sections/bupot-unifikasi/bupot-nr/store/paginationStore.ts
...ections/bupot-unifikasi/bupot-nr/store/paginationStore.ts
+74
-0
src/sections/bupot-unifikasi/bupot-nr/utils/api.tsx
src/sections/bupot-unifikasi/bupot-nr/utils/api.tsx
+156
-0
src/sections/bupot-unifikasi/bupot-nr/utils/normalizePayloadCetakPdf.ts
...upot-unifikasi/bupot-nr/utils/normalizePayloadCetakPdf.ts
+61
-0
src/sections/bupot-unifikasi/bupot-nr/utils/unifikasiClient.tsx
...ctions/bupot-unifikasi/bupot-nr/utils/unifikasiClient.tsx
+28
-0
src/sections/bupot-unifikasi/bupot-nr/utils/utils.tsx
src/sections/bupot-unifikasi/bupot-nr/utils/utils.tsx
+19
-0
src/sections/bupot-unifikasi/bupot-nr/view/index.tsx
src/sections/bupot-unifikasi/bupot-nr/view/index.tsx
+0
-0
src/sections/bupot-unifikasi/bupot-nr/view/nr-list-view.tsx
src/sections/bupot-unifikasi/bupot-nr/view/nr-list-view.tsx
+458
-109
src/sections/bupot-unifikasi/bupot-nr/view/nrRekamView.tsx
src/sections/bupot-unifikasi/bupot-nr/view/nrRekamView.tsx
+0
-147
No files found.
src/pages/unifikasi/unifikasiNr.tsx
View file @
04416926
import
{
CONFIG
}
from
'
src/global-config
'
;
import
{
NrListView
}
from
'
src/sections/bupot-unifikasi/bupot-nr/view
'
;
import
{
NrListView
}
from
'
src/sections/bupot-unifikasi/bupot-nr/view/nr-list-view
'
;
// import { NrListView } from 'src/sections/bupot-unifikasi/bupot-nr/view';
const
metadata
=
{
title
:
`E-Bupot Unifikasi-
${
CONFIG
.
appName
}
`
};
...
...
src/sections/bupot-unifikasi/bupot-dn/view/dn-list-view.tsx
View file @
04416926
...
...
@@ -108,47 +108,6 @@ export function DnListView() {
const
{
buildAdvancedFilter
,
buildRequestParams
}
=
useAdvancedFilter
();
// const buildAdvancedFilter = useCallback((filters?: GridFilterModel['items']) => {
// if (!filters || filters.length === 0) return '';
// return filters
// .map((f) => {
// if (!f.value || !f.field) return null;
// const field = `LOWER("${f.field}")`;
// const val = String(f.value).toLowerCase();
// switch (f.operator) {
// case 'contains':
// return `${field} LIKE '%${val}%'`;
// case 'equals':
// case 'is':
// return `${field} = '${val}'`;
// case 'isNot':
// return `${field} <> '${val}'`;
// default:
// return null;
// }
// })
// .filter(Boolean)
// .join(' AND ');
// }, []);
// ---------- params memo (uses pagination from Zustand) ----------
// const params = useMemo(() => {
// const advanced = buildAdvancedFilter(filterModel.items);
// return {
// page,
// limit: pageSize,
// noBupot: '',
// idDipotong: '',
// namaDipotong: '',
// msPajak: '',
// thnPajak: '',
// advanced,
// sortingMode: sortModel[0]?.field ?? '',
// sortingMethod: sortModel[0]?.sort ?? '',
// };
// }, [page, pageSize, sortModel, filterModel.items, buildAdvancedFilter]);
const
params
=
useMemo
(()
=>
{
const
advanced
=
buildAdvancedFilter
(
filterModel
.
items
);
...
...
@@ -230,10 +189,6 @@ export function DnListView() {
width
:
200
,
type
:
'
singleSelect
'
,
valueOptions
:
statusOptions
.
map
((
opt
)
=>
opt
.
value
),
// valueFormatter: (params: any) => {
// const option = statusOptions.find((opt) => opt.value === params.value);
// return option ? option.label : (params.value as string);
// },
valueFormatter
:
({
value
}:
{
value
:
string
})
=>
{
const
option
=
statusOptions
.
find
((
opt
)
=>
opt
.
value
===
value
);
return
option
?
option
.
label
:
value
;
...
...
@@ -275,37 +230,6 @@ export function DnListView() {
[
statusOptions
]
);
// --- selection helpers (kept same)
const
normalizeSelectionToArray
=
(
raw
:
unknown
):
GridRowId
[]
=>
{
if
(
!
raw
)
return
[];
if
(
typeof
raw
===
'
object
'
&&
raw
!==
null
&&
'
ids
'
in
(
raw
as
any
))
{
const
ids
=
(
raw
as
any
).
ids
;
if
(
ids
instanceof
Set
)
return
Array
.
from
(
ids
)
as
GridRowId
[];
if
(
Array
.
isArray
(
ids
))
return
ids
as
GridRowId
[];
if
(
ids
instanceof
Map
)
return
Array
.
from
((
ids
as
Map
<
any
,
any
>
).
keys
())
as
GridRowId
[];
if
(
typeof
ids
===
'
object
'
&&
ids
!==
null
)
return
Object
.
keys
(
ids
)
as
GridRowId
[];
}
if
(
Array
.
isArray
(
raw
))
return
raw
as
GridRowId
[];
if
(
raw
instanceof
Set
)
return
Array
.
from
(
raw
)
as
GridRowId
[];
if
(
raw
instanceof
Map
)
return
Array
.
from
((
raw
as
Map
<
any
,
any
>
).
keys
())
as
GridRowId
[];
if
(
typeof
raw
===
'
object
'
&&
raw
!==
null
)
{
const
obj
=
raw
as
Record
<
string
,
any
>
;
const
keys
=
Object
.
keys
(
obj
).
filter
((
k
)
=>
!!
obj
[
k
]);
if
(
keys
.
length
)
return
keys
as
GridRowId
[];
}
try
{
if
((
raw
as
any
)[
Symbol
.
iterator
])
{
return
Array
.
from
(
raw
as
Iterable
<
unknown
>
)
as
GridRowId
[];
}
}
catch
{
/* ignore */
}
return
[];
};
const
getSelectedRowByKey
=
(
key
?:
GridRowId
|
'
all
'
)
=>
{
const
api
=
apiRef
.
current
;
if
(
!
api
)
return
null
;
...
...
src/sections/bupot-unifikasi/bupot-nr/components/CustomColumnsButton.tsx
0 → 100644
View file @
04416926
import
React
from
'
react
'
;
import
{
GridPreferencePanelsValue
,
useGridApiContext
}
from
'
@mui/x-data-grid-premium
'
;
import
{
IconButton
,
Tooltip
}
from
'
@mui/material
'
;
import
ViewColumnIcon
from
'
@mui/icons-material/ViewColumn
'
;
// ✅ React.memo: cegah render ulang tanpa alasan
const
CustomColumnsButton
:
React
.
FC
=
React
.
memo
(()
=>
{
const
apiRef
=
useGridApiContext
();
// ✅ useCallback biar referensi handleClick stabil di setiap render
const
handleClick
=
React
.
useCallback
(()
=>
{
if
(
!
apiRef
.
current
)
return
;
apiRef
.
current
.
showPreferences
(
'
columns
'
as
GridPreferencePanelsValue
);
},
[
apiRef
]);
return
(
<
Tooltip
title=
"Kolom"
>
<
IconButton
size=
"small"
onClick=
{
handleClick
}
sx=
{
{
color
:
'
#123375
'
,
'
&:hover
'
:
{
backgroundColor
:
'
rgba(18, 51, 117, 0.08)
'
},
}
}
>
<
ViewColumnIcon
fontSize=
"small"
/>
</
IconButton
>
</
Tooltip
>
);
});
export
default
CustomColumnsButton
;
src/sections/bupot-unifikasi/bupot-nr/components/CustomFilterButton.tsx
0 → 100644
View file @
04416926
This diff is collapsed.
Click to expand it.
src/sections/bupot-unifikasi/bupot-nr/components/CustomToolbar.tsx
0 → 100644
View file @
04416926
import
*
as
React
from
'
react
'
;
import
{
GridToolbarContainer
,
GridToolbarProps
}
from
'
@mui/x-data-grid-premium
'
;
import
{
Stack
,
Divider
,
IconButton
,
Tooltip
}
from
'
@mui/material
'
;
import
{
ActionItem
}
from
'
../types/types
'
;
import
{
CustomFilterButton
}
from
'
./CustomFilterButton
'
;
import
CustomColumnsButton
from
'
./CustomColumnsButton
'
;
interface
CustomToolbarProps
extends
GridToolbarProps
{
actions
?:
ActionItem
[][];
columns
:
any
[];
// GridColDef[]
filterModel
:
any
;
setFilterModel
:
(
m
:
any
)
=>
void
;
statusOptions
?:
{
value
:
string
;
label
:
string
}[];
}
// ✅ React.memo mencegah render ulang kalau props sama
export
const
CustomToolbar
=
React
.
memo
(
function
CustomToolbar
({
actions
=
[],
columns
,
filterModel
,
setFilterModel
,
statusOptions
=
[],
...
gridToolbarProps
}:
CustomToolbarProps
)
{
return
(
<
GridToolbarContainer
sx=
{
{
display
:
'
flex
'
,
justifyContent
:
'
space-between
'
,
alignItems
:
'
center
'
,
p
:
1.5
,
}
}
{
...
gridToolbarProps
}
>
<
Stack
direction=
"row"
alignItems=
"center"
gap=
{
1
}
>
{
actions
.
map
((
group
,
groupIdx
)
=>
(
<
Stack
key=
{
groupIdx
}
direction=
"row"
gap=
{
0.5
}
alignItems=
"center"
>
{
group
.
map
((
action
,
idx
)
=>
(
<
Tooltip
key=
{
idx
}
title=
{
action
.
title
}
>
<
span
>
<
IconButton
sx=
{
{
color
:
action
.
disabled
?
'
action.disabled
'
:
'
#123375
'
}
}
size=
"small"
onClick=
{
action
.
func
}
disabled=
{
action
.
disabled
}
>
{
action
.
icon
}
</
IconButton
>
</
span
>
</
Tooltip
>
))
}
{
groupIdx
<
actions
.
length
-
1
&&
<
Divider
orientation=
"vertical"
flexItem
/>
}
</
Stack
>
))
}
</
Stack
>
<
Stack
direction=
"row"
alignItems=
"center"
gap=
{
0.5
}
>
<
CustomColumnsButton
/>
<
CustomFilterButton
columns=
{
columns
}
filterModel=
{
filterModel
}
setFilterModel=
{
setFilterModel
}
statusOptions=
{
statusOptions
}
/>
</
Stack
>
</
GridToolbarContainer
>
);
});
src/sections/bupot-unifikasi/bupot-nr/components/StatusChip.tsx
0 → 100644
View file @
04416926
import
React
from
'
react
'
;
import
Chip
from
'
@mui/material/Chip
'
;
import
Box
from
'
@mui/material/Box
'
;
type
Props
=
{
value
?:
string
;
revNo
?:
number
};
const
StatusChip
:
React
.
FC
<
Props
>
=
({
value
,
revNo
})
=>
{
if
(
!
value
)
return
<
Chip
label=
""
size=
"small"
/>;
if
(
value
===
'
NORMAL-Done
'
&&
revNo
!==
0
)
{
return
(
<
Box
sx=
{
{
position
:
'
relative
'
,
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
}
}
>
<
Chip
label=
"Normal Pengganti"
size=
"small"
variant=
"outlined"
sx=
{
{
borderColor
:
'
#1976d2
'
,
color
:
'
#1976d2
'
,
borderRadius
:
'
8px
'
,
fontWeight
:
500
,
paddingRight
:
'
5px
'
,
}
}
/>
<
Chip
label=
{
revNo
}
size=
"small"
variant=
"filled"
sx=
{
{
position
:
'
absolute
'
,
top
:
-
6
,
right
:
-
6
,
backgroundColor
:
'
#1976d2
'
,
color
:
'
#fff
'
,
borderRadius
:
'
50%
'
,
fontWeight
:
500
,
width
:
18
,
height
:
18
,
minWidth
:
0
,
border
:
'
2px solid #fff
'
,
boxShadow
:
'
0 1px 3px rgba(0, 0, 0, 0.25)
'
,
'
& .MuiChip-label
'
:
{
padding
:
0
,
fontSize
:
'
0.65rem
'
,
lineHeight
:
1
,
},
}
}
/>
</
Box
>
);
}
if
(
value
===
'
NORMAL-Done
'
&&
revNo
===
0
)
{
return
(
<
Chip
label=
"Normal"
size=
"small"
variant=
"outlined"
sx=
{
{
borderColor
:
'
#1976d2
'
,
color
:
'
#1976d2
'
,
borderRadius
:
'
8px
'
,
fontWeight
:
'
500
'
,
}
}
/>
);
}
if
(
value
===
'
AMENDED
'
)
{
return
(
<
Chip
label=
"Diganti"
size=
"small"
variant=
"outlined"
sx=
{
{
color
:
'
#fff
'
,
backgroundColor
:
'
#f38c28
'
,
borderRadius
:
'
8px
'
,
fontWeight
:
500
,
border
:
'
none
'
,
boxShadow
:
'
0 1px 2px rgba(0, 0, 0, 0.15)
'
,
}
}
/>
);
}
if
(
value
===
'
CANCELLED
'
)
{
return
(
<
Chip
label=
"Dibatalkan"
size=
"small"
variant=
"outlined"
sx=
{
{
borderColor
:
'
#d32f2f
'
,
color
:
'
#d32f2f
'
,
borderRadius
:
'
8px
'
,
fontWeight
:
'
500
'
,
}
}
/>
);
}
if
(
value
===
'
DRAFT
'
)
{
return
(
<
Chip
label=
"Draft"
size=
"small"
variant=
"outlined"
sx=
{
{
borderColor
:
'
#9e9e9e
'
,
color
:
'
#616161
'
,
borderRadius
:
'
8px
'
,
}
}
/>
);
}
return
<
Chip
label=
{
value
}
size=
"small"
/>;
};
export
default
React
.
memo
(
StatusChip
);
src/sections/bupot-unifikasi/bupot-nr/components/dialog/CancelConfirmationDialog.tsx
0 → 100644
View file @
04416926
import
React
from
'
react
'
;
import
{
Dialog
,
DialogTitle
,
DialogContent
,
DialogActions
,
Button
,
Typography
,
}
from
'
@mui/material
'
;
interface
CancelConfirmationDialogProps
{
open
:
boolean
;
onClose
:
()
=>
void
;
onConfirm
:
()
=>
void
;
selectedCount
:
number
;
}
const
CancelConfirmationDialog
:
React
.
FC
<
CancelConfirmationDialogProps
>
=
({
open
,
onClose
,
onConfirm
,
selectedCount
,
})
=>
(
<
Dialog
open=
{
open
}
onClose=
{
onClose
}
>
<
DialogTitle
>
Konfirmasi Pembatalan
</
DialogTitle
>
<
DialogContent
>
<
Typography
>
Apakah Anda yakin ingin membatalkan
{
selectedCount
}
data yang dipilih?
</
Typography
>
</
DialogContent
>
<
DialogActions
>
<
Button
onClick=
{
onClose
}
>
Batal
</
Button
>
<
Button
onClick=
{
onConfirm
}
color=
"error"
variant=
"contained"
>
Ya, Batalkan
</
Button
>
</
DialogActions
>
</
Dialog
>
);
export
default
CancelConfirmationDialog
;
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalCancelDn.tsx
0 → 100644
View file @
04416926
This diff is collapsed.
Click to expand it.
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalCetakPdfDn.tsx
0 → 100644
View file @
04416926
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
enqueueSnackbar
}
from
'
notistack
'
;
import
DialogUmum
from
'
src/shared/components/dialog/DialogUmum
'
;
import
DialogContent
from
'
@mui/material/DialogContent
'
;
import
CircularProgress
from
'
@mui/material/CircularProgress
'
;
import
Box
from
'
@mui/material/Box
'
;
import
useCetakPdfDn
from
'
../../hooks/useCetakPdfDn
'
;
import
normalizePayloadCetakPdf
from
'
../../utils/normalizePayloadCetakPdf
'
;
interface
ModalCetakPdfDnProps
{
payload
?:
Record
<
string
,
any
>
;
isOpen
:
boolean
;
onClose
:
()
=>
void
;
}
const
formatTanggalIndo
=
(
isoDate
:
string
|
undefined
|
null
):
string
=>
{
if
(
!
isoDate
)
return
''
;
const
date
=
new
Date
(
isoDate
);
const
formatter
=
new
Intl
.
DateTimeFormat
(
'
id-ID
'
,
{
day
:
'
2-digit
'
,
month
:
'
long
'
,
year
:
'
numeric
'
,
});
return
formatter
.
format
(
date
);
};
const
ModalCetakPdfDn
:
React
.
FC
<
ModalCetakPdfDnProps
>
=
({
payload
,
isOpen
,
onClose
})
=>
{
const
[
pdfUrl
,
setPdfUrl
]
=
useState
<
string
|
null
>
(
null
);
const
[
loading
,
setLoading
]
=
useState
<
boolean
>
(
false
);
const
{
mutateAsync
}
=
useCetakPdfDn
({
onError
:
(
error
:
any
)
=>
{
enqueueSnackbar
(
error
?.
message
||
'
Gagal memuat PDF
'
,
{
variant
:
'
error
'
});
setLoading
(
false
);
},
onSuccess
:
(
res
:
any
)
=>
{
const
fileUrl
=
res
?.
url
||
res
?.
data
?.
url
;
if
(
!
fileUrl
)
{
enqueueSnackbar
(
'
URL PDF tidak ditemukan di respons API
'
,
{
variant
:
'
warning
'
});
setLoading
(
false
);
return
;
}
setPdfUrl
(
fileUrl
);
setLoading
(
false
);
enqueueSnackbar
(
res
?.
MsgStatus
||
'
PDF berhasil dibentuk
'
,
{
variant
:
'
success
'
});
},
});
useEffect
(()
=>
{
const
runCetak
=
async
()
=>
{
if
(
!
isOpen
||
!
payload
)
return
;
setLoading
(
true
);
setPdfUrl
(
null
);
try
{
// Payload sudah lengkap dari parent (sudah ada namaObjekPajak, pasalPPh, statusPPh)
const
normalized
=
normalizePayloadCetakPdf
(
payload
);
console
.
log
(
'
📤 Payload final cetak PDF:
'
,
normalized
);
await
mutateAsync
(
normalized
);
}
catch
(
err
)
{
console
.
error
(
'
❌ Error cetak PDF:
'
,
err
);
enqueueSnackbar
(
'
Gagal generate PDF
'
,
{
variant
:
'
error
'
});
setLoading
(
false
);
}
};
runCetak
();
},
[
isOpen
,
payload
,
mutateAsync
]);
return
(
<
DialogUmum
maxWidth=
"lg"
isOpen=
{
isOpen
}
onClose=
{
onClose
}
title=
"Detail Bupot Unifikasi (PDF)"
>
<
DialogContent
classes=
{
{
root
:
'
p-16 sm:p-24
'
}
}
>
{
loading
&&
(
<
Box
display=
"flex"
justifyContent=
"center"
alignItems=
"center"
height=
"60vh"
>
<
CircularProgress
/>
</
Box
>
)
}
{
!
loading
&&
pdfUrl
&&
(
<
iframe
src=
{
pdfUrl
}
style=
{
{
width
:
'
100%
'
,
height
:
'
80vh
'
,
border
:
'
none
'
,
borderRadius
:
8
,
}
}
title=
"Preview PDF Bupot"
/>
)
}
{
!
loading
&&
!
pdfUrl
&&
(
<
Box
textAlign=
"center"
color=
"text.secondary"
py=
{
4
}
>
PDF tidak tersedia untuk ditampilkan.
</
Box
>
)
}
</
DialogContent
>
</
DialogUmum
>
);
};
export
default
ModalCetakPdfDn
;
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalDeleteDn.tsx
0 → 100644
View file @
04416926
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
useMutation
,
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
{
enqueueSnackbar
}
from
'
notistack
'
;
import
DialogProgressBar
from
'
src/shared/components/dialog/DialogProgressBar
'
;
import
useDialogProgressBar
from
'
src/shared/hooks/useDialogProgressBar
'
;
import
dnApi
from
'
../../utils/api
'
;
import
queryKey
from
'
../../constant/queryKey
'
;
import
DialogConfirm
from
'
src/shared/components/dialog/DialogConfirm
'
;
import
{
GridRowSelectionModel
}
from
'
@mui/x-data-grid-premium
'
;
import
useDeleteDn
from
'
../../hooks/useDeleteDn
'
;
interface
ModalDeleteDnProps
{
dataSelected
?:
GridRowSelectionModel
;
setSelectionModel
?:
React
.
Dispatch
<
React
.
SetStateAction
<
GridRowSelectionModel
|
undefined
>>
;
tableApiRef
?:
React
.
MutableRefObject
<
any
>
;
isOpenDialogDelete
:
boolean
;
setIsOpenDialogDelete
:
(
v
:
boolean
)
=>
void
;
successMessage
?:
string
;
}
/**
* Normalize various possible shapes of grid selection model into array of ids.
* Handles:
* - array of ids: [1, 2, 'a']
* - Set-of-ids: new Set([1,2])
* - object like { ids: Set(...), type: 'include' }
* - fallback: tries to extract keys if it's an object map
*/
const
normalizeSelection
=
(
sel
?:
any
):
(
string
|
number
)[]
=>
{
if
(
!
sel
)
return
[];
if
(
Array
.
isArray
(
sel
))
return
sel
as
(
string
|
number
)[];
if
(
sel
instanceof
Set
)
return
Array
.
from
(
sel
)
as
(
string
|
number
)[];
if
(
typeof
sel
===
'
object
'
)
{
// common shape from newer MUI: { ids: Set(...), type: 'include' }
if
(
sel
.
ids
instanceof
Set
)
return
Array
.
from
(
sel
.
ids
)
as
(
string
|
number
)[];
// maybe it's a map-like object { '1': true, '2': true }
const
maybeIds
=
Object
.
keys
(
sel
).
filter
((
k
)
=>
k
!==
'
type
'
&&
k
!==
'
size
'
);
if
(
maybeIds
.
length
>
0
)
{
// try to convert numeric-like keys to number where applicable
return
maybeIds
.
map
((
k
)
=>
{
const
n
=
Number
(
k
);
return
Number
.
isNaN
(
n
)
?
k
:
n
;
});
}
}
return
[];
};
const
ModalDeleteDn
:
React
.
FC
<
ModalDeleteDnProps
>
=
({
dataSelected
,
setSelectionModel
,
tableApiRef
,
isOpenDialogDelete
,
setIsOpenDialogDelete
,
successMessage
=
'
Data berhasil dihapus
'
,
})
=>
{
const
queryClient
=
useQueryClient
();
// custom hooks for progress state
const
{
numberOfData
,
setNumberOfData
,
numberOfDataFail
,
numberOfDataProcessed
,
numberOfDataSuccess
,
processSuccess
,
processFail
,
resetToDefault
,
status
,
}
=
useDialogProgressBar
();
const
[
isOpenDialogProgressBar
,
setIsOpenDialogProgressBar
]
=
useState
(
false
);
// React Query mutation for delete
const
{
mutateAsync
}
=
useDeleteDn
({
onSuccess
:
()
=>
processSuccess
(),
onError
:
()
=>
processFail
(),
});
// fungsi multiple delete -- gunakan normalized array of ids
const
handleMultipleDelete
=
async
()
=>
{
const
ids
=
normalizeSelection
(
dataSelected
);
return
Promise
.
allSettled
(
ids
.
map
(
async
(
id
)
=>
mutateAsync
({
id
:
String
(
id
)
})));
};
const
clearSelection
=
()
=>
{
// clear grid selection via apiRef if available
tableApiRef
?.
current
?.
setRowSelectionModel
?.([]);
// also clear local state if setter provided
// set to undefined to follow the native hook type (or to empty array cast)
setSelectionModel
?.(
undefined
);
};
const
handleCloseModal
=
()
=>
{
setIsOpenDialogDelete
(
false
);
resetToDefault
();
};
const
onSubmit
=
async
()
=>
{
try
{
setIsOpenDialogProgressBar
(
true
);
await
handleMultipleDelete
();
enqueueSnackbar
(
successMessage
,
{
variant
:
'
success
'
});
handleCloseModal
();
clearSelection
();
}
catch
(
error
:
any
)
{
enqueueSnackbar
(
error
?.
message
||
'
Gagal menghapus data
'
,
{
variant
:
'
error
'
});
}
finally
{
// sesuaikan queryKey jika perlu; tetap panggil invalidasi
queryClient
.
invalidateQueries
({
queryKey
:
[
'
unifikasi
'
,
'
dn
'
]
});
}
};
useEffect
(()
=>
{
setNumberOfData
(
normalizeSelection
(
dataSelected
).
length
);
},
[
isOpenDialogDelete
,
dataSelected
,
setNumberOfData
]);
return
(
<>
<
DialogConfirm
fullWidth
maxWidth=
"xs"
title=
"Apakah Anda yakin akan menghapus data ini?"
description=
"Data yang sudah dihapus tidak dapat dikembalikan."
actionTitle=
"Hapus"
isOpen=
{
isOpenDialogDelete
}
isLoadingBtnSubmit=
{
false
}
handleClose=
{
handleCloseModal
}
handleSubmit=
{
onSubmit
}
/>
<
DialogProgressBar
isOpen=
{
isOpenDialogProgressBar
}
handleClose=
{
()
=>
{
handleCloseModal
();
setIsOpenDialogProgressBar
(
false
);
}
}
numberOfData=
{
numberOfData
}
numberOfDataProcessed=
{
numberOfDataProcessed
}
numberOfDataFail=
{
numberOfDataFail
}
numberOfDataSuccess=
{
numberOfDataSuccess
}
status=
{
status
}
/>
</>
);
};
export
default
ModalDeleteDn
;
src/sections/bupot-unifikasi/bupot-nr/components/dialog/ModalUploadDn.tsx
0 → 100644
View file @
04416926
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
useQueryClient
}
from
'
@tanstack/react-query
'
;
import
{
enqueueSnackbar
}
from
'
notistack
'
;
import
DialogProgressBar
from
'
src/shared/components/dialog/DialogProgressBar
'
;
import
useDialogProgressBar
from
'
src/shared/hooks/useDialogProgressBar
'
;
import
{
GridRowSelectionModel
}
from
'
@mui/x-data-grid-premium
'
;
import
useUpload
from
'
../../hooks/useUpload
'
;
import
DialogUmum
from
'
src/shared/components/dialog/DialogUmum
'
;
import
Stack
from
'
@mui/material/Stack
'
;
import
Grid
from
'
@mui/material/Grid
'
;
import
{
Field
}
from
'
src/components/hook-form
'
;
import
MenuItem
from
'
@mui/material/MenuItem
'
;
import
{
useSelector
}
from
'
react-redux
'
;
import
{
RootState
}
from
'
src/store
'
;
import
Agreement
from
'
src/shared/components/agreement/Agreement
'
;
import
{
FormProvider
,
useForm
}
from
'
react-hook-form
'
;
import
{
LoadingButton
}
from
'
@mui/lab
'
;
interface
ModalUploadDnProps
{
dataSelected
?:
GridRowSelectionModel
;
setSelectionModel
?:
React
.
Dispatch
<
React
.
SetStateAction
<
GridRowSelectionModel
|
undefined
>>
;
tableApiRef
?:
React
.
MutableRefObject
<
any
>
;
isOpenDialogUpload
:
boolean
;
setIsOpenDialogUpload
:
(
v
:
boolean
)
=>
void
;
successMessage
?:
string
;
// onConfirmUpload?: () => void;
onConfirmUpload
?:
()
=>
Promise
<
void
>
|
void
;
}
/**
* Normalize various possible shapes of grid selection model into array of ids.
* Handles:
* - array of ids: [1, 2, 'a']
* - Set-of-ids: new Set([1,2])
* - object like { ids: Set(...), type: 'include' }
* - fallback: tries to extract keys if it's an object map
*/
const
normalizeSelection
=
(
sel
?:
any
):
(
string
|
number
)[]
=>
{
if
(
!
sel
)
return
[];
if
(
Array
.
isArray
(
sel
))
return
sel
as
(
string
|
number
)[];
if
(
sel
instanceof
Set
)
return
Array
.
from
(
sel
)
as
(
string
|
number
)[];
if
(
typeof
sel
===
'
object
'
)
{
// common shape from newer MUI: { ids: Set(...), type: 'include' }
if
(
sel
.
ids
instanceof
Set
)
return
Array
.
from
(
sel
.
ids
)
as
(
string
|
number
)[];
// maybe it's a map-like object { '1': true, '2': true }
const
maybeIds
=
Object
.
keys
(
sel
).
filter
((
k
)
=>
k
!==
'
type
'
&&
k
!==
'
size
'
);
if
(
maybeIds
.
length
>
0
)
{
// try to convert numeric-like keys to number where applicable
return
maybeIds
.
map
((
k
)
=>
{
const
n
=
Number
(
k
);
return
Number
.
isNaN
(
n
)
?
k
:
n
;
});
}
}
return
[];
};
const
ModalUploadDn
:
React
.
FC
<
ModalUploadDnProps
>
=
({
dataSelected
,
setSelectionModel
,
tableApiRef
,
isOpenDialogUpload
,
setIsOpenDialogUpload
,
successMessage
=
'
Data berhasil diupload
'
,
onConfirmUpload
,
})
=>
{
const
queryClient
=
useQueryClient
();
const
uploadDn
=
useUpload
();
// custom hooks for progress state
const
{
numberOfData
,
setNumberOfData
,
numberOfDataFail
,
numberOfDataProcessed
,
numberOfDataSuccess
,
processSuccess
,
processFail
,
resetToDefault
,
status
,
}
=
useDialogProgressBar
();
const
[
isOpenDialogProgressBar
,
setIsOpenDialogProgressBar
]
=
useState
(
false
);
const
[
isCheckedAgreement
,
setIsCheckedAgreement
]
=
useState
<
boolean
>
(
false
);
const
signer
=
useSelector
((
state
:
RootState
)
=>
state
.
user
.
data
.
signer
);
const
{
mutateAsync
}
=
useUpload
({
onSuccess
:
()
=>
processSuccess
(),
onError
:
()
=>
processFail
(),
});
const
methods
=
useForm
({
defaultValues
:
{
signer
:
signer
||
''
,
},
});
// fungsi multiple delete -- gunakan normalized array of ids
const
handleMultipleDelete
=
async
()
=>
{
const
ids
=
normalizeSelection
(
dataSelected
);
return
Promise
.
allSettled
(
ids
.
map
(
async
(
id
)
=>
mutateAsync
({
id
:
String
(
id
)
})));
};
const
clearSelection
=
()
=>
{
// clear grid selection via apiRef if available
tableApiRef
?.
current
?.
setRowSelectionModel
?.([]);
// also clear local state if setter provided
// set to undefined to follow the native hook type (or to empty array cast)
setSelectionModel
?.(
undefined
);
};
const
handleCloseModal
=
()
=>
{
setIsOpenDialogUpload
(
false
);
resetToDefault
();
};
const
onSubmit
=
async
()
=>
{
try
{
setIsOpenDialogProgressBar
(
true
);
await
handleMultipleDelete
();
enqueueSnackbar
(
successMessage
,
{
variant
:
'
success
'
});
handleCloseModal
();
clearSelection
();
}
catch
(
error
:
any
)
{
enqueueSnackbar
(
error
?.
message
||
'
Gagal upload data
'
,
{
variant
:
'
error
'
});
}
finally
{
// sesuaikan queryKey jika perlu; tetap panggil invalidasi
queryClient
.
invalidateQueries
({
queryKey
:
[
'
unifikasi
'
,
'
dn
'
]
});
}
};
useEffect
(()
=>
{
setNumberOfData
(
normalizeSelection
(
dataSelected
).
length
);
},
[
isOpenDialogUpload
,
dataSelected
,
setNumberOfData
]);
return
(
<>
<
FormProvider
{
...
methods
}
>
<
DialogUmum
isOpen=
{
isOpenDialogUpload
}
onClose=
{
handleCloseModal
}
title=
"Upload Bukti Potong"
>
<
Stack
spacing=
{
2
}
sx=
{
{
mt
:
2
}
}
>
<
Grid
size=
{
{
md
:
12
}
}
>
<
Field
.
Select
name=
"signer"
label=
"NPWP/NIK Penandatangan"
>
<
MenuItem
value=
{
signer
}
>
{
signer
}
</
MenuItem
>
</
Field
.
Select
>
</
Grid
>
<
Grid
size=
{
12
}
>
<
Agreement
isCheckedAgreement=
{
isCheckedAgreement
}
setIsCheckedAgreement=
{
setIsCheckedAgreement
}
text=
"Dengan ini saya menyatakan bahwa Bukti Pemotongan/Pemungutan Unifikasi telah saya isi dengan benar secara elektronik sesuai dengan"
/>
</
Grid
>
<
Stack
direction=
"row"
justifyContent=
"flex-end"
spacing=
{
1
}
mt=
{
1
}
>
<
LoadingButton
type=
"button"
disabled=
{
!
isCheckedAgreement
}
// onClick={onSubmit}
onClick=
{
async
()
=>
{
if
(
onConfirmUpload
)
{
await
onConfirmUpload
();
setIsOpenDialogUpload
(
false
);
return
;
}
await
onSubmit
();
}
}
loading=
{
uploadDn
.
isPending
}
variant=
"contained"
sx=
{
{
background
:
'
#143B88
'
}
}
>
Save
</
LoadingButton
>
</
Stack
>
</
Stack
>
</
DialogUmum
>
</
FormProvider
>
<
DialogProgressBar
isOpen=
{
isOpenDialogProgressBar
}
handleClose=
{
()
=>
{
handleCloseModal
();
setIsOpenDialogProgressBar
(
false
);
}
}
numberOfData=
{
numberOfData
}
numberOfDataProcessed=
{
numberOfDataProcessed
}
numberOfDataFail=
{
numberOfDataFail
}
numberOfDataSuccess=
{
numberOfDataSuccess
}
status=
{
status
}
/>
</>
);
};
export
default
ModalUploadDn
;
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/DokumenReferensi.tsx
deleted
100644 → 0
View file @
3b0dd65d
import
Divider
from
'
@mui/material/Divider
'
;
import
Grid
from
'
@mui/material/Grid
'
;
import
MenuItem
from
'
@mui/material/MenuItem
'
;
import
{
Field
}
from
'
src/components/hook-form
'
;
import
{
JENIS_DOKUMEN
}
from
'
src/sections/bupot-unifikasi/bupot-dn/constant
'
;
const
DokumenReferensi
=
()
=>
{
const
MockNitku
=
[
{
nama
:
'
1091031210912281000000
'
,
},
{
nama
:
'
1091031210912281000001
'
,
},
];
return
(
<>
<
Grid
sx=
{
{
mb
:
3
}
}
container
rowSpacing=
{
2
}
columnSpacing=
{
2
}
>
<
Grid
sx=
{
{
mt
:
3
}
}
size=
{
{
md
:
12
}
}
>
<
Divider
sx=
{
{
fontWeight
:
'
bold
'
}
}
textAlign=
"left"
>
Daftar Dokumen
</
Divider
>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Select
name=
"namaDok"
label=
"Nama Dokumen"
>
{
JENIS_DOKUMEN
.
map
((
item
,
index
)
=>
(
<
MenuItem
key=
{
index
}
value=
{
item
.
value
}
>
{
item
.
label
}
</
MenuItem
>
))
}
</
Field
.
Select
>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"nomorDok"
label=
"Nomor Dokumen"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
DatePicker
name=
"tglDok"
label=
"Tanggal Dokumen"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Select
name=
"idTku"
label=
"NITKU Pemotong"
>
{
MockNitku
.
map
((
item
,
index
)
=>
(
<
MenuItem
key=
{
index
}
value=
{
item
.
nama
}
>
{
item
.
nama
}
</
MenuItem
>
))
}
</
Field
.
Select
>
</
Grid
>
</
Grid
>
</>
);
};
export
default
DokumenReferensi
;
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/Identitas.tsx
deleted
100644 → 0
View file @
3b0dd65d
// import Grid from '@mui/material/Grid';
import
Box
from
'
@mui/material/Box
'
;
import
Button
from
'
@mui/material/Button
'
;
import
Grid
from
'
@mui/material/Grid
'
;
import
MenuItem
from
'
@mui/material/MenuItem
'
;
import
{
useState
}
from
'
react
'
;
import
{
useFormContext
}
from
'
react-hook-form
'
;
import
{
useParams
}
from
'
react-router
'
;
import
{
Field
}
from
'
src/components/hook-form
'
;
type
IdentitasProps
=
{
isPengganti
:
boolean
;
// disabledTambah: boolean;
// disabledHapus: boolean;
};
const
Identitas
=
({
isPengganti
}:
IdentitasProps
)
=>
{
const
{
dnId
}
=
useParams
();
const
{
setValue
}
=
useFormContext
();
const
[
jumlahKeterangan
,
setJumlahKeterangan
]
=
useState
<
number
>
(
0
);
const
maxKeterangan
=
5
;
const
handleTambah
=
()
=>
{
if
(
jumlahKeterangan
<
maxKeterangan
)
{
setJumlahKeterangan
(
jumlahKeterangan
+
1
);
}
};
const
handleHapus
=
()
=>
{
if
(
jumlahKeterangan
>
0
)
{
const
newCount
=
jumlahKeterangan
-
1
;
setJumlahKeterangan
(
newCount
);
// reset value form field yang dihapus
setValue
(
`keterangan
${
newCount
+
1
}
`
,
null
);
}
};
return
(
<>
<
Grid
container
rowSpacing=
{
2
}
alignItems=
"center"
columnSpacing=
{
2
}
sx=
{
{
mb
:
4
}
}
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
DatePicker
name=
"tglPemotongan"
label=
"Tanggal Pemotongan"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
3
}
}
>
<
Field
.
DatePicker
name=
"thnPajak"
label=
"Tahun Pajak"
view=
"year"
format=
"YYYY"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
3
}
}
>
<
Field
.
DatePicker
name=
"msPajak"
label=
"Masa Pajak"
view=
"month"
format=
"MM"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"idDipotong"
label=
"Tax ID Number (TIN)"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"namaDipotong"
label=
"Nama"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
12
}
}
>
<
Field
.
Text
name=
"email"
label=
"Email (Optional)"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
12
}
}
>
<
Field
.
Text
name=
"almtDipotong"
label=
"Alamat"
multiline
rows=
{
2
}
/>
</
Grid
>
<
Grid
size=
{
{
md
:
12
}
}
>
<
Field
.
Select
name=
"kdNgrDipotong"
label=
"Negara"
>
<
MenuItem
>
Indonesia
</
MenuItem
>
</
Field
.
Select
>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"tempatLahirDipotong"
label=
"Tempat Lahir"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
DatePicker
name=
"tglLahirDipotong"
label=
"Tanggal Lahir"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"noPasporDipotong"
label=
"No. Paspor"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"noKitasDipotong"
label=
"No.KITAS/KITAP"
/>
</
Grid
>
</
Grid
>
<
Box
sx=
{
{
display
:
'
flex
'
,
gap
:
2
,
mb
:
3
}
}
>
<
Box
sx=
{
{
borderRadius
:
'
18px
'
,
border
:
jumlahKeterangan
>=
maxKeterangan
?
'
1px solid #eee
'
:
'
1px solid #2e7d3280
'
,
color
:
jumlahKeterangan
>=
maxKeterangan
?
'
#eee
'
:
'
#2e7d3280
'
,
p
:
'
0px 10px
'
,
}
}
>
<
Button
disabled=
{
jumlahKeterangan
>=
maxKeterangan
}
onClick=
{
handleTambah
}
>
Tambah Keterangan
</
Button
>
</
Box
>
<
Box
sx=
{
{
borderRadius
:
'
18px
'
,
border
:
jumlahKeterangan
===
0
?
'
1px solid #eee
'
:
'
1px solid #f44336
'
,
color
:
jumlahKeterangan
===
0
?
'
#eee
'
:
'
#f44336
'
,
p
:
'
0px 10px
'
,
}
}
>
<
Button
disabled=
{
jumlahKeterangan
===
0
}
onClick=
{
handleHapus
}
>
Hapus Keterangan
</
Button
>
</
Box
>
</
Box
>
<
Box
sx=
{
{
mb
:
3
}
}
>
{
Array
.
from
({
length
:
jumlahKeterangan
}).
map
((
_
,
i
)
=>
(
<
Grid
size=
{
{
md
:
12
}
}
key=
{
i
}
>
<
Field
.
Text
sx=
{
{
mb
:
2
}
}
name=
{
`keterangan${i + 1}`
}
label=
{
`Keterangan Tambahan ${i + 1}`
}
/>
</
Grid
>
))
}
</
Box
>
</>
);
};
export
default
Identitas
;
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/PanduanNrRekam.tsx
deleted
100644 → 0
View file @
3b0dd65d
import
{
FC
}
from
'
react
'
;
import
{
Box
,
Button
,
Card
,
CardContent
,
CardHeader
,
IconButton
,
Typography
}
from
'
@mui/material
'
;
import
{
ChevronRightRounded
,
CloseRounded
}
from
'
@mui/icons-material
'
;
import
{
m
}
from
'
framer-motion
'
;
import
{
PANDUAN_REKAM_NR
}
from
'
../../constant
'
;
interface
PanduanDnRekamProps
{
handleOpen
:
()
=>
void
;
isOpen
:
boolean
;
}
const
PanduanNrRekam
:
FC
<
PanduanDnRekamProps
>
=
({
handleOpen
,
isOpen
})
=>
{
return
(
<
Box
position=
"sticky"
>
{
/* Tombol toggle */
}
<
Box
height=
"100%"
display=
{
isOpen
?
'
none
'
:
'
flex
'
}
justifyContent=
"center"
alignItems=
"center"
>
<
Button
variant=
"contained"
sx=
{
{
height
:
'
fit-content
'
,
right
:
0
,
borderRadius
:
0
,
minWidth
:
35
,
pt
:
3
,
pb
:
3
,
fontWeight
:
'
bold
'
,
fontSize
:
16
,
backgroundColor
:
'
#143B88
'
,
}
}
size=
"small"
onClick=
{
handleOpen
}
>
<
span
style=
{
{
writingMode
:
'
vertical-rl
'
,
transform
:
'
rotate(180deg)
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
}
}
>
Panduan Penggunaan
<
ChevronRightRounded
sx=
{
{
fontSize
:
30
}
}
/>
</
span
>
</
Button
>
</
Box
>
{
/* Konten panduan */
}
{
isOpen
&&
(
<
m
.
div
initial=
{
{
x
:
20
,
opacity
:
0
}
}
animate=
{
{
x
:
0
,
opacity
:
1
,
transition
:
{
delay
:
0.2
}
}
}
>
<
Card
>
<
CardHeader
avatar=
{
<
img
src=
"/assets/icon_panduan_penggunaan_1.svg"
alt=
"Panduan"
loading=
"lazy"
/>
}
sx=
{
{
backgroundColor
:
'
#123375
'
,
color
:
'
#FFFFFF
'
,
padding
:
'
16px
'
,
'
& .MuiCardHeader-title
'
:
{
fontSize
:
18
,
},
}
}
action=
{
<
IconButton
aria
-
label=
"close"
onClick=
{
handleOpen
}
sx=
{
{
color
:
'
white
'
}
}
>
<
CloseRounded
/>
</
IconButton
>
}
title=
"Panduan Penggunaan"
/>
<
CardContent
sx=
{
{
maxHeight
:
300
,
overflow
:
'
auto
'
,
'
&::-webkit-scrollbar
'
:
{
width
:
6
},
'
&::-webkit-scrollbar-track
'
:
{
backgroundColor
:
'
#f0f0f0
'
,
borderRadius
:
8
},
'
&::-webkit-scrollbar-thumb
'
:
{
backgroundColor
:
'
#123375
'
,
borderRadius
:
8
},
'
&::-webkit-scrollbar-thumb:hover
'
:
{
backgroundColor
:
'
#0d2858
'
},
scrollbarWidth
:
'
thin
'
,
scrollbarColor
:
'
#123375 #f0f0f0
'
,
}
}
>
{
/* Deskripsi Form */
}
<
Typography
variant=
"body2"
sx=
{
{
mb
:
2
,
whiteSpace
:
'
pre-line
'
}
}
>
<
span
style=
{
{
fontWeight
:
600
}
}
>
Deskripsi Form:
</
span
>
<
br
/>
{
PANDUAN_REKAM_NR
.
description
.
intro
}
</
Typography
>
<
Typography
variant=
"body2"
sx=
{
{}
}
>
{
PANDUAN_REKAM_NR
.
description
.
textList
}
</
Typography
>
<
Box
component=
"ol"
sx=
{
{
pl
:
3
,
mb
:
2
}
}
>
{
PANDUAN_REKAM_NR
.
description
.
list
.
map
((
item
,
idx
)
=>
(
<
Typography
key=
{
idx
}
variant=
"body2"
component=
"li"
>
{
item
}
</
Typography
>
))
}
</
Box
>
<
Typography
variant=
"body2"
sx=
{
{
mb
:
2
}
}
>
{
PANDUAN_REKAM_NR
.
description
.
closing
}
</
Typography
>
{
/* Bagian-bagian */
}
{
PANDUAN_REKAM_NR
.
sections
.
map
((
section
,
i
)
=>
(
<
Box
key=
{
i
}
sx=
{
{
mb
:
2
}
}
>
<
Typography
variant=
"body2"
sx=
{
{
fontWeight
:
'
bold
'
,
fontSize
:
'
0.95rem
'
,
mb
:
0.5
}
}
>
{
section
.
title
}
</
Typography
>
<
Box
component=
"ul"
sx=
{
{
pl
:
2
,
listStyle
:
'
disc
'
}
}
>
{
section
.
items
.
map
((
item
,
idx
)
=>
(
<
Box
key=
{
idx
}
component=
"li"
sx=
{
{
mb
:
0.5
}
}
>
<
Typography
variant=
"body2"
component=
"span"
>
{
item
.
text
}
</
Typography
>
{
item
.
subItems
.
length
>
0
&&
(
<
Box
component=
"ol"
sx=
{
{
pl
:
3
,
listStyle
:
'
decimal
'
}
}
>
{
item
.
subItems
.
map
((
sub
,
subIdx
)
=>
(
<
Typography
key=
{
subIdx
}
variant=
"body2"
component=
"li"
>
{
sub
}
</
Typography
>
))
}
</
Box
>
)
}
</
Box
>
))
}
</
Box
>
</
Box
>
))
}
</
CardContent
>
</
Card
>
</
m
.
div
>
)
}
</
Box
>
);
};
export
default
PanduanNrRekam
;
src/sections/bupot-unifikasi/bupot-nr/components/rekamNr/PphDipotong.tsx
deleted
100644 → 0
View file @
3b0dd65d
import
Divider
from
'
@mui/material/Divider
'
;
import
Grid
from
'
@mui/material/Grid
'
;
import
MenuItem
from
'
@mui/material/MenuItem
'
;
import
{
useEffect
,
useMemo
}
from
'
react
'
;
import
{
Field
}
from
'
src/components/hook-form
'
;
import
{
useFormContext
}
from
'
react-hook-form
'
;
import
FieldNumberText
from
'
src/shared/components/FieldNumberText
'
;
import
usePphDipotong
from
'
src/sections/bupot-unifikasi/bupot-dn/hooks/usePphDipotong
'
;
import
{
FG_FASILITAS_DN
,
FG_FASILITAS_MASTER_KEY
,
FG_FASILITAS_TEXT
,
}
from
'
src/sections/bupot-unifikasi/bupot-dn/constant
'
;
import
{
TGetListDataKOPDn
}
from
'
src/sections/bupot-unifikasi/bupot-dn/types/types
'
;
type
PPHDipotongProps
=
{
kodeObjectPajak
:
TGetListDataKOPDn
[];
};
const
PphDipotong
=
({
kodeObjectPajak
}:
PPHDipotongProps
)
=>
{
const
{
watch
,
setValue
}
=
useFormContext
();
const
selectedKode
=
watch
(
'
kdObjPjk
'
);
const
fgFasilitas
=
watch
(
'
fgFasilitas
'
);
const
kodeObjekPajakSelected
=
useMemo
(
()
=>
kodeObjectPajak
.
find
((
item
)
=>
item
.
kode
===
selectedKode
),
[
kodeObjectPajak
,
selectedKode
]
);
// Hook otomatis hitung tarif & pphDipotong
usePphDipotong
(
kodeObjekPajakSelected
);
// Fasilitas options
const
fgFasilitasOptions
=
useMemo
(
()
=>
Object
.
entries
(
FG_FASILITAS_DN
).
map
(([
_
,
value
])
=>
({
value
,
label
:
FG_FASILITAS_TEXT
[
value
],
})),
[]
);
const
fasilitasOptions
=
useMemo
(
()
=>
fgFasilitasOptions
.
filter
(
(
opt
)
=>
kodeObjekPajakSelected
&&
kodeObjekPajakSelected
[
FG_FASILITAS_MASTER_KEY
[
opt
.
value
]
as
keyof
TGetListDataKOPDn
]
===
1
),
[
fgFasilitasOptions
,
kodeObjekPajakSelected
]
);
// Reset fasilitas jika kode objek pajak berubah
useEffect
(()
=>
{
setValue
(
'
fgFasilitas
'
,
''
,
{
shouldValidate
:
true
});
},
[
selectedKode
,
setValue
]);
return
(
<
Grid
container
rowSpacing=
{
2
}
columnSpacing=
{
2
}
>
{
/* Kode Objek Pajak */
}
<
Grid
sx=
{
{
mt
:
3
}
}
size=
{
{
md
:
6
}
}
>
<
Field
.
Select
name=
"kdObjPjk"
label=
"Kode Objek Pajak"
>
{
kodeObjectPajak
.
map
((
item
)
=>
(
<
MenuItem
key=
{
item
.
kode
}
value=
{
item
.
kode
}
>
{
`(${item.kode}) ${item.nama}`
}
</
MenuItem
>
))
}
</
Field
.
Select
>
</
Grid
>
{
/* Divider */
}
<
Grid
size=
{
{
md
:
12
}
}
>
<
Divider
sx=
{
{
fontWeight
:
'
bold
'
}
}
textAlign=
"left"
>
Fasilitas Pajak Penghasilan
</
Divider
>
</
Grid
>
{
/* Fasilitas */
}
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Select
name=
"fgFasilitas"
label=
"Fasilitas"
>
{
fasilitasOptions
.
length
===
0
?
(
<
MenuItem
disabled
value=
""
>
No options
</
MenuItem
>
)
:
(
fasilitasOptions
.
map
((
opt
)
=>
(
<
MenuItem
key=
{
opt
.
value
}
value=
{
opt
.
value
}
>
{
opt
.
label
}
</
MenuItem
>
))
)
}
</
Field
.
Select
>
</
Grid
>
{
/* Nomor Dokumen Lainnya */
}
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"noDokLainnya"
label=
"Nomor Dokumen Lainnya"
disabled=
{
[
'
9
'
,
''
].
includes
(
fgFasilitas
)
}
sx=
{
{
'
& .MuiInputBase-root.Mui-disabled
'
:
{
backgroundColor
:
'
#f6f6f6
'
,
},
}
}
/>
</
Grid
>
{
/* Jumlah Bruto */
}
<
Grid
size=
{
{
md
:
6
}
}
>
<
FieldNumberText
name=
"jmlBruto"
label=
"Jumlah Penghasilan Bruto (Rp)"
/>
</
Grid
>
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"kiraNetto"
label=
"Perkiraan Penghasilan Netto (%)"
type=
"number"
slotProps=
{
{
input
:
{
readOnly
:
true
,
style
:
{
backgroundColor
:
'
#f6f6f6
'
},
},
}
}
/>
</
Grid
>
{
/* Tarif */
}
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"tarif"
label=
"Tarif (%)"
type=
"number"
value=
{
kodeObjekPajakSelected
?.
tarif
||
''
}
slotProps=
{
{
input
:
{
readOnly
:
!
[
FG_FASILITAS_DN
.
SKD_WPLN
,
FG_FASILITAS_DN
.
FASILITAS_LAINNYA
].
includes
(
fgFasilitas
),
style
:
{
backgroundColor
:
!
[
FG_FASILITAS_DN
.
SKD_WPLN
,
FG_FASILITAS_DN
.
FASILITAS_LAINNYA
,
].
includes
(
fgFasilitas
)
?
'
#f6f6f6
'
:
undefined
,
},
},
}
}
/>
</
Grid
>
{
/* PPh Dipotong */
}
<
Grid
size=
{
{
md
:
6
}
}
>
<
Field
.
Text
name=
"pphDipotong"
label=
"PPh Yang Dipotong/Dipungut"
type=
"number"
slotProps=
{
{
input
:
{
readOnly
:
true
,
style
:
{
backgroundColor
:
'
#f6f6f6
'
},
},
}
}
/>
</
Grid
>
</
Grid
>
);
};
export
default
PphDipotong
;
src/sections/bupot-unifikasi/bupot-nr/components/toolbarCancel.tsx
0 → 100644
View file @
04416926
import
React
from
'
react
'
;
import
{
IconButton
,
Tooltip
}
from
'
@mui/material
'
;
import
HighlightOffTwoToneIcon
from
'
@mui/icons-material/HighlightOffTwoTone
'
;
interface
ToolbarCancelProps
{
selectedRows
:
any
[];
selectedRowsData
:
any
[];
onCancel
:
(
ids
:
number
[])
=>
void
;
}
const
ToolbarCancel
:
React
.
FC
<
ToolbarCancelProps
>
=
({
selectedRows
,
selectedRowsData
,
onCancel
,
})
=>
{
// Logic sederhana
const
isEnabled
=
selectedRows
.
length
>
0
&&
selectedRowsData
.
every
((
row
:
any
)
=>
row
.
fgStatus
===
'
normal
'
||
row
.
fgStatus
===
'
amendment
'
);
const
handleClick
=
()
=>
{
if
(
!
isEnabled
)
return
;
const
ids
=
selectedRowsData
.
map
((
row
:
any
)
=>
row
.
id
).
filter
((
id
:
any
)
=>
id
!==
undefined
);
onCancel
(
ids
);
};
return
(
<
Tooltip
title=
{
isEnabled
?
`Batalkan ${selectedRows.length} data`
:
'
Pilih data yang valid
'
}
>
<
IconButton
onClick=
{
handleClick
}
disabled=
{
!
isEnabled
}
color=
{
isEnabled
?
'
error
'
:
'
default
'
}
>
<
HighlightOffTwoToneIcon
/>
</
IconButton
>
</
Tooltip
>
);
};
export
default
ToolbarCancel
;
src/sections/bupot-unifikasi/bupot-nr/constant/index.tsx
View file @
04416926
This diff is collapsed.
Click to expand it.
src/sections/bupot-unifikasi/bupot-nr/constant/queryKey.tsx
0 → 100644
View file @
04416926
const
appRootKey
=
'
unifikasi
'
;
const
queryKey
=
{
getKodeObjekPajak
:
(
params
:
any
)
=>
[
appRootKey
,
'
kode-objek-pajak
'
,
params
],
dn
:
{
all
:
(
params
:
any
)
=>
[
appRootKey
,
'
dn
'
,
params
],
detail
:
(
params
:
any
)
=>
[
appRootKey
,
'
dn
'
,
'
detail
'
,
params
],
draft
:
[
appRootKey
,
'
dn
'
,
'
draft
'
],
delete
:
[
appRootKey
,
'
dn
'
,
'
delete
'
],
upload
:
[
appRootKey
,
'
dn
'
,
'
upload
'
],
cancel
:
[
appRootKey
,
'
dn
'
,
'
cancel
'
],
cetakPdf
:
(
params
:
any
)
=>
[
appRootKey
,
'
dn-cetak-pdf
'
,
params
],
},
};
export
default
queryKey
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useAdvancedFilterDn.tsx
0 → 100644
View file @
04416926
This diff is collapsed.
Click to expand it.
src/sections/bupot-unifikasi/bupot-nr/hooks/useCancelDn.tsx
0 → 100644
View file @
04416926
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
{
TCancelDnRequest
,
TCancelDnResponse
}
from
'
../types/types
'
;
import
dnApi
from
'
../utils/api
'
;
const
useCancelDn
=
(
props
?:
any
)
=>
useMutation
<
TCancelDnResponse
,
Error
,
TCancelDnRequest
>
({
mutationKey
:
[
'
cancel-dn
'
],
mutationFn
:
(
payload
)
=>
dnApi
.
cancel
(
payload
),
...
props
,
});
export
default
useCancelDn
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useCetakPdfDn.tsx
0 → 100644
View file @
04416926
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
dnApi
from
'
../utils/api
'
;
const
useCetakPdfDn
=
(
options
?:
any
)
=>
useMutation
({
mutationKey
:
[
'
unifikasi
'
,
'
dn
'
,
'
cetak-pdf
'
],
mutationFn
:
async
(
params
:
any
)
=>
dnApi
.
cetakPdfDetail
(
params
),
...
options
,
});
export
default
useCetakPdfDn
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useDeleteDn.tsx
0 → 100644
View file @
04416926
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
{
TDeleteDnRequest
,
TBaseResponseAPI
}
from
'
../types/types
'
;
import
dnApi
from
'
../utils/api
'
;
const
useDeleteDn
=
(
props
?:
any
)
=>
useMutation
<
TBaseResponseAPI
<
null
>
,
Error
,
TDeleteDnRequest
>
({
mutationKey
:
[
'
delete-dn
'
],
mutationFn
:
(
payload
)
=>
dnApi
.
deleteDn
(
payload
),
...
props
,
});
export
default
useDeleteDn
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useGetDn.tsx
0 → 100644
View file @
04416926
import
{
isEmpty
}
from
'
lodash
'
;
import
{
useQuery
}
from
'
@tanstack/react-query
'
;
import
dnApi
from
'
../utils/api
'
;
import
{
TGetListDataTableDn
,
TGetListDataTableDnResult
}
from
'
../types/types
'
;
import
{
FG_PDF_STATUS
,
FG_SIGN_STATUS
}
from
'
../constant
'
;
import
queryKey
from
'
../constant/queryKey
'
;
export
type
TGetDnApiWrapped
=
{
data
:
TGetListDataTableDnResult
[];
total
:
number
;
pageSize
:
number
;
page
:
number
;
// 1-based
};
// ---------- helpers (unchanged, kept for completeness) ----------
export
const
transformFgStatusToFgSignStatus
=
(
fgStatus
:
any
)
=>
{
const
splittedFgStatus
=
fgStatus
?.
split
(
'
-
'
)
||
[];
if
(
splittedFgStatus
.
includes
(
'
SIGN
'
)
>
0
)
return
FG_SIGN_STATUS
.
FAILED
;
if
(
splittedFgStatus
.
includes
(
'
SIGNING IN PROGRESS
'
))
return
FG_SIGN_STATUS
.
IN_PROGRESS
;
if
(
fgStatus
===
'
DUPLICATE
'
)
return
FG_SIGN_STATUS
.
DUPLICATE
;
if
(
fgStatus
===
'
NOT_MATCH_STATUS
'
)
return
FG_SIGN_STATUS
.
NOT_MATCH_STATUS
;
if
(
fgStatus
===
'
NOT_MATCH_NILAI
'
)
return
FG_SIGN_STATUS
.
NOT_MATCH_NILAI
;
if
(
fgStatus
===
'
NOT_MATCH_IDBUPOT
'
)
return
FG_SIGN_STATUS
.
NOT_MATCH_IDBUPOT
;
switch
(
splittedFgStatus
[
1
])
{
case
'
document signed successfully
'
:
case
'
Done
'
:
return
FG_SIGN_STATUS
.
SIGNED
;
case
'
SIGNING_IN_PROGRESS
'
:
return
FG_SIGN_STATUS
.
IN_PROGRESS
;
case
'
DUPLICATE
'
:
return
FG_SIGN_STATUS
.
DUPLICATE
;
case
'
NOT_MATCH_STATUS
'
:
return
FG_SIGN_STATUS
.
NOT_MATCH_STATUS
;
case
'
NOT_MATCH_IDBUPOT
'
:
return
FG_SIGN_STATUS
.
NOT_MATCH_IDBUPOT
;
default
:
return
null
;
}
};
export
const
getFgStatusPdf
=
(
link
:
any
,
fgSignStatus
:
any
)
=>
{
if
(
!
link
||
[
FG_SIGN_STATUS
.
IN_PROGRESS
].
includes
(
fgSignStatus
))
return
FG_PDF_STATUS
.
TIDAK_TERSEDIA
;
if
(
!
link
.
includes
(
'
https://coretaxdjp.pajak.go.id/
'
))
return
FG_PDF_STATUS
.
BELUM_TERBENTUK
;
return
FG_PDF_STATUS
.
TERBENTUK
;
};
export
const
transformSortModelToSortApiPayload
=
(
transformedModel
:
any
)
=>
({
sortingMode
:
transformedModel
.
map
((
item
:
any
)
=>
item
.
field
).
join
(
'
,
'
),
sortingMethod
:
transformedModel
.
length
>
0
?
transformedModel
[
0
].
sort
:
'
desc
'
,
});
export
const
formatDateToDDMMYYYY
=
(
dateString
:
string
|
null
|
undefined
)
=>
{
if
(
!
dateString
)
return
''
;
const
date
=
new
Date
(
dateString
);
const
day
=
String
(
date
.
getDate
()).
padStart
(
2
,
'
0
'
);
const
month
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'
0
'
);
const
year
=
date
.
getFullYear
();
return
`
${
day
}
/
${
month
}
/
${
year
}
`
;
};
const
normalisePropsGetDn
=
(
params
:
TGetListDataTableDn
)
=>
({
...
params
,
nomorSP2D
:
params
.
dokumen_referensi
?.[
0
]?.
nomorSP2D
||
''
,
metodePembayaranBendahara
:
params
.
dokumen_referensi
?.[
0
]?.
metodePembayaranBendahara
||
''
,
dokReferensi
:
params
.
dokumen_referensi
?.[
0
]?.
dokReferensi
||
''
,
nomorDokumen
:
params
.
dokumen_referensi
?.[
0
]?.
nomorDokumen
||
''
,
id
:
params
.
id
,
npwpPemotong
:
params
.
npwpPemotong
,
idBupot
:
params
.
idBupot
,
internal_id
:
params
.
internal_id
,
fgStatus
:
params
.
fgStatus
,
fgSignStatus
:
transformFgStatusToFgSignStatus
(
params
.
fgStatus
),
fgPdf
:
getFgStatusPdf
(
params
.
link
,
transformFgStatusToFgSignStatus
(
params
.
fgStatus
)),
fgLapor
:
params
.
fgLapor
,
revNo
:
params
.
revNo
,
thnPajak
:
params
.
tahunPajak
,
msPajak
:
params
.
masaPajak
,
kdObjPjk
:
params
.
kodeObjekPajak
,
noBupot
:
params
.
noBupot
,
idDipotong
:
params
.
userId
,
glAccount
:
params
.
glAccount
,
namaDipotong
:
params
.
nama
,
jmlBruto
:
params
.
dpp
,
pphDipotong
:
params
.
pphDipotong
,
created
:
params
.
created_by
,
fgKirimEmail
:
params
.
fgkirimemail
,
created_at
:
formatDateToDDMMYYYY
(
params
.
created_at
),
updated
:
params
.
updated_by
,
updated_at
:
formatDateToDDMMYYYY
(
params
.
updated_at
),
});
// ---------- normalizer for params request ----------
const
normalisPropsParmasGetDn
=
(
params
:
any
)
=>
{
const
sorting
=
!
isEmpty
(
params
.
sortModel
)
?
transformSortModelToSortApiPayload
(
params
.
sortModel
)
:
{};
return
{
...
params
,
page
:
(
typeof
params
.
page
===
'
number
'
?
params
.
page
:
0
)
+
1
,
limit
:
params
.
pageSize
,
masaPajak
:
params
.
msPajak
||
null
,
tahunPajak
:
params
.
thnPajak
||
null
,
npwp
:
params
.
idDipotong
||
null
,
advanced
:
isEmpty
(
params
.
advanced
)
?
undefined
:
params
.
advanced
,
...
sorting
,
};
};
const
normalizeParams
=
(
params
:
any
)
=>
{
const
{
page
=
0
,
pageSize
=
params
.
limit
??
10
,
sort
,
filter
,
advanced
,
sortingMode
:
sortingModeParam
,
sortingMethod
:
sortingMethodParam
,
...
rest
}
=
params
;
let
sortPayload
:
any
;
let
sortingMode
=
sortingModeParam
||
''
;
let
sortingMethod
=
sortingMethodParam
||
''
;
if
(
sort
)
{
try
{
const
parsed
=
JSON
.
parse
(
sort
);
if
(
Array
.
isArray
(
parsed
)
&&
parsed
.
length
>
0
)
{
sortPayload
=
parsed
;
sortingMode
=
parsed
[
0
]?.
field
??
sortingMode
;
sortingMethod
=
parsed
[
0
]?.
sort
??
sortingMethod
;
}
}
catch
{
sortPayload
=
[];
}
}
return
{
page
:
page
+
1
,
limit
:
pageSize
,
advanced
:
typeof
advanced
===
'
string
'
&&
advanced
.
trim
()
!==
''
?
advanced
:
filter
&&
!
isEmpty
(
JSON
.
parse
(
filter
))
?
filter
:
undefined
,
...(
sortPayload
?
{
sort
:
sortPayload
}
:
{}),
sortingMode
,
sortingMethod
,
...
rest
,
};
};
export
const
useGetDn
=
({
params
}:
{
params
:
any
})
=>
{
const
{
page
,
limit
,
advanced
,
sortingMode
,
sortingMethod
}
=
params
;
const
normalized
=
normalizeParams
(
params
);
return
useQuery
<
TGetDnApiWrapped
>
({
queryKey
:
[
'
dn
'
,
page
,
limit
,
advanced
,
sortingMode
,
sortingMethod
],
queryFn
:
async
()
=>
{
const
res
:
any
=
await
dnApi
.
getDn
({
params
:
normalized
});
const
rawData
:
any
[]
=
Array
.
isArray
(
res
?.
data
)
?
res
.
data
:
res
?.
data
?
[
res
.
data
]
:
[];
const
total
=
Number
(
res
?.
total
??
res
?.
totalRow
??
0
);
let
dataArray
:
TGetListDataTableDnResult
[]
=
[];
const
normalizeWithWorker
=
()
=>
new
Promise
<
TGetListDataTableDnResult
[]
>
((
resolve
,
reject
)
=>
{
try
{
const
worker
=
new
Worker
(
new
URL
(
'
../workers/normalizeDn.worker.js
'
,
import
.
meta
.
url
),
{
type
:
'
module
'
}
);
worker
.
onmessage
=
(
e
)
=>
{
const
{
data
,
error
}
=
e
.
data
;
if
(
error
)
{
worker
.
terminate
();
reject
(
new
Error
(
error
));
}
else
{
worker
.
terminate
();
resolve
(
data
as
TGetListDataTableDnResult
[]);
}
};
worker
.
onerror
=
(
err
)
=>
{
worker
.
terminate
();
reject
(
err
);
};
worker
.
postMessage
(
rawData
);
}
catch
(
err
)
{
reject
(
err
);
}
});
try
{
if
(
typeof
Worker
!==
'
undefined
'
)
{
dataArray
=
await
normalizeWithWorker
();
}
else
{
console
.
warn
(
'
⚠️ Worker not supported, using sync normalization
'
);
dataArray
=
rawData
.
map
(
normalisePropsGetDn
)
as
unknown
as
TGetListDataTableDnResult
[];
}
}
catch
(
err
)
{
console
.
error
(
'
❌ Worker failed, fallback to sync normalize:
'
,
err
);
dataArray
=
rawData
.
map
(
normalisePropsGetDn
)
as
unknown
as
TGetListDataTableDnResult
[];
}
return
{
data
:
dataArray
,
total
,
pageSize
:
normalized
.
limit
,
page
:
normalized
.
page
,
};
},
placeholderData
:
(
prev
)
=>
prev
,
refetchOnWindowFocus
:
false
,
refetchOnMount
:
false
,
staleTime
:
0
,
gcTime
:
0
,
retry
:
false
,
});
};
export
const
useGetDnById
=
(
id
:
string
,
options
=
{})
=>
useQuery
({
queryKey
:
queryKey
.
dn
.
detail
(
id
),
queryFn
:
async
()
=>
{
console
.
log
(
'
🔍 Fetching getDnById with ID:
'
,
id
);
const
res
=
await
dnApi
.
getDnById
(
id
);
if
(
!
res
)
throw
new
Error
(
'
Data tidak ditemukan
'
);
const
normalized
=
{
id
:
res
.
id
??
''
,
tglPemotongan
:
res
.
tglpemotongan
??
''
,
thnPajak
:
res
.
tahunPajak
??
''
,
msPajak
:
res
.
masaPajak
??
''
,
idDipotong
:
res
.
npwpPemotong
??
''
,
nitku
:
res
.
idTku
??
''
,
namaDipotong
:
res
.
nama
??
''
,
email
:
res
.
email
??
''
,
keterangan1
:
res
.
keterangan1
??
''
,
keterangan2
:
res
.
keterangan2
??
''
,
keterangan3
:
res
.
keterangan3
??
''
,
keterangan4
:
res
.
keterangan4
??
''
,
keterangan5
:
res
.
keterangan5
??
''
,
kdObjPjk
:
res
.
kodeObjekPajak
??
''
,
fgFasilitas
:
res
.
sertifikatInsentifDipotong
??
''
,
noDokLainnya
:
res
.
nomorSertifikatInsentif
??
''
,
jmlBruto
:
res
.
dpp
??
''
,
tarif
:
String
(
res
.
tarif
??
''
),
pphDipotong
:
String
(
res
.
pphDipotong
??
''
),
namaDok
:
res
.
dokumen_referensi
?.[
0
]?.
dokReferensi
??
''
,
nomorDok
:
res
.
dokumen_referensi
?.[
0
]?.
nomorDokumen
??
''
,
tglDok
:
res
.
dokumen_referensi
?.[
0
]?.
tanggal_Dokumen
??
''
,
idTku
:
res
.
idTku
??
''
,
revNo
:
res
.
revNo
??
0
,
noBupot
:
res
.
noBupot
??
''
,
idBupot
:
res
.
idBupot
??
''
,
};
console
.
log
(
'
✅ Normalized data:
'
,
normalized
);
return
normalized
;
},
enabled
:
!!
id
,
refetchOnWindowFocus
:
false
,
...
options
,
});
export
default
useGetDn
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useGetKodeObjekPajak.tsx
0 → 100644
View file @
04416926
import
{
useQuery
}
from
'
@tanstack/react-query
'
;
import
{
TBaseResponseAPI
,
TGetListDataKOPDnResult
}
from
'
../types/types
'
;
import
queryKey
from
'
../constant/queryKey
'
;
import
dnApi
from
'
../utils/api
'
;
const
useGetKodeObjekPajak
=
(
params
?:
Record
<
string
,
any
>
)
=>
useQuery
<
TBaseResponseAPI
<
TGetListDataKOPDnResult
>>
({
queryKey
:
queryKey
.
getKodeObjekPajak
(
params
),
queryFn
:
()
=>
dnApi
.
getKodeObjekPajakDn
(
params
),
});
export
default
useGetKodeObjekPajak
;
src/sections/bupot-unifikasi/bupot-nr/hooks/usePphDipotong.tsx
0 → 100644
View file @
04416926
import
{
useEffect
}
from
'
react
'
;
import
{
useFormContext
,
useWatch
}
from
'
react-hook-form
'
;
import
{
TGetListDataKOPDn
}
from
'
../types/types
'
;
const
usePphDipotong
=
(
kodeObjekPajakSelected
?:
TGetListDataKOPDn
)
=>
{
const
{
watch
,
setValue
,
control
}
=
useFormContext
();
// ambil value dari form
const
fgFasilitas
=
watch
(
'
fgFasilitas
'
);
const
fgIdDipotong
=
watch
(
'
fgIdDipotong
'
);
// mapping statusPPh ke isFinal
const
isFinal
=
kodeObjekPajakSelected
?.
statuspph
?.
toLowerCase
()
===
'
final
'
?
1
:
0
;
const
updateTarifValues
=
()
=>
{
if
(
kodeObjekPajakSelected
)
{
let
valueTarif
=
Number
(
kodeObjekPajakSelected
.
tarif
)
||
0
;
if
(
fgFasilitas
===
'
6
'
)
{
valueTarif
=
0.5
;
}
else
if
(
fgFasilitas
===
'
8
'
)
{
valueTarif
=
0
;
}
setValue
(
'
tarif
'
,
valueTarif
,
{
shouldValidate
:
true
});
setValue
(
'
tarifLt
'
,
fgIdDipotong
===
'
1
'
&&
isFinal
===
0
?
'
100
'
:
'
0
'
,
{
shouldValidate
:
true
,
});
}
};
// watch field yang mempengaruhi perhitungan
const
handlerSetPphDipotong
=
useWatch
({
control
,
name
:
[
'
thnPajak
'
,
'
fgFasilitas
'
,
'
fgIdDipotong
'
,
'
jmlBruto
'
,
'
tarif
'
],
});
// eslint-disable-next-line @typescript-eslint/no-shadow
const
calculateAndSetPphDipotong
=
(
thnPajak
:
number
,
fgFasilitas
:
string
,
fgIdDipotong
:
string
,
jmlBruto
:
number
,
tarif
:
number
)
=>
{
if
(
kodeObjekPajakSelected
)
{
const
valTarif
=
thnPajak
<
2024
&&
fgIdDipotong
===
'
1
'
&&
isFinal
===
0
?
tarif
*
2
:
tarif
;
const
valPphDipotong
=
fgFasilitas
===
'
8
'
// contoh: fasilitas tertentu PPh 0
?
0
:
(
jmlBruto
*
valTarif
)
/
100
;
setValue
(
'
pphDipotong
'
,
Math
.
round
(
valPphDipotong
||
0
),
{
shouldValidate
:
true
,
});
}
};
useEffect
(()
=>
{
if
(
handlerSetPphDipotong
.
filter
((
item
)
=>
!
item
).
length
<
2
)
{
calculateAndSetPphDipotong
(
Number
(
handlerSetPphDipotong
[
0
]),
handlerSetPphDipotong
[
1
]
as
string
,
handlerSetPphDipotong
[
2
]
as
string
,
Number
(
handlerSetPphDipotong
[
3
]),
Number
(
handlerSetPphDipotong
[
4
])
);
}
},
[
handlerSetPphDipotong
]);
return
{
updateTarifValues
,
};
};
export
default
usePphDipotong
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useSaveDn.tsx
0 → 100644
View file @
04416926
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
dayjs
from
'
dayjs
'
;
import
dnApi
from
'
../utils/api
'
;
import
{
TPostDnRequest
}
from
'
../types/types
'
;
const
transformParams
=
({
isPengganti
=
false
,
...
dnData
}:
any
):
TPostDnRequest
=>
{
const
{
id
,
idBupot
,
noBupot
,
msPajak
,
thnPajak
,
idDipotong
,
nitku
,
namaDipotong
,
fgFasilitas
,
noDokLainnya
,
kdObjPjk
,
kdJnsPjk
,
statusPph
,
jmlBruto
,
tarif
,
pphDipotong
,
kap
,
kjs
,
revNo
:
initialRevNo
,
tglPemotongan
,
namaDok
,
nomorDok
,
tglDok
,
metodePembayaranBendahara
,
nomorSP2D
,
idTku
,
email
,
glAccount
,
keterangan1
,
keterangan2
,
keterangan3
,
keterangan4
,
keterangan5
,
}
=
dnData
;
const
dokReferensi
=
[
{
dokReferensi
:
namaDok
||
''
,
nomorDokumen
:
nomorDok
||
''
,
tanggal_Dokumen
:
tglDok
?
dayjs
(
tglDok
).
format
(
'
DDMMYYYY
'
)
:
''
,
metodePembayaranBendahara
:
metodePembayaranBendahara
||
''
,
nomorSP2D
:
nomorSP2D
||
''
,
},
];
const
revNo
=
isPengganti
?
parseInt
(
initialRevNo
||
0
,
10
)
+
1
:
parseInt
(
initialRevNo
||
0
,
10
);
const
npwpLog
=
localStorage
.
getItem
(
'
npwp_log
'
)
??
''
;
return
{
id
:
!
isPengganti
?
(
id
??
null
)
:
null
,
idBupot
:
idBupot
??
null
,
noBupot
:
noBupot
??
null
,
npwpPemotong
:
npwpLog
,
idTku
:
idTku
??
''
,
masaPajak
:
msPajak
?
dayjs
(
msPajak
).
format
(
'
MM
'
)
:
''
,
tahunPajak
:
thnPajak
?
Number
(
dayjs
(
thnPajak
).
format
(
'
YYYY
'
))
:
0
,
npwp
:
idDipotong
??
''
,
nik
:
nitku
??
(
idDipotong
?
`
${
idDipotong
}
000000`
:
''
),
nama
:
namaDipotong
??
''
,
revNo
,
fgNpwpNik
:
'
true
'
,
// static
fgJnsBupot
:
'
BPU
'
,
// static
dataDetilBpu
:
{
sertifikatInsentifDipotong
:
fgFasilitas
??
'
9
'
,
nomorSertifikatInsentif
:
noDokLainnya
??
''
,
kodeObjekPajak
:
kdObjPjk
??
''
,
pasalPPh
:
kdJnsPjk
??
''
,
statusPPh
:
statusPph
??
''
,
dpp
:
jmlBruto
??
''
,
tarif
:
tarif
??
''
,
pphDipotong
:
pphDipotong
??
''
,
kap
:
kap
??
''
,
kjs
:
kjs
??
''
,
dokReferensi
,
},
tglPemotongan
:
tglPemotongan
?
dayjs
(
tglPemotongan
).
format
(
'
DDMMYYYY
'
)
:
''
,
email
:
email
??
''
,
glAccount
:
glAccount
??
''
,
keterangan1
:
keterangan1
??
''
,
keterangan2
:
keterangan2
??
''
,
keterangan3
:
keterangan3
??
''
,
keterangan4
:
keterangan4
??
''
,
keterangan5
:
keterangan5
??
''
,
};
};
const
useSaveDn
=
(
props
?:
any
)
=>
useMutation
({
mutationKey
:
[
'
Save-Dn
'
],
mutationFn
:
(
params
:
any
)
=>
dnApi
.
saveDn
(
transformParams
(
params
)),
...
props
,
});
export
default
useSaveDn
;
src/sections/bupot-unifikasi/bupot-nr/hooks/useUpload.tsx
0 → 100644
View file @
04416926
// hooks/useUpload.ts
import
{
useMutation
}
from
'
@tanstack/react-query
'
;
import
dnApi
from
'
../utils/api
'
;
const
useUpload
=
(
props
?:
any
)
=>
useMutation
({
mutationKey
:
[
'
upload-dn
'
],
mutationFn
:
(
payload
:
{
id
:
string
|
number
})
=>
dnApi
.
upload
(
payload
),
...
props
,
});
export
default
useUpload
;
src/sections/bupot-unifikasi/bupot-nr/store/paginationStore.ts
0 → 100644
View file @
04416926
// import { create } from 'zustand';
// type TableKey = string;
// interface TablePagination {
// page: number;
// pageSize: number;
// }
// interface PaginationState {
// tables: Record<TableKey, TablePagination>;
// setPagination: (table: TableKey, next: Partial<TablePagination>) => void;
// resetPagination: (table: TableKey) => void;
// }
// export const usePaginationStore = create<PaginationState>((set) => ({
// tables: {},
// setPagination: (table, next) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: {
// page: next.page ?? state.tables[table]?.page ?? 0,
// pageSize: next.pageSize ?? state.tables[table]?.pageSize ?? 10,
// },
// },
// })),
// resetPagination: (table) =>
// set((state) => ({
// tables: {
// ...state.tables,
// [table]: { page: 0, pageSize: state.tables[table]?.pageSize ?? 10 },
// },
// })),
// }));
import
{
create
}
from
'
zustand
'
;
type
TableKey
=
string
;
interface
TablePagination
{
page
:
number
;
pageSize
:
number
;
}
interface
PaginationState
{
tables
:
Record
<
TableKey
,
TablePagination
>
;
setPagination
:
(
table
:
TableKey
,
next
:
Partial
<
TablePagination
>
)
=>
void
;
resetPagination
:
(
table
:
TableKey
)
=>
void
;
}
export
const
usePaginationStore
=
create
<
PaginationState
>
((
set
)
=>
({
tables
:
{},
setPagination
:
(
table
,
next
)
=>
set
((
state
)
=>
{
const
prev
=
state
.
tables
[
table
]
??
{
page
:
0
,
pageSize
:
10
};
return
{
tables
:
{
...
state
.
tables
,
[
table
]:
{
page
:
next
.
page
??
prev
.
page
,
pageSize
:
next
.
pageSize
??
prev
.
pageSize
,
},
},
};
}),
resetPagination
:
(
table
)
=>
set
((
state
)
=>
({
tables
:
{
...
state
.
tables
,
[
table
]:
{
page
:
0
,
pageSize
:
state
.
tables
[
table
]?.
pageSize
??
10
},
},
})),
}));
src/sections/bupot-unifikasi/bupot-nr/utils/api.tsx
0 → 100644
View file @
04416926
import
axios
from
'
axios
'
;
import
{
TBaseResponseAPI
,
TCancelDnRequest
,
TCancelDnResponse
,
TDeleteDnRequest
,
TGetListDataKOPDnResult
,
TGetListDataTableDnResult
,
TPostDnRequest
,
TPostUpload
,
}
from
'
../types/types
'
;
import
unifikasiClient
from
'
./unifikasiClient
'
;
const
dnApi
=
()
=>
{};
const
axiosCetakPdf
=
axios
.
create
({
baseURL
:
import
.
meta
.
env
.
VITE_APP_BASE_API_URL_CETAK
,
headers
:
{
Authorization
:
`Basic
${
window
.
btoa
(
'
admin:ortax123
'
)}
`
,
password
:
''
,
},
});
// API untuk get list table
dnApi
.
getDn
=
async
(
config
:
any
)
=>
{
const
{
data
:
{
status
,
message
,
metaPage
,
data
},
status
:
statusCode
,
}
=
await
unifikasiClient
.
get
<
TBaseResponseAPI
<
TGetListDataTableDnResult
>
>
('IF_TXR_028/bpu',
{
...
config
,
}
);
if (statusCode !== 200)
{
throw
new
Error
(
message
);
}
return
{
total
:
metaPage
?
Number
(
metaPage
.
totalRow
)
:
0
,
data
}
;
};
dnApi.getKodeObjekPajakDn = async (params?: Record
<
string
,
any
>
) =
>
{
const
response
=
await
unifikasiClient
.
get
<
TBaseResponseAPI
<
TGetListDataKOPDnResult
>
>
(
'/sandbox/mst_kop_bpu',
{
params
}
);
const body = response.data;
if (response.status !== 200 || body.status !== 'success')
{
throw
new
Error
(
body
.
message
);
}
return body;
}
;
dnApi.saveDn = async (config: TPostDnRequest) =
>
{
const
{
data
:
{
status
,
message
,
data
,
code
},
status
:
statusCode
,
}
=
await
unifikasiClient
.
post
<
TBaseResponseAPI
<
TPostDnRequest
>>
(
'
/IF_TXR_028/bpu
'
,
{
...
config
,
});
if
(
code
===
0
)
{
throw
new
Error
(
message
);
}
return
data
;
}
;
dnApi.getDnById = async (id: string) =
>
{
const
res
=
await
unifikasiClient
.
get
(
'
/IF_TXR_028/bpu
'
,
{
params
:
{
id
}
});
const
{
data
:
{
status
,
message
,
data
},
status
:
statusCode
,
}
=
res
;
if
(
statusCode
!==
200
||
status
?.
toLowerCase
()
!==
'
success
'
)
{
console
.
error
(
'
getDnById failed:
'
,
{
statusCode
,
status
,
message
});
throw
new
Error
(
message
||
'
Gagal mengambil data DN
'
);
}
const
dnData
=
Array
.
isArray
(
data
)
?
data
[
0
]
:
data
;
return
dnData
;
}
;
dnApi.upload = async (
{
id
}
:
{
id
:
string
|
number
}
) =
>
{
const
{
data
:
{
status
,
message
,
data
,
code
},
status
:
statusCode
,
}
=
await
unifikasiClient
.
post
(
'
/IF_TXR_028/bpu/upload
'
,
{
id
});
return
{
status
,
message
,
data
,
code
,
statusCode
};
}
;
dnApi.deleteDn = async (payload: TDeleteDnRequest, config?: Record
<
string
,
any
>
): Promise
<
any
>
=
>
{
const
{
data
:
{
status
,
message
,
data
},
status
:
statusCode
,
}
=
await
unifikasiClient
.
post
<
TBaseResponseAPI
<
any
>>
(
'
/IF_TXR_028/bpu/delete
'
,
payload
,
{
...
config
,
});
if
(
statusCode
!==
200
||
status
?.
toLowerCase
()
===
'
error
'
)
{
throw
new
Error
(
message
||
'
Gagal menghapus data DN
'
);
}
return
data
;
}
;
dnApi.cancel = async (
{
id
,
tglPembatalan
}
: TCancelDnRequest): Promise
<
TCancelDnResponse
>
=
>
{
const
{
data
:
{
status
,
message
,
data
,
code
,
time
,
metaPage
,
total
},
status
:
statusCode
,
}
=
await
unifikasiClient
.
post
(
'
/IF_TXR_028/bpu/batal
'
,
{
id
,
tglPembatalan
,
});
console
.
log
(
'
Cancel DN response:
'
,
{
code
,
message
,
status
});
if
(
code
===
0
)
{
throw
new
Error
(
message
||
'
Gagal membatalkan data
'
);
}
return
{
status
,
message
,
data
,
code
,
time
,
metaPage
,
total
,
};
}
;
dnApi.cetakPdfDetail = async (payload: Record
<
string
,
any
>
) =
>
{
const
response
=
await
axiosCetakPdf
.
post
(
'
/report/ctas/bpu
'
,
payload
);
const
body
=
response
.
data
;
if
(
!
response
||
response
.
status
!==
200
||
body
.
status
===
'
fail
'
||
body
.
status
===
'
error
'
||
body
.
status
===
'
0
'
)
{
throw
new
Error
(
body
.
message
||
'
System tidak dapat memenuhi permintaan, coba beberapa saat lagi
'
);
}
return
body
;
}
;
export default dnApi;
src/sections/bupot-unifikasi/bupot-nr/utils/normalizePayloadCetakPdf.ts
0 → 100644
View file @
04416926
import
dayjs
from
'
dayjs
'
;
import
{
FG_FASILITAS_DN
}
from
'
../constant
'
;
const
FASILITAS_LABEL_MAP
:
Record
<
string
,
string
>
=
{
[
FG_FASILITAS_DN
.
SKB_PPH_PASAL_22
]:
'
SKB PPh Pasal 22
'
,
[
FG_FASILITAS_DN
.
SKB_PPH_PASAL_23
]:
'
SKB PPh Pasal 23
'
,
[
FG_FASILITAS_DN
.
SKB_PPH_PHTB
]:
'
SKB PPh PHTB
'
,
[
FG_FASILITAS_DN
.
DTP
]:
'
DTP
'
,
[
FG_FASILITAS_DN
.
SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN
]:
'
SKB PPh Bunga Deposito Dana Pensiun Tabungan
'
,
[
FG_FASILITAS_DN
.
SUKET_PP23_PP52
]:
'
Suket PP23/PP52
'
,
[
FG_FASILITAS_DN
.
SKD_WPLN
]:
'
SKD WPLN
'
,
[
FG_FASILITAS_DN
.
FASILITAS_LAINNYA
]:
'
Fasilitas Lainnya
'
,
[
FG_FASILITAS_DN
.
TANPA_FASILITAS
]:
'
Tanpa Fasilitas
'
,
[
FG_FASILITAS_DN
.
SKB_PPH_PASAL_21
]:
'
SKB PPh Pasal 21
'
,
[
FG_FASILITAS_DN
.
DTP_PPH_PASAL_21
]:
'
DTP PPh Pasal 21
'
,
};
const
formatTanggalIndo
=
(
isoDate
?:
string
):
string
=>
{
if
(
!
isoDate
)
return
''
;
return
dayjs
(
isoDate
).
locale
(
'
id
'
).
format
(
'
DD MMMM YYYY
'
);
};
/**
* Normalisasi payload Bupot Unifikasi agar sesuai format yang digunakan API cetak PDF
*/
export
const
normalizePayloadCetakPdf
=
(
payload
:
Record
<
string
,
any
>
)
=>
{
if
(
!
payload
)
return
payload
;
const
adjusted
=
{
...
payload
};
if
(
adjusted
.
tglpemotongan
)
{
adjusted
.
tglPemotongan
=
formatTanggalIndo
(
adjusted
.
tglpemotongan
);
// versi tampil
}
// === Konversi kode fasilitas ke label ===
const
fasilitasCode
=
adjusted
.
sertifikatInsentifDipotong
;
adjusted
.
sertifikatInsentifDipotong
=
FASILITAS_LABEL_MAP
[
fasilitasCode
]
||
fasilitasCode
||
''
;
// === Field default tambahan ===
adjusted
.
mixcode
=
adjusted
.
mixcode
||
'
mixcode
'
;
adjusted
.
qrcode
=
adjusted
.
qrcode
||
'
qrcode
'
;
adjusted
.
metodePembayaranBendahara
=
adjusted
.
metodePembayaranBendahara
||
'
-
'
;
adjusted
.
nomorSP2D
=
adjusted
.
nomorSP2D
||
'
-
'
;
adjusted
.
npwpDipotong
=
adjusted
.
npwp
||
''
;
adjusted
.
namaDipotong
=
adjusted
.
nama
||
''
;
adjusted
.
nitkuDipotong
=
adjusted
.
nik
||
''
;
adjusted
.
namaPemotong
=
adjusted
.
nama
||
''
;
adjusted
.
nitkuPemotong
=
adjusted
.
nik
||
''
;
adjusted
.
penghasilanBruto
=
adjusted
.
dpp
||
''
;
adjusted
.
tanggal_Dokumen
=
adjusted
.
dokumen_referensi
[
0
].
tanggal_Dokumen
;
adjusted
.
status
=
'
Proforma
'
;
adjusted
.
msPajak
=
adjusted
.
masaPajak
;
adjusted
.
thnPajak
=
adjusted
.
tahunPajak
;
adjusted
.
kdObjPjk
=
adjusted
.
kodeObjekPajak
;
adjusted
.
fgPdf
=
adjusted
.
fgPdf
===
'
TIDAK_TERSEDIA
'
?
'
2
'
:
adjusted
.
fgPdf
;
return
adjusted
;
};
export
default
normalizePayloadCetakPdf
;
src/sections/bupot-unifikasi/bupot-nr/utils/unifikasiClient.tsx
0 → 100644
View file @
04416926
import
axios
from
'
axios
'
;
const
BASE_URL
=
`https://nodesandbox.pajakexpress.id:1837`
;
const
unifikasiClient
=
axios
.
create
({
baseURL
:
BASE_URL
,
validateStatus
(
status
)
{
return
(
status
>=
200
&&
status
<
300
)
||
status
===
500
;
},
});
// Interceptor untuk selalu update token dari localStorage
unifikasiClient
.
interceptors
.
request
.
use
((
config
)
=>
{
const
jwtAccessToken
=
localStorage
.
getItem
(
'
jwt_access_token
'
);
const
xToken
=
localStorage
.
getItem
(
'
x-token
'
);
if
(
jwtAccessToken
)
{
config
.
headers
.
Authorization
=
`Bearer
${
jwtAccessToken
}
`
;
}
if
(
xToken
)
{
config
.
headers
[
'
x-token
'
]
=
xToken
;
}
return
config
;
});
export
default
unifikasiClient
;
src/sections/bupot-unifikasi/bupot-nr/utils/utils.tsx
0 → 100644
View file @
04416926
import
dayjs
from
'
dayjs
'
;
import
{
MIN_THN_PAJAK
}
from
'
../constant
'
;
export
const
currentYear
=
dayjs
().
year
();
export
const
getHighestStartingYear
=
(
thnAwalUnifikasi
:
any
)
=>
Math
.
max
(
MIN_THN_PAJAK
,
thnAwalUnifikasi
);
export
const
selectedInitialMonth
=
({
thnAwalUnifikasi
,
masaAwalUnifikasi
}:
any
)
=>
{
const
highestYear
=
getHighestStartingYear
(
thnAwalUnifikasi
);
return
highestYear
>
thnAwalUnifikasi
?
'
01
'
:
masaAwalUnifikasi
;
};
export
const
determineStartingMonth
=
({
thnPajak
,
thnAwalUnifikasi
,
masaAwalUnifikasi
}:
any
)
=>
{
const
highestYear
=
getHighestStartingYear
(
thnAwalUnifikasi
);
const
initialMonth
=
selectedInitialMonth
({
thnAwalUnifikasi
,
masaAwalUnifikasi
});
return
thnPajak
>=
highestYear
&&
thnPajak
<=
currentYear
?
initialMonth
:
''
;
};
src/sections/bupot-unifikasi/bupot-nr/view/index.ts
→
src/sections/bupot-unifikasi/bupot-nr/view/index.ts
x
View file @
04416926
File moved
src/sections/bupot-unifikasi/bupot-nr/view/nr-list-view.tsx
View file @
04416926
This diff is collapsed.
Click to expand it.
src/sections/bupot-unifikasi/bupot-nr/view/nrRekamView.tsx
View file @
04416926
import
{
zodResolver
}
from
'
@hookform/resolvers/zod
'
;
import
{
LoadingButton
}
from
'
@mui/lab
'
;
import
Grid
from
'
@mui/material/Grid
'
;
import
React
,
{
Suspense
,
useState
}
from
'
react
'
;
import
{
FormProvider
,
useForm
}
from
'
react-hook-form
'
;
import
{
CustomBreadcrumbs
}
from
'
src/components/custom-breadcrumbs
'
;
import
{
DashboardContent
}
from
'
src/layouts/dashboard
'
;
import
{
paths
}
from
'
src/routes/paths
'
;
import
HeadingRekam
from
'
src/shared/components/HeadingRekam
'
;
import
z
from
'
zod
'
;
import
Divider
from
'
@mui/material/Divider
'
;
import
FormSkeleton
from
'
src/shared/skeletons/FormSkeleton
'
;
import
useGetKodeObjekPajak
from
'
../../bupot-dn/hooks/useGetKodeObjekPajak
'
;
import
DokumenReferensi
from
'
../../bupot-dn/components/rekamDn/DokumenReferensi
'
;
import
Agreement
from
'
src/shared/components/agreement/Agreement
'
;
import
Stack
from
'
@mui/material/Stack
'
;
import
PanduanNrRekam
from
'
../components/rekamNr/PanduanNrRekam
'
;
import
Identitas
from
'
../components/rekamNr/Identitas
'
;
import
PphDipotong
from
'
../components/rekamNr/PphDipotong
'
;
const
bpuSchema
=
z
.
object
({
npwpPemotong
:
z
.
string
().
min
(
10
,
'
NPWP Pemotong wajib diisi
'
),
idTku
:
z
.
string
().
min
(
5
,
'
ID TKU wajib diisi
'
),
masaPajak
:
z
.
string
().
length
(
2
,
'
Masa Pajak harus 2 digit
'
),
tahunPajak
:
z
.
string
().
length
(
4
,
'
Tahun Pajak harus 4 digit
'
),
npwp
:
z
.
string
().
min
(
10
,
'
NPWP wajib diisi
'
),
nik
:
z
.
string
().
min
(
10
,
'
NIK wajib diisi
'
),
nama
:
z
.
string
().
min
(
2
,
'
Nama wajib diisi
'
),
dpp
:
z
.
string
().
min
(
1
,
'
DPP wajib diisi
'
),
tarif
:
z
.
string
().
min
(
1
,
'
Tarif wajib diisi
'
),
pphDipotong
:
z
.
string
().
min
(
1
,
'
PPh Dipotong wajib diisi
'
),
tglPemotongan
:
z
.
string
().
min
(
8
,
'
Format tgl: DDMMYYYY
'
),
glAccount
:
z
.
string
().
min
(
3
,
'
GL Account wajib diisi
'
),
});
const
NrRekamView
=
()
=>
{
const
[
isOpenPanduan
,
setIsOpenPanduan
]
=
useState
<
boolean
>
(
false
);
const
[
isCheckedAgreement
,
setIsCheckedAgreement
]
=
useState
<
boolean
>
(
false
);
const
{
data
,
isLoading
,
isError
}
=
useGetKodeObjekPajak
();
type
BpuFormData
=
z
.
infer
<
typeof
bpuSchema
>
;
const
handleOpenPanduan
=
()
=>
setIsOpenPanduan
(
!
isOpenPanduan
);
const
defaultValues
=
{
npwpPemotong
:
''
,
idTku
:
''
,
masaPajak
:
''
,
tahunPajak
:
''
,
npwp
:
''
,
nik
:
''
,
nama
:
''
,
dpp
:
''
,
tarif
:
''
,
pphDipotong
:
''
,
tglPemotongan
:
''
,
glAccount
:
''
,
fgFasilitas
:
''
,
};
const
methods
=
useForm
({
mode
:
'
all
'
,
resolver
:
zodResolver
(
bpuSchema
),
defaultValues
,
});
const
{
reset
,
handleSubmit
,
formState
:
{
isSubmitting
},
}
=
methods
;
const
SubmitRekam
=
()
=>
{
console
.
log
(
'
Submit API
'
);
};
return
(
<>
<
DashboardContent
>
<
CustomBreadcrumbs
heading=
"Add Bupot Unifikasi Non Residen"
links=
{
[
{
name
:
'
Dashboard
'
,
href
:
paths
.
dashboard
.
root
},
{
name
:
'
e-Bupot Unifikasi Non Residen
'
,
href
:
paths
.
unifikasi
.
nr
},
{
name
:
'
Add Bupot Unifikasi Non Residen
'
},
]
}
/>
<
HeadingRekam
label=
"Rekam Data Bukti Potong PPh Non Residen"
/>
<
Grid
container
columnSpacing=
{
2
}
/* container otomatis */
>
<
Grid
size=
{
{
xs
:
isOpenPanduan
?
8
:
11
}
}
>
<
form
onSubmit=
{
methods
.
handleSubmit
(
SubmitRekam
)
}
>
<
FormProvider
{
...
methods
}
>
<
Suspense
fallback=
{
<
FormSkeleton
/>
}
>
<
Identitas
isPengganti=
{
true
}
/>
<
Divider
/>
<
Suspense
fallback=
{
<
FormSkeleton
/>
}
>
<
PphDipotong
kodeObjectPajak=
{
data
?.
data
??
[]
}
/>
</
Suspense
>
<
DokumenReferensi
/>
<
Divider
/>
<
Grid
size=
{
12
}
>
<
Agreement
isCheckedAgreement=
{
isCheckedAgreement
}
setIsCheckedAgreement=
{
setIsCheckedAgreement
}
text=
"Dengan ini saya menyatakan bahwa Bukti Pemotongan/Pemungutan Unifikasi telah
saya isi dengan benar secara elektronik sesuai
dengan"
/>
</
Grid
>
<
Stack
direction=
"row"
gap=
{
2
}
justifyContent=
"end"
marginTop=
{
2
}
>
<
LoadingButton
type=
"submit"
// loading={saveDn.isLoading}
disabled=
{
!
isCheckedAgreement
}
variant=
"outlined"
sx=
{
{
color
:
'
#143B88
'
}
}
>
Save as Draft
</
LoadingButton
>
<
LoadingButton
type=
"button"
disabled=
{
!
isCheckedAgreement
}
// onClick={handleClickUploadSsp}
// loading={uploadDn.isLoading}
variant=
"contained"
sx=
{
{
background
:
'
#143B88
'
}
}
>
Save and Upload
</
LoadingButton
>
</
Stack
>
</
Suspense
>
</
FormProvider
>
</
form
>
</
Grid
>
<
Grid
size=
{
{
xs
:
isOpenPanduan
?
4
:
1
}
}
>
<
PanduanNrRekam
handleOpen=
{
handleOpenPanduan
}
isOpen=
{
isOpenPanduan
}
/>
</
Grid
>
</
Grid
>
</
DashboardContent
>
</>
);
};
export
default
NrRekamView
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment