detail.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. import React, { Component } from "react";
  2. import Router from "next/router";
  3. import Link from "next/link";
  4. import Select from "react-select";
  5. import DetailSanksi from "@/components/Main/DetailSanksi";
  6. import Header from "@/components/Main/Header";
  7. import DetailPT from "@/components/Main/DetailPT";
  8. import PermohonanPT from "@/components/Main/PermohonanPT";
  9. import Riwayat from "@/components/Keberatan/Riwayat";
  10. import { getOneSanksi } from "@/actions/sanksi";
  11. import { addJawabanKeberatan } from "@/actions/keberatan";
  12. import ContentWrapper from "@/components/Layout/ContentWrapper";
  13. import { Row, Col, Card, CardBody, FormGroup, Input, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
  14. import { getPT } from "@/actions/PT";
  15. import Loader from "@/components/Common/Loader";
  16. import { toast } from "react-toastify";
  17. import { connect } from "react-redux";
  18. import { Formik, Form, Field, ErrorMessage } from "formik";
  19. import * as Yup from "yup";
  20. import { createLog } from "@/actions/log";
  21. import DatePicker from "react-datepicker";
  22. import "react-datepicker/dist/react-datepicker.css";
  23. import id from 'date-fns/locale/id';
  24. import moment from "moment";
  25. import Datetime from "react-datetime";
  26. import { getCsrf } from "../../../actions/security";
  27. import swal from "sweetalert2";
  28. let Dropzone = null;
  29. class DropzoneWrapper extends Component {
  30. state = {
  31. isClient: false,
  32. };
  33. componentDidMount = () => {
  34. Dropzone = require("react-dropzone").default;
  35. this.setState({ isClient: true });
  36. };
  37. render() {
  38. return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
  39. }
  40. }
  41. const selectInstanceId = 1;
  42. const checkIfFilesAreTooBig = (files) => {
  43. let valid = true;
  44. if (files) {
  45. files.map((file) => {
  46. if (file.size > 15 * 1024 * 1024) {
  47. valid = false;
  48. }
  49. });
  50. }
  51. return valid;
  52. };
  53. const checkIfFilesAreCorrectType = (files) => {
  54. let valid = true;
  55. if (files) {
  56. files.map((file) => {
  57. if (!["image/jpeg", "image/png"].includes(file.type)) {
  58. valid = false;
  59. }
  60. });
  61. }
  62. return valid;
  63. };
  64. const jawabanKeberatanSchema = Yup.object().shape({
  65. status: Yup.string().required("Harap Diisi"),
  66. keterangan: Yup.string().min(3).max(200).required("Harap Diisi"),
  67. dokumen: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
  68. dokumen_terima_keberatan: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
  69. tanggal_terima_keberatan: Yup.string().notRequired("Wajib diisi"),
  70. no_keberatan: Yup.string().min(3).notRequired("Harap Diisi"),
  71. tanggal_akhir_banding: Yup.string().notRequired("Wajib diisi"),
  72. tanggal_surat_keberatan: Yup.string().notRequired("Wajib diisi"),
  73. });
  74. class DetailKeberatan extends Component {
  75. constructor(props) {
  76. super(props);
  77. this.state = {
  78. modal: false,
  79. selectedOption: null,
  80. files: [],
  81. fileTTSKeberatan: [],
  82. no_keberatan: "",
  83. tglTerimaKeberatan: "",
  84. tglAkhirBanding: "",
  85. keterangan: "",
  86. sanksi: {},
  87. pt: null,
  88. data: {},
  89. tglSuratKeberatan: "",
  90. selectedFile: {},
  91. selectFileTTSKeberatan: {},
  92. };
  93. }
  94. static getInitialProps = async ({ query }) => {
  95. return { query };
  96. };
  97. componentDidMount = async () => {
  98. const { query, token } = this.props;
  99. const sanksi = await getOneSanksi(token, query.id);
  100. const getTokenCsrf = await getCsrf();
  101. const _csrf = getTokenCsrf.token;
  102. await createLog(token, { aktivitas: `Mengakses halaman detail Keberatan dengan No. Sanksi ${sanksi.data.no_sanksi}`, menu: "Keberatan", _csrf: _csrf });
  103. const pt = sanksi.data.laporan.pt;
  104. this.setState({ sanksi, pt });
  105. };
  106. toggleModal = () => {
  107. this.setState({
  108. modal: !this.state.modal,
  109. });
  110. };
  111. handleChangeSelect = (selectedOption) => {
  112. this.setState({ selectedOption });
  113. };
  114. onDrop = (selectedFile) => {
  115. this.setState({
  116. selectedFile: selectedFile.map((file) =>
  117. Object.assign(file, {
  118. preview: URL.createObjectURL(file),
  119. })
  120. ),
  121. stat: "Added " + selectedFile.length + " file(s)",
  122. });
  123. const selectFile = this.state.selectedFile
  124. this.setState(prevState => ({
  125. files: [...prevState.files, ...selectFile]
  126. }))
  127. };
  128. onDropTTSKeberatan = (selectFileTTSKeberatan) => {
  129. this.setState({
  130. selectFileTTSKeberatan: selectFileTTSKeberatan.map((file) =>
  131. Object.assign(file, {
  132. preview: URL.createObjectURL(file),
  133. })
  134. ),
  135. stat: "Added " + selectFileTTSKeberatan.length + " file(s)",
  136. });
  137. const selectFile = this.state.selectFileTTSKeberatan
  138. this.setState(prevState => ({
  139. fileTTSKeberatan: [...prevState.fileTTSKeberatan, ...selectFile]
  140. }))
  141. };
  142. uploadFiles = (e) => {
  143. e.preventDefault();
  144. e.stopPropagation();
  145. this.setState({
  146. stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
  147. });
  148. };
  149. clearFiles = (e) => {
  150. e.preventDefault();
  151. e.stopPropagation();
  152. this.setState({
  153. stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
  154. });
  155. this.setState({
  156. files: [],
  157. });
  158. };
  159. clearFilesTTSKeberatan = (e) => {
  160. e.preventDefault();
  161. e.stopPropagation();
  162. this.setState({
  163. stat: this.state.fileTTSKeberatan.length ? this.state.fileTTSKeberatan.length + " file(s) cleared." : "No files to clear.",
  164. });
  165. this.setState({
  166. fileTTSKeberatan: [],
  167. });
  168. };
  169. handelSimpan = async () => {
  170. if (this.props.user.role.id === 2024) {
  171. swal.fire({
  172. icon: 'error',
  173. title: 'Oops...',
  174. html: 'Maaf anda tidak memiliki akses untuk menyelesaikan<p> proses ini.</p>',
  175. confirmButtonColor: "#3e3a8e",
  176. confirmButtonText: 'Oke'
  177. // footer: '<a href="">Why do I have this issue?</a>'
  178. })
  179. } else {
  180. const getTokenCsrf = await getCsrf();
  181. const _csrf = getTokenCsrf.token;
  182. if (this.state.modal === true) {
  183. this.toggleModal();
  184. }
  185. const { data } = this.state;
  186. const { query, token } = this.props;
  187. const { id } = query;
  188. const formdata = new FormData();
  189. formdata.append("keterangan", data.keterangan);
  190. formdata.append("status", data.status);
  191. formdata.append("no_keberatan", data.no_keberatan);
  192. formdata.append("tanggal_terima_keberatan", data.tanggal_terima_keberatan);
  193. formdata.append("tanggal_akhir_banding", data.tanggal_akhir_banding);
  194. formdata.append("tanggal_surat_keberatan", data.tanggal_surat_keberatan);
  195. data.dokumen.forEach((e) => {
  196. formdata.append("dokumen", e);
  197. });
  198. data.dokumen_terima_keberatan.forEach((e) => {
  199. formdata.append("dokumen_terima_keberatan", e);
  200. });
  201. const toastid = toast.loading("Please wait...");
  202. const added = await addJawabanKeberatan(token, id, formdata, _csrf);
  203. if (!added) {
  204. toast.update(toastid, { render: "Gagal Menjawab Keberatan", type: "error", isLoading: false, autoClose: true, closeButton: true });
  205. } else {
  206. toast.update(toastid, { render: "Berhasil Menjawab Keberatan", type: "success", isLoading: false, autoClose: true, closeButton: true });
  207. Router.push({
  208. pathname: "/app/keberatan",
  209. });
  210. }
  211. }
  212. };
  213. setTglTerimaKeberatan = (tglTerimaKeberatan) => {
  214. this.setState({ tglTerimaKeberatan })
  215. }
  216. setTglSuratKeberatan = (tglSuratKeberatan) => {
  217. this.setState({ tglSuratKeberatan })
  218. }
  219. render() {
  220. const { files, sanksi, pt, fileTTSKeberatan, tglTerimaKeberatan, tglSuratKeberatan } = this.state;
  221. const thumbs = files.map((file, index) => (
  222. <div md={3} key={index}>
  223. {/* <img className="img-fluid mb-2" src={file.preview} alt="Item" /> */}
  224. <span className="text-left">{index + 1}.{file.name}</span>
  225. </div>
  226. ));
  227. const thumbsTTSKeberatan = fileTTSKeberatan.map((file, index) => (
  228. <div md={3} key={index}>
  229. {/* <img className="img-fluid mb-2" src={file.preview} alt="Item" /> */}
  230. <span className="text-left">{index + 1}.{file.name}</span>
  231. </div>
  232. ));
  233. return (
  234. <ContentWrapper unwrap>
  235. {/* <Header /> */}
  236. <div className="p-3">
  237. <div className="content-heading">
  238. <span className="font-color-white">Permohonan Keberatan</span>
  239. <div className="ml-auto">
  240. <Link href="/app/keberatan">
  241. <Button className="color-3e3a8e btn-login" color>
  242. <span className="font-color-white">&lt; Kembali</span>
  243. </Button>
  244. </Link>
  245. </div>
  246. </div>
  247. <Row>
  248. {sanksi.data ? (
  249. <Col xl="9">
  250. <Card className="card-default">
  251. <CardBody>
  252. <Row>
  253. <Col lg={12}>
  254. <DetailSanksi data={sanksi.data} role={this.props.user.role.id} />
  255. </Col>
  256. </Row>
  257. <Row>
  258. <Col lg={12}>
  259. <PermohonanPT data={sanksi.data.pengajuan.keberatan} title="Permohonan Keberatan" role={this.props.user.role.id} />
  260. </Col>
  261. </Row>
  262. <Row>
  263. <Col lg={12}>
  264. <p className="lead bb">Jawaban</p>
  265. <Formik
  266. initialValues={{
  267. status: "",
  268. keterangan: "",
  269. dokumen: [],
  270. tanggal_terima_keberatan: "",
  271. no_keberatan: "",
  272. dokumen_terima_keberatan: [],
  273. tanggal_akhir_banding: "",
  274. tanggal_surat_keberatan: ""
  275. }}
  276. validationSchema={jawabanKeberatanSchema}
  277. onSubmit={async (data) => {
  278. this.setState({ data });
  279. if (sanksi.data.jawaban?.keberatan) this.toggleModal();
  280. else await this.handelSimpan();
  281. }}
  282. >
  283. {({ isSubmitting }) => (
  284. <Form className="form-horizontal">
  285. <FormGroup>
  286. <label className="row-form-label">Status :<span className="text-danger">*</span></label>
  287. <div className="row-md-10">
  288. <Field name="status">
  289. {({ field, form, meta }) => (
  290. <Select
  291. instanceId={selectInstanceId + 1}
  292. value={this.state.selectedOption}
  293. onChange={(e) => {
  294. this.handleChangeSelect(e);
  295. form.setFieldValue(field.name, e.value);
  296. }}
  297. options={[
  298. { value: "Menolak", label: "Menolak", className: "State-ACT" },
  299. { value: "Mengubah Keputusan", label: "Mengubah Keputusan", className: "State-ACT" },
  300. { value: "Membatalkan Keputusan", label: "Membatalkan Keputusan", className: "State-ACT" },
  301. ]}
  302. />
  303. )}
  304. </Field>
  305. <ErrorMessage name="status" component="div" className="form-text text-danger" />
  306. </div>
  307. </FormGroup>
  308. <FormGroup>
  309. <label className="row-form-label">Keterangan :<span className="text-danger">*</span></label>
  310. <div className="row-md-10">
  311. <Field name="keterangan">{({ field }) => <Input type="textarea" {...field} />}</Field>
  312. <ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
  313. {/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
  314. </div>
  315. </FormGroup>
  316. <FormGroup>
  317. <label className="row-form-label">Nomor Surat Jawaban Atas Permohonan Keberatan :<span className="text-danger">*</span></label>
  318. <div className="row-md-10">
  319. <Field name="no_keberatan">{({ field }) => <Input type="text" {...field} />}</Field>
  320. <ErrorMessage name="no_keberatan" component="div" className="form-text text-danger" />
  321. </div>
  322. </FormGroup>
  323. <FormGroup>
  324. <label className="row-form-label">Tanggal Surat Jawaban Atas Permohonan Keberatan :<span className="text-danger">*</span></label>
  325. <div className="row-md-10">
  326. <Field name="tanggal_surat_keberatan">
  327. {({ field, form }) => (
  328. <DatePicker
  329. selected={this.state.tglSuratKeberatan || field.value}
  330. onChange={(e) => {
  331. form.setFieldValue(field.name, e);
  332. this.setTglSuratKeberatan(e)
  333. }}
  334. dateFormat="dd/MM/yyyy"
  335. placeholderText="Isi Tanggal"
  336. locale={id}
  337. className="form-control bg-white"
  338. />
  339. )}
  340. </Field>
  341. <ErrorMessage name="tanggal_surat_keberatan" component="div" className="form-text text-danger" />
  342. </div>
  343. </FormGroup>
  344. <FormGroup>
  345. <label className="row-form-label">Dokumen Jawaban Atas Permohonan Keberatan :<span className="text-danger">*</span></label>
  346. <div className="row-md-10">
  347. <Field name="dokumen">
  348. {({ field, form }) => (
  349. <DropzoneWrapper
  350. className=""
  351. onDrop={(e) => {
  352. this.onDrop(e);
  353. form.setFieldValue(field.name, e);
  354. }}
  355. >
  356. {({ getRootProps, getInputProps, isDragActive }) => {
  357. return (
  358. <div {...getRootProps()} className={"dropzone card" + (isDragActive ? "dropzone-drag-active" : "")}>
  359. <input {...getInputProps()} />
  360. <div className="dropzone-previews flex">
  361. <div className="dropzone-style-1">
  362. <div className="center-ver-hor dropzone-previews flex">{this.state.files.length > 0 ? <Row><span className="text-left">{thumbs}</span></Row> :
  363. <div className="text-center fa-2x icon-cloud-upload mr-2 ">
  364. <h5 className="text-center dz-default dz-message">Klik untuk upload dokumen</h5>
  365. </div>
  366. }
  367. </div>
  368. </div>
  369. </div>
  370. <div className="d-flex align-items-center">
  371. <small className="ml-auto">
  372. <button
  373. type="button"
  374. className="btn btn-link"
  375. onClick={(e) => {
  376. this.clearFiles(e);
  377. form.setFieldValue(field.name, []);
  378. }}
  379. >
  380. Reset dokumen
  381. </button>
  382. </small>
  383. </div>
  384. </div>
  385. );
  386. }}
  387. </DropzoneWrapper>
  388. )}
  389. </Field>
  390. <ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
  391. <p className="mrgn-top-5">Ukuran setiap dokumen maksimal 15mb</p>
  392. </div>
  393. </FormGroup>
  394. <FormGroup>
  395. <label className="row-form-label">Tanggal Tanda Terima Surat Jawaban Atas Permohonan Keberatan :<span className="text-danger">*</span></label>
  396. <div className="row-md-10">
  397. <Field name="tanggal_terima_keberatan">
  398. {({ field, form }) => (
  399. <DatePicker
  400. selected={this.state.tglTerimaKeberatan || field.value}
  401. onChange={(e) => {
  402. form.setFieldValue(field.name, e);
  403. this.setTglTerimaKeberatan(e)
  404. }}
  405. dateFormat="dd/MM/yyyy"
  406. minDate={tglSuratKeberatan}
  407. placeholderText="Isi Tanggal"
  408. locale={id}
  409. className="form-control bg-white"
  410. />
  411. )}
  412. </Field>
  413. <ErrorMessage name="tanggal_terima_keberatan" component="div" className="form-text text-danger" />
  414. </div>
  415. </FormGroup>
  416. <FormGroup>
  417. <label className="row-form-label">Dokumen Tanda Terima Surat Jawaban Atas Permohonan Keberatan :<span className="text-danger">*</span></label>
  418. <div className="row-md-10">
  419. <Field name="dokumen_terima_keberatan">
  420. {({ field, form }) => (
  421. <DropzoneWrapper
  422. className=""
  423. onDrop={(e) => {
  424. this.onDropTTSKeberatan(e);
  425. form.setFieldValue(field.name, e);
  426. }}
  427. >
  428. {({ getRootProps, getInputProps, isDragActive }) => {
  429. return (
  430. <div {...getRootProps()} className={"dropzone card" + (isDragActive ? "dropzone-drag-active" : "")}>
  431. <input {...getInputProps()} />
  432. <div className="dropzone-previews flex">
  433. <div className="dropzone-style-1">
  434. <div className="center-ver-hor dropzone-previews flex">{this.state.fileTTSKeberatan.length > 0 ? <Row><span className="text-left">{thumbsTTSKeberatan}</span></Row> :
  435. <div className="text-center fa-2x icon-cloud-upload mr-2 ">
  436. <h5 className="text-center dz-default dz-message">Klik untuk upload dokumen</h5>
  437. </div>
  438. }
  439. </div>
  440. </div>
  441. </div>
  442. <div className="d-flex align-items-center">
  443. <small className="ml-auto">
  444. <button
  445. type="button"
  446. className="btn btn-link"
  447. onClick={(e) => {
  448. this.clearFilesTTSKeberatan(e);
  449. form.setFieldValue(field.name, []);
  450. }}
  451. >
  452. Reset dokumen
  453. </button>
  454. </small>
  455. </div>
  456. </div>
  457. );
  458. }}
  459. </DropzoneWrapper>
  460. )}
  461. </Field>
  462. <ErrorMessage name="dokumen_terima_keberatan" component="div" className="form-text text-danger" />
  463. <p className="mrgn-top-5">Ukuran setiap dokumen maksimal 15mb</p>
  464. </div>
  465. </FormGroup>
  466. <FormGroup>
  467. <label className="row-form-label">Tanggal Akhir Pengajuan Banding :<span className="text-danger">*</span> </label>
  468. <p>Note : 21 hari kerja</p>
  469. <div className="row-md-10">
  470. <Field name="tanggal_akhir_banding">
  471. {({ field, form }) => (
  472. <DatePicker
  473. selected={this.state.tglAkhirBanding || field.value}
  474. onChange={(e) => {
  475. form.setFieldValue(field.name, e);
  476. }}
  477. minDate={tglTerimaKeberatan}
  478. dateFormat="dd/MM/yyyy"
  479. placeholderText="Isi Tanggal"
  480. locale={id}
  481. className="form-control bg-white"
  482. />
  483. )}
  484. </Field>
  485. <ErrorMessage name="tanggal_akhir_banding" component="div" className="form-text text-danger" />
  486. </div>
  487. </FormGroup>
  488. <FormGroup row>
  489. <div className="col-xl-10">
  490. <Button color className="color-3e3a8e btn-login" type="submit" disabled={isSubmitting}>
  491. <span className="font-color-white">Simpan</span>
  492. </Button>
  493. </div>
  494. </FormGroup>
  495. </Form>
  496. )}
  497. </Formik>
  498. </Col>
  499. </Row>
  500. </CardBody>
  501. </Card>
  502. </Col>
  503. ) : (
  504. <Loader />
  505. )}
  506. <Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
  507. </Row>
  508. {sanksi.data && (
  509. <Row>
  510. <Col>
  511. <Riwayat data={sanksi.data.jawaban?.keberatan ? sanksi.data.jawaban.keberatan : null} role={this.props.user.role.id} />
  512. </Col>
  513. </Row>
  514. )}
  515. </div>
  516. <Modal isOpen={this.state.modal} toggle={this.toggleModal}>
  517. <ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
  518. <ModalFooter>
  519. <Button color="primary" onClick={this.handelSimpan}>
  520. Ya
  521. </Button>{" "}
  522. <Button color="secondary" onClick={this.toggleModal}>
  523. Tidak
  524. </Button>
  525. </ModalFooter>
  526. </Modal>
  527. </ContentWrapper>
  528. );
  529. }
  530. }
  531. const mapStateToProps = (state) => ({ user: state.user, token: state.token });
  532. export default connect(mapStateToProps)(DetailKeberatan);