calendar.view.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. import React, { Component } from "react";
  2. import ContentWrapper from "@/components/Layout/ContentWrapper";
  3. import { Card, CardBody, CardHeader, CardTitle, Button, Row, Col, FormGroup, Input } from "reactstrap";
  4. import { getPelaporan, getOneLaporan, updateLaporan } from "@/actions/pelaporan";
  5. import { updateJadwal } from "@/actions/penjadwalan";
  6. import DetailLaporan from "@/components/Penjadwalan/DetailLaporan";
  7. import Link from "next/link";
  8. import FullCalendar from "@fullcalendar/react";
  9. import dayGridPlugin from "@fullcalendar/daygrid";
  10. import timeGridPlugin from "@fullcalendar/timegrid";
  11. import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
  12. import listPlugin from "@fullcalendar/list";
  13. import bootstrapPlugin from "@fullcalendar/bootstrap";
  14. import Select from "react-select";
  15. import moment from "moment-timezone";
  16. import { connect } from "react-redux";
  17. import Loader from "@/components/Common/Loader";
  18. import Router from "next/router";
  19. import { ToastContainer, toast } from "react-toastify";
  20. import { Formik, Form, Field, ErrorMessage } from "formik";
  21. import * as Yup from "yup";
  22. import Datetime from "react-datetime";
  23. import 'moment/locale/id';
  24. import { getCsrf } from "../../actions/security";
  25. import { createLog } from "../../actions/log";
  26. moment.locale('id')
  27. import Swal from "sweetalert2";
  28. const checkIfFilesAreTooBig = (files) => {
  29. let valid = true;
  30. if (files) {
  31. files.map((file) => {
  32. if (file.size > 15 * 1024 * 1024) {
  33. valid = false;
  34. }
  35. });
  36. }
  37. return valid;
  38. };
  39. const status = [
  40. { value: "Ditindaklanjuti DIKTI", label: "Ditindaklanjuti DIKTI", className: "State-ACT" },
  41. { value: "Delegasi ke LLDIKTI", label: "Delegasi ke LLDIKTI", className: "State-ACT" },
  42. { value: "Ditutup", label: "Ditutup", className: "State-ACT" },
  43. ];
  44. const statusLLDIKTI = [
  45. { value: "Ditindaklanjuti LLDIKTI", label: "Ditindaklanjuti LLDIKTI", className: "State-ACT" },
  46. { value: "Delegasi ke DIKTI", label: "Delegasi ke DIKTI", className: "State-ACT" },
  47. { value: "Ditutup", label: "Ditutup", className: "State-ACT" },
  48. ];
  49. const jadwalSchema = Yup.object().shape({
  50. judul: Yup.string().required("Wajib diisi"),
  51. dari_tanggal: Yup.date().required("Wajib diisi"),
  52. sampai_tanggal: Yup.date().required("Wajib diisi"),
  53. });
  54. const laporanSchema = Yup.object().shape({
  55. keterangan: Yup.string().required("Harus diisi"),
  56. dokumen: Yup.array().min(1).test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
  57. });
  58. const ditutupSchema = Yup.object().shape({
  59. keterangan: Yup.string().required("Harus diisi"),
  60. dokumen: Yup.array().min(1).required("Wajib diisi").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
  61. });
  62. let Dropzone = null;
  63. class DropzoneWrapper extends Component {
  64. state = {
  65. isClient: false,
  66. };
  67. componentDidMount = () => {
  68. Dropzone = require("react-dropzone").default;
  69. this.setState({ isClient: true });
  70. };
  71. render() {
  72. return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
  73. }
  74. }
  75. class Calendar extends Component {
  76. calendarPlugins = [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, bootstrapPlugin];
  77. calendarHeader = {
  78. left: "prev,next today",
  79. center: "title",
  80. right: "dayGridMonth,timeGridWeek,timeGridDay",
  81. };
  82. constructor(props) {
  83. super(props);
  84. this.state = {
  85. selectedEvent: null,
  86. evRemoveOnDrop: true,
  87. evNewName: "",
  88. externalEvents: [],
  89. dataLaporan: [],
  90. dataEvent: [],
  91. laporan: {},
  92. selectedOption: null,
  93. color: "",
  94. disabled: true,
  95. files: [],
  96. };
  97. }
  98. onDrop = (selectedFile) => {
  99. this.setState({
  100. selectedFile: selectedFile.map((file) =>
  101. Object.assign(file, {
  102. preview: URL.createObjectURL(file),
  103. })
  104. ),
  105. stat: "Added " + selectedFile.length + " file(s)",
  106. });
  107. const selectFile = this.state.selectedFile
  108. this.setState(prevState => ({
  109. files: [...prevState.files, ...selectFile]
  110. }))
  111. };
  112. uploadFiles = (e) => {
  113. e.preventDefault();
  114. e.stopPropagation();
  115. this.setState({
  116. stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
  117. });
  118. };
  119. clearFiles = (e) => {
  120. e.preventDefault();
  121. e.stopPropagation();
  122. this.setState({
  123. stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
  124. });
  125. this.setState({
  126. files: [],
  127. });
  128. };
  129. static getInitialProps = ({ query }) => ({ query });
  130. async componentDidMount() {
  131. const { token, query } = this.props;
  132. const laporan = await getOneLaporan(token, query.id);
  133. const dataLaporan = await getPelaporan(token, { jadwal: true });
  134. this.setState({ dataLaporan });
  135. this.getDataEvent();
  136. this.defaultStatus();
  137. this.setState({ laporan });
  138. let color = "#" + Math.random().toString(16).slice(2, 8);
  139. if (laporan.data.jadwal) {
  140. color = laporan.data.jadwal.warna;
  141. }
  142. this.setState({ color });
  143. }
  144. componentShouldUpdate(nextProps, nextState) {
  145. return nextState.dataLaporan !== this.state.dataLaporan;
  146. }
  147. getStatus = () => (this.props.user?.role.id === 2021 ? statusLLDIKTI : status);
  148. getDataEvent = () => {
  149. const dataEvent = this.state.dataLaporan.data.map((e) => ({
  150. id: e._id,
  151. title: e.jadwal.judul,
  152. start: moment(e.jadwal.dari_tanggal).format("YYYY-MM-DD"),
  153. end: moment(e.jadwal.sampai_tanggal).add(1, "d").format("YYYY-MM-DD"),
  154. backgroundColor: e.jadwal.warna,
  155. borderColor: e.jadwal.warna,
  156. }));
  157. this.setState({ dataEvent });
  158. };
  159. eventClick = (info) => {
  160. const data = {
  161. title: info.event.title,
  162. start: moment(info.event.start).format("DD MMMM YYYY"),
  163. end: moment(info.event.end - 1).format("DD MMMM YYYY"),
  164. };
  165. this.setState({ selectedEvent: data });
  166. };
  167. handleEventReceive = (info) => {
  168. var styles = getComputedStyle(info.draggedEl);
  169. info.event.setProp("backgroundColor", styles.backgroundColor);
  170. info.event.setProp("borderColor", styles.borderColor);
  171. this.handleEventCalendar(info);
  172. };
  173. logSuccessUpdateJadwal = async () => {
  174. const { query, token } = this.props;
  175. const { id } = query;
  176. const getToken = await getCsrf();
  177. const _csrf = getToken.token;
  178. await createLog(token, { aktivitas: `Berhasil menetapkan jadwal, id: ${id}`, _csrf: _csrf });
  179. }
  180. handleEventCalendar = async (data) => {
  181. if (this.props.user.role.id === 2024) {
  182. Swal.fire({
  183. icon: 'error',
  184. title: 'Oops...',
  185. html: 'Maaf anda tidak memiliki akses untuk menyelesaikan<p> proses ini.</p>',
  186. confirmButtonColor: "#3e3a8e",
  187. confirmButtonText: 'Oke'
  188. })
  189. } else {
  190. const { query, token } = this.props;
  191. const { id } = query;
  192. const { color, laporan } = this.state;
  193. const getToken = await getCsrf();
  194. const _csrf = getToken.token;
  195. await toast.promise(
  196. updateJadwal(token, id, {
  197. judul: "No.Laporan " + laporan.data.no_laporan + " - " + data.judul,
  198. dari_tanggal: data.dari_tanggal,
  199. sampai_tanggal: data.sampai_tanggal,
  200. warna: color,
  201. }, _csrf),
  202. {
  203. pending: "Loading",
  204. success: "Success",
  205. error: "Error",
  206. }
  207. );
  208. Router.push("/app/penjadwalan");
  209. const dataLaporan = await getPelaporan(token, { jadwal: true });
  210. this.setState({ dataLaporan });
  211. this.getDataEvent();
  212. }
  213. };
  214. defaultStatus = async () => {
  215. const status = this.getStatus();
  216. return this.setState({ selectedOption: status[0] });
  217. };
  218. handleChangeSelect = (selectedOption) => this.setState({ selectedOption });
  219. logUpdateLaporanSuccess = async () => {
  220. const getToken = await getCsrf();
  221. const _csrf = getToken.token;
  222. const { token, query } = this.props;
  223. const { id } = query;
  224. await createLog(token, { aktivitas: `Berhasil mengubah data laporan, id: ${id}`, _csrf: _csrf });
  225. }
  226. logUpdateLaporanError = async () => {
  227. const getToken = await getCsrf();
  228. const _csrf = getToken.token;
  229. const { token, query } = this.props;
  230. const { id } = query;
  231. await createLog(token, { aktivitas: `Berhasil mengubah data laporan, id: ${id}`, _csrf: _csrf });
  232. }
  233. handleSimpan = async (value) => {
  234. if (this.props.user.role.id === 2024) {
  235. Swal.fire({
  236. icon: 'error',
  237. title: 'Oops...',
  238. html: 'Maaf anda tidak memiliki akses untuk menyelesaikan<p> proses ini.</p>',
  239. confirmButtonColor: "#3e3a8e",
  240. confirmButtonText: 'Oke'
  241. })
  242. } else {
  243. const getToken = await getCsrf();
  244. const _csrf = getToken.token;
  245. const { token, query } = this.props;
  246. const { id } = query;
  247. let update = null;
  248. if (value.status.value === this.getStatus()[1].value || value.status.value === this.getStatus()[2].value) {
  249. const toastid = toast.loading("Please wait...");
  250. const data = { keterangan: value.keterangan };
  251. const formdata = new FormData();
  252. formdata.append("keterangan", value.keterangan);
  253. this.state.files.forEach((e) => {
  254. formdata.append("dokumen", e);
  255. });
  256. formdata.append("aktif", "false");
  257. if (value.status.value === this.getStatus()[1].value) {
  258. data.change_role = "true";
  259. update = await updateLaporan(token, id, data, _csrf);
  260. Router.push("/app/penjadwalan");
  261. } else if (value.status.value === this.getStatus()[2].value) {
  262. update = await updateLaporan(token, id, formdata, _csrf + `&redudansi=true`);
  263. Router.push("/app/penjadwalan");
  264. }
  265. if (!update) {
  266. toast.update(toastid, { render: "Gagal simpan jadwal", type: "error", isLoading: false, autoClose: true, closeButton: true });
  267. this.logUpdateLaporanError()
  268. } else {
  269. toast.update(toastid, { render: "Input jadwal berhasil", type: "success", isLoading: false, autoClose: true, closeButton: true });
  270. this.logUpdateLaporanSuccess()
  271. Router.push("/app/penjadwalan");
  272. }
  273. Router.push("/app/penjadwalan");
  274. }
  275. Router.push("/app/penjadwalan");
  276. }
  277. };
  278. render() {
  279. const { files, externalEvents, laporan, selectedOption, selectedEvent } = this.state;
  280. const removeFile = file => () => {
  281. const newFiles = [...files]
  282. newFiles.splice(newFiles.indexOf(file), 1)
  283. this.setState({
  284. files: newFiles,
  285. });
  286. }
  287. const thumbs = files.map((file, index) => (
  288. <p>
  289. <em className="far fa-file" />&nbsp;&nbsp;{file.name}
  290. <button className="bg-transparent button-transparent border-0 fas fa-trash text-danger float-right" onClick={removeFile(file)} />
  291. </p>
  292. ));
  293. return (
  294. <ContentWrapper>
  295. <div className="content-heading">
  296. <span className="font-color-white">
  297. Membuat Jadwal Pemeriksaan
  298. </span>
  299. <div className="ml-auto">
  300. <Link href="/app/penjadwalan">
  301. <Button className="color-3e3a8e" color>
  302. <span className="font-color-white">
  303. &lt; Kembali
  304. </span>
  305. </Button>
  306. </Link>
  307. </div>
  308. </div>
  309. <div className="calendar-app">
  310. {laporan.data ? (
  311. <Row>
  312. <Col>
  313. <Card className="card-default">
  314. <CardBody>
  315. <DetailLaporan noStatus data={laporan.data} role={this.props.user.role.id} />
  316. </CardBody>
  317. </Card>
  318. </Col>
  319. </Row>
  320. ) : (
  321. <Row className="mb-4">
  322. <Col>
  323. <Loader />
  324. </Col>
  325. </Row>
  326. )}
  327. <div className="row">
  328. <div className="col-xl-4 col-lg-5">
  329. <div className="row">
  330. <div className="col-lg-12 col-md-6 col-12">
  331. <Card className="card-default">
  332. <div class="header-penjadwalan">
  333. <h2 class="card-title-1">Status Pelaporan</h2>
  334. </div>
  335. <CardBody>
  336. <Formik
  337. enableReinitialize={true}
  338. initialValues={{
  339. status: this.getStatus()[0],
  340. keterangan: "",
  341. }}
  342. validationSchema={selectedOption?.value === this.getStatus()[2].value || selectedOption?.value === this.getStatus()[1].value ? laporanSchema : null}
  343. onSubmit={this.handleSimpan}
  344. >
  345. {({ isSubmitting }) => (
  346. <Form>
  347. <FormGroup>
  348. <label className="col-form-label">Status Laporan</label>
  349. <Field name="status">
  350. {({ field, form }) => (
  351. <Select
  352. value={field.value}
  353. onChange={(e) => {
  354. form.setFieldValue(field.name, e);
  355. this.handleChangeSelect(e);
  356. }}
  357. options={this.getStatus()}
  358. required
  359. isDisabled={laporan.data?.sanksi}
  360. />
  361. )}
  362. </Field>
  363. <ErrorMessage name="status" component="div" className="form-text text-danger" />
  364. </FormGroup>
  365. {selectedOption?.value === this.getStatus()[0].value ? (
  366. ""
  367. ) : (
  368. selectedOption?.value === this.getStatus()[1].value ? (
  369. <FormGroup>
  370. <label className="col-form-label">Keterangan<span className=" text-danger">*</span></label>
  371. <Field name="keterangan">{({ field, form }) => <Input type="text" placeholder="Keterangan" {...field} />}</Field>
  372. <ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
  373. </FormGroup>
  374. ) : (
  375. <div>
  376. <FormGroup >
  377. <label className="col-form-label">Keterangan<span className=" text-danger">*</span></label>
  378. <Field name="keterangan">{({ field, form }) => <Input type="text" placeholder="Keterangan" {...field} />}</Field>
  379. <ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
  380. </FormGroup>
  381. <FormGroup >
  382. <label className=" col-form-label">Upload File Pendukung<span className="text-danger">*</span></label>
  383. <div >
  384. <Field name="dokumen">
  385. {({ field, form, meta }) => (
  386. <DropzoneWrapper
  387. className=""
  388. onDrop={(e) => {
  389. this.onDrop(e);
  390. form.setFieldValue(field.name, e);
  391. }}
  392. >
  393. {({ getRootProps, getInputProps, isDragActive }) => {
  394. return (
  395. <div {...getRootProps()} className={"dropzone card" + (isDragActive ? "dropzone-drag-active" : "")}>
  396. <input name="dokumen" {...getInputProps()} />
  397. <div className="dropzone-style-1">
  398. <div className="center-ver-hor dropzone-previews flex">{this.state.files.length > 0 ?
  399. <div className="text-center fa-2x icon-cloud-upload mr-2 ">
  400. <h5 className="text-center dz-default dz-message">Klik untuk tambah file</h5>
  401. </div> :
  402. <div className="text-center fa-2x icon-cloud-upload mr-2 ">
  403. <h5 className="text-center dz-default dz-message">Klik untuk upload dokumen</h5>
  404. </div>
  405. }
  406. </div>
  407. </div>
  408. <div className="d-flex align-items-center">
  409. <small className="ml-auto">
  410. <button
  411. type="button"
  412. className="btn btn-link"
  413. onClick={(e) => {
  414. this.clearFiles(e);
  415. form.setFieldValue(field.name, []);
  416. }}
  417. >
  418. Reset dokumen
  419. </button>
  420. </small>
  421. </div>
  422. </div>
  423. );
  424. }}
  425. </DropzoneWrapper>
  426. )}
  427. </Field>
  428. {thumbs}
  429. <ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
  430. <p className="mrgn-top-5 font-color-black">
  431. Ukuran setiap dokumen maksimal 15mb
  432. </p>
  433. </div>
  434. </FormGroup>
  435. </div>
  436. )
  437. )}
  438. <div className="btn-simpanjadwal">
  439. <Button className="text-btn-penjadwalan-1 btn-colorpenjadwalan color-3e3a8e" color block disabled={laporan.data?.sanksi || isSubmitting} >
  440. <h4 className="text-btn-penjadwalan-1 font-color-white">Simpan</h4>
  441. </Button>
  442. </div>
  443. </Form>
  444. )}
  445. </Formik>
  446. </CardBody>
  447. </Card>
  448. {selectedOption?.value === this.getStatus()[2].value || selectedOption?.value === this.getStatus()[1].value ? (
  449. ""
  450. ) : (
  451. <>
  452. <Card className="card-default" title="">
  453. <div class="header-penjadwalan">
  454. <h2 className="card-title-1">Input Jadwal</h2>
  455. </div>
  456. <CardBody>
  457. <Formik
  458. enableReinitialize={true}
  459. initialValues={{
  460. judul: laporan.data?.jadwal?.judul ? laporan.data.jadwal.judul.split("- ")[1] : "",
  461. dari_tanggal: laporan.data?.jadwal?.dari_tanggal ? moment(laporan.data.jadwal.dari_tanggal).format("DD MMMM YYYY") : "",
  462. sampai_tanggal: laporan.data?.jadwal?.sampai_tanggal ? moment(laporan.data.jadwal.sampai_tanggal).format("DD MMMM YYYY") : "",
  463. }}
  464. validationSchema={jadwalSchema}
  465. onSubmit={this.handleEventCalendar}
  466. >
  467. {({ isSubmitting }) => (
  468. <Form>
  469. <FormGroup>
  470. <label className="col-form-label">Warna</label>
  471. <div className="badge d-block" style={{ backgroundColor: this.state.color, color: "white" }}>
  472. {this.state.color}
  473. </div>
  474. {/* <div className="warna-penjadwalan-block" style={{ backgroundColor: this.state.color, color: "white" }}></div> */}
  475. </FormGroup>
  476. <FormGroup>
  477. <label className="col-form-label">Judul<span className=" text-danger">*</span></label>
  478. <Field name="judul">{({ field, form }) => <Input disabled={laporan.data?.sanksi} type="text" placeholder="judul" {...field} />}</Field>
  479. <ErrorMessage name="judul" component="div" className="form-text text-danger" />
  480. </FormGroup>
  481. <Row>
  482. <Col>
  483. <FormGroup>
  484. <label className="col-form-label">Dari Tanggal</label>
  485. <Field name="dari_tanggal">
  486. {({ field, form }) => (
  487. <Datetime
  488. timeFormat={false}
  489. locale="id"
  490. inputProps={{ className: "form-control" }}
  491. value={field.value}
  492. dateFormat="DD MMMM YYYY"
  493. closeOnSelect={true}
  494. onChange={(e) => {
  495. form.setFieldValue(field.name, e);
  496. }}
  497. />
  498. )}
  499. </Field>
  500. <ErrorMessage name="dari_tanggal" component="div" className="form-text text-danger" />
  501. </FormGroup>
  502. </Col>
  503. <Col>
  504. <FormGroup>
  505. <label className="col-form-label">Sampai Tanggal</label>
  506. <Field name="sampai_tanggal">
  507. {({ field, form }) => (
  508. <Datetime
  509. timeFormat={false}
  510. locale="id"
  511. dateFormat="DD MMMM YYYY"
  512. inputProps={{ className: "form-control" }}
  513. value={field.value}
  514. closeOnSelect={true}
  515. onChange={(e) => {
  516. form.setFieldValue(field.name, e);
  517. }}
  518. />
  519. )}
  520. </Field>
  521. <ErrorMessage name="sampai_tanggal" component="div" className="form-text text-danger" />
  522. </FormGroup>
  523. </Col>
  524. </Row>
  525. <FormGroup>
  526. <div className="btn-simpanpenjadwalan">
  527. <Button className="btn-colorpenjadwalan" color block type="submit" disabled={laporan.data?.sanksi || isSubmitting}>
  528. <h4 className="text-btn-penjadwalan-1 font-color-white">Simpan</h4>
  529. </Button>
  530. </div>
  531. </FormGroup>
  532. </Form>
  533. )}
  534. </Formik>
  535. </CardBody>
  536. </Card>
  537. </>
  538. )}
  539. <div className="mb-3">
  540. {selectedEvent && (
  541. <div>
  542. <p>Selected:</p>
  543. <div className="box-placeholder">{JSON.stringify(selectedEvent)}</div>
  544. </div>
  545. )}
  546. {!selectedEvent && (
  547. <div>
  548. <p>Click calendar to show information</p>
  549. </div>
  550. )}
  551. </div>
  552. </div>
  553. </div>
  554. </div>
  555. <div className="col-xl-8 col-lg-7">
  556. <Card className="card-default">
  557. <CardBody>
  558. {/* START calendar */}
  559. <FullCalendar
  560. defaultView={this.dayGridMonth}
  561. plugins={this.calendarPlugins}
  562. events={this.state.dataEvent}
  563. themeSystem={"bootstrap"}
  564. header={this.calendarHeader}
  565. deepChangeDetection={true}
  566. eventClick={this.eventClick}
  567. ></FullCalendar>
  568. </CardBody>
  569. </Card>
  570. </div>
  571. </div>
  572. </div>
  573. </ContentWrapper >
  574. );
  575. }
  576. }
  577. const mapStateToProps = (state) => ({ user: state.user, token: state.token });
  578. export default connect(mapStateToProps)(Calendar);