Эх сурвалжийг харах

commit issue 1,2,3,5,7,8,9,11,12

andifebri 3 жил өмнө
parent
commit
e0c9974684
100 өөрчлөгдсөн 7554 нэмэгдсэн , 7503 устгасан
  1. 1 1
      Jenkinsfile
  2. 48 48
      components/Banding/Riwayat.js
  3. 65 65
      components/Banding/TableSanksi.js
  4. 75 75
      components/Charts/Flot.js
  5. 43 43
      components/Charts/Morris.js
  6. 63 63
      components/Charts/chart-flot.scss
  7. 111 111
      components/Common/CardTool.js
  8. 21 21
      components/Common/Loader.js
  9. 12 12
      components/Common/Message.js
  10. 44 44
      components/Common/Now.js
  11. 10 10
      components/Common/PageLoader.js
  12. 37 37
      components/Common/Scrollable.js
  13. 76 76
      components/Common/Sparklines.js
  14. 36 36
      components/Common/Swal.js
  15. 81 81
      components/Common/ToggleFullscreen.js
  16. 47 47
      components/Common/TooltipWrapper.js
  17. 145 145
      components/Common/Translate.js
  18. 28 28
      components/Common/constants.js
  19. 48 48
      components/DocPerbaikan/Riwayat.js
  20. 52 52
      components/Extras/calendar.events.js
  21. 110 110
      components/Forms/Validator.js
  22. 51 51
      components/Keberatan/Riwayat.js
  23. 65 65
      components/Keberatan/TableSanksi.js
  24. 34 34
      components/Layout/Base.js
  25. 54 54
      components/Layout/BaseHorizontal.js
  26. 11 11
      components/Layout/BasePage.js
  27. 25 25
      components/Layout/ContentWrapper.js
  28. 16 16
      components/Layout/Footer.js
  29. 21 21
      components/Layout/Head.js
  30. 259 259
      components/Layout/HeaderHorizontal.js
  31. 51 51
      components/Layout/HeaderSearch.js
  32. 87 87
      components/Layout/Menu.js
  33. 54 54
      components/Layout/MenuPT.js
  34. 398 398
      components/Layout/Offsidebar.js
  35. 34 34
      components/Layout/SettingsProvider.js
  36. 326 326
      components/Layout/Sidebar.js
  37. 56 56
      components/Layout/SidebarUserBlock.js
  38. 26 26
      components/Layout/ThemesProvider.js
  39. 83 83
      components/Main/CaseProgress.js
  40. 162 162
      components/Main/DetailLaporan.js
  41. 31 31
      components/Main/DetailPT.js
  42. 109 109
      components/Main/DetailSanksi.js
  43. 18 18
      components/Main/Header.js
  44. 1 1
      components/Main/Login.js
  45. 37 37
      components/Main/PermohonanPT.js
  46. 81 81
      components/Main/PublicPage.js
  47. 39 39
      components/Main/RiwayatEvaluasi.js
  48. 82 73
      components/Main/TableLaporan.js
  49. 66 66
      components/Main/TableSanksi.js
  50. 100 100
      components/Main/Timeline.js
  51. 55 55
      components/Maps/VectorMap.js
  52. 45 45
      components/Maps/vector-map.scss
  53. 49 49
      components/PT/CabutSanksi/Riwayat.js
  54. 55 55
      components/PT/CabutSanksi/TableSanksiJawaban.js
  55. 48 48
      components/PT/DocPerbaikan/Riwayat.js
  56. 58 58
      components/PT/JawabanBanding/DetailJawaban.js
  57. 54 54
      components/PT/JawabanBanding/TableSanksiJawaban.js
  58. 49 49
      components/PT/JawabanKeberatan/DetailJawaban.js
  59. 215 215
      components/PT/JawabanKeberatan/ModalPermohonan.js
  60. 46 46
      components/PT/JawabanKeberatan/Riwayat.js
  61. 55 55
      components/PT/JawabanKeberatan/TableSanksiJawaban.js
  62. 5 5
      components/PT/JawabanPencabutanSanksi/DetailJawaban.js
  63. 53 53
      components/PT/JawabanPencabutanSanksi/TableSanksiJawaban.js
  64. 217 217
      components/PT/Keberatan/ModalPermohonan.js
  65. 46 46
      components/PT/Keberatan/Riwayat.js
  66. 46 46
      components/PT/Riwayat.js
  67. 49 49
      components/PT/TableSanksi.js
  68. 53 53
      components/PT/TableSanksiJawaban.js
  69. 71 71
      components/PT/Timeline.js
  70. 4 6
      components/Pelaporan/InputData.js
  71. 242 238
      components/Pemeriksaan/InputEvaluasi.js
  72. 3 4
      components/Pemeriksaan/TableLaporan.js
  73. 42 40
      components/Pemeriksaan/TableRiwayat.js
  74. 51 51
      components/PencabutanSanksi/Riwayat.js
  75. 65 65
      components/PencabutanSanksi/TableSanksi.js
  76. 124 124
      components/Penjadwalan/DetailLaporan.js
  77. 4 5
      components/Penjadwalan/TableLaporan.js
  78. 147 147
      components/Sanksi/Ringkasan.js
  79. 67 67
      components/Sanksi/TableLaporan.js
  80. 91 91
      components/Sanksi/TablePenetapanSanksi.js
  81. 125 125
      components/Sanksi/UploadSurat.js
  82. 63 63
      components/Tables/Datatable.js
  83. 24 24
      pages/404.js
  84. 93 93
      pages/_app.js
  85. 25 25
      pages/_error.js
  86. 2 2
      pages/api/profile.js
  87. 308 308
      pages/app/banding/detail.js
  88. 41 41
      pages/app/banding/index.js
  89. 3 3
      pages/app/index.js
  90. 338 338
      pages/app/keberatan/detail.js
  91. 41 41
      pages/app/keberatan/index.js
  92. 3 1
      pages/app/pelaporan/new.js
  93. 20 4
      pages/app/pelaporan/search.js
  94. 74 74
      pages/app/pemantauan-perbaikan/detail.js
  95. 41 41
      pages/app/pemantauan-perbaikan/index.js
  96. 186 176
      pages/app/pemantauan/index.js
  97. 152 143
      pages/app/pemantauan/timeline.js
  98. 10 7
      pages/app/pemeriksaan/index.js
  99. 325 325
      pages/app/pencabutan-sanksi/detail.js
  100. 41 41
      pages/app/pencabutan-sanksi/index.js

+ 1 - 1
Jenkinsfile

@@ -52,7 +52,7 @@ node {
         docker.withTool("myDocker"){
             docker.withRegistry(registryAddress, registryCredential) {
 
-                env.BUILD_ID = 45
+                env.BUILD_ID = 46
 				def dockerImage = docker.build("ptb-fe:${env.BUILD_ID}")
 
                 /* Push the container to the custom Registry */

+ 48 - 48
components/Banding/Riwayat.js

@@ -1,48 +1,48 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Status</th>
-								<th>Dokumen Jawaban</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>{data.status}</td>
-									<td>
-										{data.dokumen.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Status</th>
+								<th>Dokumen Jawaban</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>{data.status}</td>
+									<td>
+										{data.dokumen.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 65 - 65
components/Banding/TableSanksi.js

@@ -1,65 +1,65 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Datatable options={{ responsive: true }}>
-					<table className="table w-100">
-						<thead>
-							<tr>
-								<th>Nomor Sanksi</th>
-								<th>Keterangan Sanksi</th>
-								<th>Created</th>
-								<th>Status</th>
-								<th></th>
-							</tr>
-						</thead>
-						<tbody>
-							{listData.length
-								? listData.map((data) => {
-										return (
-											<tr key={data._id}>
-												<td>{data.no_sanksi}</td>
-												<td>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<h4 className="m-0">{data.laporan.pt.nama}</h4>
-																<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-												<td>{moment(data.createdAt).fromNow()}</td>
-												<td>{data.jawaban.banding ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
-												<td>
-													<div className="ml-auto">
-														<Link
-															href={{
-																pathname: to,
-																query: { id: data._id },
-															}}
-														>
-															<Button color="primary" size="sm">
-																{linkName}
-															</Button>
-														</Link>
-													</div>
-												</td>
-											</tr>
-										);
-								  })
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body">
+				<Datatable options={{ responsive: true }}>
+					<table className="table w-100" data-order='[3,"asc"]'>
+						<thead>
+							<tr>
+								<th>Nomor Sanksi</th>
+								<th>Keterangan Sanksi</th>
+								<th>Created</th>
+								<th>Status</th>
+								<th></th>
+							</tr>
+						</thead>
+						<tbody>
+							{listData.length
+								? listData.map((data) => {
+									return (
+										<tr key={data._id}>
+											<td>{data.no_sanksi}</td>
+											<td>
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															<h4 className="m-0">{data.laporan.pt.nama}</h4>
+															<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+											<td>{moment(data.createdAt).fromNow()}</td>
+											<td>{data.jawaban.banding ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
+											<td>
+												<div className="ml-auto">
+													<Link
+														href={{
+															pathname: to,
+															query: { id: data._id },
+														}}
+													>
+														<Button color="primary" size="sm">
+															{linkName}
+														</Button>
+													</Link>
+												</div>
+											</td>
+										</tr>
+									);
+								})
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 75 - 75
components/Charts/Flot.js

@@ -1,76 +1,76 @@
-import $ from 'jquery';
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import deepEqual from 'deep-equal';
-
-import './chart-flot.scss';
-
-/**
- * Wrapper component for jquery-flot plugin
- */
-class FlotChart extends Component {
-    static propTypes = {
-        /** data to display */
-        data: PropTypes.array.isRequired,
-        /** flot options object */
-        options: PropTypes.object.isRequired,
-        /** height of the container element */
-        height: PropTypes.string,
-        /** width of the container element */
-        width: PropTypes.string
-    }
-
-    static defaultProps = {
-        height: null,
-        width: '100%'
-    }
-
-    componentDidMount() {
-
-        // Flot Charts
-        require('flot/jquery.flot.js');
-        require('flot/jquery.flot.categories.js');
-        require('flot/jquery.flot.pie.js');
-        require('flot/jquery.flot.resize.js');
-        require('flot/jquery.flot.time.js');
-        require('jquery.flot.spline/jquery.flot.spline.js');
-        require('jquery.flot.tooltip/js/jquery.flot.tooltip.min.js');
-
-        setTimeout(() => {
-            this.drawChart();
-        }, 100);
-    }
-
-    componentDidUpdate(prevProps) {
-        if (!deepEqual(prevProps.data, this.props.data) || !deepEqual(prevProps.options, this.props.options)) {
-            this.drawChart();
-        }
-    }
-
-    componentWillUnmount() {
-        $(this.flotElement).data('plot').shutdown();
-    }
-
-    drawChart(nextProps) {
-        const data = (nextProps && nextProps.data) || this.props.data;
-        const options = (nextProps && nextProps.options) || this.props.options;
-        $.plot(this.flotElement, data, options);
-    }
-
-    setRef = node => {
-        this.flotElement = node;
-    }
-
-    render() {
-        const style = {
-            height: this.props.height,
-            width: this.props.width
-        };
-
-        return (
-            <div ref={this.setRef} style={style} {...this.props}/>
-        );
-    }
-}
-
+import $ from 'jquery';
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import deepEqual from 'deep-equal';
+
+import './chart-flot.scss';
+
+/**
+ * Wrapper component for jquery-flot plugin
+ */
+class FlotChart extends Component {
+    static propTypes = {
+        /** data to display */
+        data: PropTypes.array.isRequired,
+        /** flot options object */
+        options: PropTypes.object.isRequired,
+        /** height of the container element */
+        height: PropTypes.string,
+        /** width of the container element */
+        width: PropTypes.string
+    }
+
+    static defaultProps = {
+        height: null,
+        width: '100%'
+    }
+
+    componentDidMount() {
+
+        // Flot Charts
+        require('flot/jquery.flot.js');
+        require('flot/jquery.flot.categories.js');
+        require('flot/jquery.flot.pie.js');
+        require('flot/jquery.flot.resize.js');
+        require('flot/jquery.flot.time.js');
+        require('jquery.flot.spline/jquery.flot.spline.js');
+        require('jquery.flot.tooltip/js/jquery.flot.tooltip.min.js');
+
+        setTimeout(() => {
+            this.drawChart();
+        }, 100);
+    }
+
+    componentDidUpdate(prevProps) {
+        if (!deepEqual(prevProps.data, this.props.data) || !deepEqual(prevProps.options, this.props.options)) {
+            this.drawChart();
+        }
+    }
+
+    componentWillUnmount() {
+        $(this.flotElement).data('plot').shutdown();
+    }
+
+    drawChart(nextProps) {
+        const data = (nextProps && nextProps.data) || this.props.data;
+        const options = (nextProps && nextProps.options) || this.props.options;
+        $.plot(this.flotElement, data, options);
+    }
+
+    setRef = node => {
+        this.flotElement = node;
+    }
+
+    render() {
+        const style = {
+            height: this.props.height,
+            width: this.props.width
+        };
+
+        return (
+            <div ref={this.setRef} style={style} {...this.props}/>
+        );
+    }
+}
+
 export default FlotChart;

+ 43 - 43
components/Charts/Morris.js

@@ -1,43 +1,43 @@
-/* global Morris */
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-
-/**
- * Wrapper for morris chart plugin
- */
-class MorrisChart extends Component {
-    static propTypes = {
-        /** id of the container element */
-        id: PropTypes.string.isRequired,
-        /** data to display */
-        data: PropTypes.array.isRequired,
-        /** morris option object */
-        options: PropTypes.object.isRequired,
-        /** chart type */
-        type: PropTypes.oneOf(['Line', 'Area', 'Donut', 'Bar']).isRequired
-    };
-
-    componentDidMount() {
-        // Morris.js
-        require('morris.js.so/morris.js');
-        require('morris.js.so/morris.css');
-
-        window.requestAnimationFrame(() => this.drawChart());
-    }
-
-    drawChart() {
-        const element = { element: this.props.id };
-        const data = { data: this.props.data };
-        this.chart = new Morris[this.props.type]({
-            ...element,
-            ...data,
-            ...this.props.options
-        });
-    }
-
-    render() {
-        return <div id={this.props.id} />;
-    }
-}
-
-export default MorrisChart;
+/* global Morris */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ * Wrapper for morris chart plugin
+ */
+class MorrisChart extends Component {
+    static propTypes = {
+        /** id of the container element */
+        id: PropTypes.string.isRequired,
+        /** data to display */
+        data: PropTypes.array.isRequired,
+        /** morris option object */
+        options: PropTypes.object.isRequired,
+        /** chart type */
+        type: PropTypes.oneOf(['Line', 'Area', 'Donut', 'Bar']).isRequired
+    };
+
+    componentDidMount() {
+        // Morris.js
+        require('morris.js.so/morris.js');
+        require('morris.js.so/morris.css');
+
+        window.requestAnimationFrame(() => this.drawChart());
+    }
+
+    drawChart() {
+        const element = { element: this.props.id };
+        const data = { data: this.props.data };
+        this.chart = new Morris[this.props.type]({
+            ...element,
+            ...data,
+            ...this.props.options
+        });
+    }
+
+    render() {
+        return <div id={this.props.id} />;
+    }
+}
+
+export default MorrisChart;

+ 63 - 63
components/Charts/chart-flot.scss

@@ -1,63 +1,63 @@
-/* ========================================================================
-     Component: chart-flot
- ======================================================================== */
-
-
-.flot-chart {
-    display: block;
-    width: 100%;
-    height: 250px;
-    .legend {
-        >table tr td {
-            padding: 3px;
-        }
-        >table tr td:first-child {
-            padding-left: 3px;
-        }
-        >table tr td:last-child {
-            padding-right: 3px;
-        }
-        >table tr+tr td {
-            padding-top: 0;
-        }
-
-        >div:first-child {
-            border-color: rgba(0, 0, 0, .1) !important;
-        }
-
-        .legendColorBox>div,
-        .legendColorBox>div>div {
-            border-radius: 400px;
-        }
-    }
-}
-
-.flot-chart-content {
-    width: 100%;
-    height: 100%;
-}
-
-// Labels for PIE CHARTS
-.flot-pie-label {
-    padding: 3px 5px;
-    font-size: 10px;
-    text-align: center;
-    color: #fff;
-}
-
-.flot-base {
-    max-width: 100% !important;
-}
-
-// Tooltip style
-// --------------------------------------
-#flotTip {
-    position: relative;
-    padding: 5px;
-    font-size: 12px !important;
-    border-radius: 2px !important;
-    border-color: transparent !important;
-    background-color: rgba(0, 0, 0, .75) !important;
-    color: #f1f1f1;
-    z-index: 5;
-}
+/* ========================================================================
+     Component: chart-flot
+ ======================================================================== */
+
+
+.flot-chart {
+    display: block;
+    width: 100%;
+    height: 250px;
+    .legend {
+        >table tr td {
+            padding: 3px;
+        }
+        >table tr td:first-child {
+            padding-left: 3px;
+        }
+        >table tr td:last-child {
+            padding-right: 3px;
+        }
+        >table tr+tr td {
+            padding-top: 0;
+        }
+
+        >div:first-child {
+            border-color: rgba(0, 0, 0, .1) !important;
+        }
+
+        .legendColorBox>div,
+        .legendColorBox>div>div {
+            border-radius: 400px;
+        }
+    }
+}
+
+.flot-chart-content {
+    width: 100%;
+    height: 100%;
+}
+
+// Labels for PIE CHARTS
+.flot-pie-label {
+    padding: 3px 5px;
+    font-size: 10px;
+    text-align: center;
+    color: #fff;
+}
+
+.flot-base {
+    max-width: 100% !important;
+}
+
+// Tooltip style
+// --------------------------------------
+#flotTip {
+    position: relative;
+    padding: 5px;
+    font-size: 12px !important;
+    border-radius: 2px !important;
+    border-color: transparent !important;
+    background-color: rgba(0, 0, 0, .75) !important;
+    color: #f1f1f1;
+    z-index: 5;
+}

+ 111 - 111
components/Common/CardTool.js

@@ -1,111 +1,111 @@
-// Card Tools
-// -----------------------------------
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-
-const checkRequiredProps = (props, propName, componentName) => {
-  if (!props.dismiss && !props.refresh) {
-    return new Error(`One of 'dismiss' or 'refresh' is required by '${componentName}' component.`)
-  }
-}
-
-/**
- * Add action icons to card components to allow
- * refresh data or remove a card element
- */
-class CardTool extends Component {
-
-    static propTypes = {
-        /** show the refreshe icon */
-        refresh: checkRequiredProps,
-        /** show the remove icon */
-        dismiss: checkRequiredProps,
-        /** triggers before card is removed */
-        onRemove: PropTypes.func,
-        /** triggers after card was removed */
-        onRemoved: PropTypes.func,
-        /** triggers when user click on refresh button */
-        onRefresh: PropTypes.func,
-        /** name if the icon class to use as spinner */
-        spinner: PropTypes.string
-    }
-
-    static defaultProps = {
-        refresh: false,
-        dismiss: false,
-        onRemove: () => {},
-        onRemoved: () => {},
-        onRefresh: () => {},
-        spinner: 'standard'
-    }
-
-    /**
-     * Helper function to find the closest
-     * ascending .card element
-     */
-    getCardParent(item) {
-        var el = item.parentElement;
-        while (el && !el.classList.contains('card'))
-            el = el.parentElement
-        return el
-    }
-
-    handleDismiss = e => {
-        // find the first parent card
-        const card = this.getCardParent(this.element);
-
-        const destroyCard = () => {
-            // remove card
-            card.parentNode.removeChild(card);
-            // An event to catch when the card has been removed from DOM
-            this.props.onRemoved();
-        }
-
-        const animate = function(item, cb) {
-            if ('onanimationend' in window) { // animation supported
-                item.addEventListener('animationend', cb.bind(this))
-                item.className += ' animated bounceOut'; // requires animate.css
-            } else cb.call(this) // no animation, just remove
-        }
-
-        const confirmRemove = function() {
-            animate(card, function() {
-                destroyCard();
-            })
-        }
-
-        // Trigger the event and finally remove the element
-        this.props.onRemove(card, confirmRemove);
-
-    }
-
-    handleRefresh = e => {
-        const WHIRL_CLASS = 'whirl';
-        const card = this.getCardParent(this.element);
-
-        const showSpinner = function(card, spinner) {
-            card.classList.add(WHIRL_CLASS);
-            spinner.forEach(function(s) { card.classList.add(s) })
-        }
-
-        // method to clear the spinner when done
-        const done = () => { card.classList.remove(WHIRL_CLASS); }
-        // start showing the spinner
-        showSpinner(card, this.props.spinner.split(' '));
-        // event to remove spinner when refres is done
-        this.props.onRefresh(card, done);
-    }
-
-    setRef = node => this.element = node;
-
-    render() {
-        return (
-            <div ref={this.setRef} className="card-tool float-right">
-                { this.props.refresh && <em onClick={this.handleRefresh} className="fas fa-sync"></em> }
-                { this.props.dismiss && <em onClick={this.handleDismiss} className="fa fa-times"></em> }
-            </div>
-        )
-    }
-}
-
-export default CardTool;
+// Card Tools
+// -----------------------------------
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+const checkRequiredProps = (props, propName, componentName) => {
+  if (!props.dismiss && !props.refresh) {
+    return new Error(`One of 'dismiss' or 'refresh' is required by '${componentName}' component.`)
+  }
+}
+
+/**
+ * Add action icons to card components to allow
+ * refresh data or remove a card element
+ */
+class CardTool extends Component {
+
+    static propTypes = {
+        /** show the refreshe icon */
+        refresh: checkRequiredProps,
+        /** show the remove icon */
+        dismiss: checkRequiredProps,
+        /** triggers before card is removed */
+        onRemove: PropTypes.func,
+        /** triggers after card was removed */
+        onRemoved: PropTypes.func,
+        /** triggers when user click on refresh button */
+        onRefresh: PropTypes.func,
+        /** name if the icon class to use as spinner */
+        spinner: PropTypes.string
+    }
+
+    static defaultProps = {
+        refresh: false,
+        dismiss: false,
+        onRemove: () => {},
+        onRemoved: () => {},
+        onRefresh: () => {},
+        spinner: 'standard'
+    }
+
+    /**
+     * Helper function to find the closest
+     * ascending .card element
+     */
+    getCardParent(item) {
+        var el = item.parentElement;
+        while (el && !el.classList.contains('card'))
+            el = el.parentElement
+        return el
+    }
+
+    handleDismiss = e => {
+        // find the first parent card
+        const card = this.getCardParent(this.element);
+
+        const destroyCard = () => {
+            // remove card
+            card.parentNode.removeChild(card);
+            // An event to catch when the card has been removed from DOM
+            this.props.onRemoved();
+        }
+
+        const animate = function(item, cb) {
+            if ('onanimationend' in window) { // animation supported
+                item.addEventListener('animationend', cb.bind(this))
+                item.className += ' animated bounceOut'; // requires animate.css
+            } else cb.call(this) // no animation, just remove
+        }
+
+        const confirmRemove = function() {
+            animate(card, function() {
+                destroyCard();
+            })
+        }
+
+        // Trigger the event and finally remove the element
+        this.props.onRemove(card, confirmRemove);
+
+    }
+
+    handleRefresh = e => {
+        const WHIRL_CLASS = 'whirl';
+        const card = this.getCardParent(this.element);
+
+        const showSpinner = function(card, spinner) {
+            card.classList.add(WHIRL_CLASS);
+            spinner.forEach(function(s) { card.classList.add(s) })
+        }
+
+        // method to clear the spinner when done
+        const done = () => { card.classList.remove(WHIRL_CLASS); }
+        // start showing the spinner
+        showSpinner(card, this.props.spinner.split(' '));
+        // event to remove spinner when refres is done
+        this.props.onRefresh(card, done);
+    }
+
+    setRef = node => this.element = node;
+
+    render() {
+        return (
+            <div ref={this.setRef} className="card-tool float-right">
+                { this.props.refresh && <em onClick={this.handleRefresh} className="fas fa-sync"></em> }
+                { this.props.dismiss && <em onClick={this.handleDismiss} className="fa fa-times"></em> }
+            </div>
+        )
+    }
+}
+
+export default CardTool;

+ 21 - 21
components/Common/Loader.js

@@ -1,21 +1,21 @@
-import React from "react";
-import { Spinner } from "react-bootstrap";
-
-const Loader = () => {
-	return (
-		<Spinner
-			animation="border"
-			role="status"
-			style={{
-				width: "100px",
-				height: "100px",
-				margin: "auto",
-				display: "block",
-			}}
-		>
-			<span className="sr-only">Loading...</span>
-		</Spinner>
-	);
-};
-
-export default Loader;
+import React from "react";
+import { Spinner } from "react-bootstrap";
+
+const Loader = () => {
+	return (
+		<Spinner
+			animation="border"
+			role="status"
+			style={{
+				width: "100px",
+				height: "100px",
+				margin: "auto",
+				display: "block",
+			}}
+		>
+			<span className="sr-only">Loading...</span>
+		</Spinner>
+	);
+};
+
+export default Loader;

+ 12 - 12
components/Common/Message.js

@@ -1,12 +1,12 @@
-import React from "react";
-import { Alert } from "react-bootstrap";
-
-const Message = ({ variant, children }) => {
-	return <Alert variant={variant}>{children}</Alert>;
-};
-
-Message.defaultProps = {
-	variant: "info",
-};
-
-export default Message;
+import React from "react";
+import { Alert } from "react-bootstrap";
+
+const Message = ({ variant, children }) => {
+	return <Alert variant={variant}>{children}</Alert>;
+};
+
+Message.defaultProps = {
+	variant: "info",
+};
+
+export default Message;

+ 44 - 44
components/Common/Now.js

@@ -1,44 +1,44 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import * as moment from 'moment';
-
-/**
- * Updates every second the content of the element
- * with the current time formmated
- */
-export default class Now extends Component {
-
-    static propTypes = {
-        /** string to format current date */
-        format: PropTypes.string.isRequired
-    }
-
-    state = {
-        currentTime: null,
-        format: ''
-    }
-
-    componentDidMount() {
-        this.updateTime();
-        this.interval = setInterval(this.updateTime, 1000);
-    }
-
-    componentWillUnmount() {
-        if(this.interval)
-            clearInterval(this.interval);
-    }
-
-    updateTime = () => {
-        this.setState({
-            currentTime: moment(new Date()).format(this.props.format)
-        })
-    }
-
-    render() {
-        return (
-            <div {...this.props} style={{display: 'inline-block'}}>
-                {this.state.currentTime}
-            </div>
-        )
-    }
-}
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import * as moment from 'moment';
+
+/**
+ * Updates every second the content of the element
+ * with the current time formmated
+ */
+export default class Now extends Component {
+
+    static propTypes = {
+        /** string to format current date */
+        format: PropTypes.string.isRequired
+    }
+
+    state = {
+        currentTime: null,
+        format: ''
+    }
+
+    componentDidMount() {
+        this.updateTime();
+        this.interval = setInterval(this.updateTime, 1000);
+    }
+
+    componentWillUnmount() {
+        if(this.interval)
+            clearInterval(this.interval);
+    }
+
+    updateTime = () => {
+        this.setState({
+            currentTime: moment(new Date()).format(this.props.format)
+        })
+    }
+
+    render() {
+        return (
+            <div {...this.props} style={{display: 'inline-block'}}>
+                {this.state.currentTime}
+            </div>
+        )
+    }
+}

+ 10 - 10
components/Common/PageLoader.js

@@ -1,11 +1,11 @@
-import React from 'react';
-
-// See more loading icons here:
-// https://fontawesome.com/how-to-use/on-the-web/styling/animating-icons
-const PageLoader = () => (
-    <div className="page-loader">
-        <em className="fas fa-circle-notch fa-spin fa-2x text-muted"></em>
-    </div>
-)
-
+import React from 'react';
+
+// See more loading icons here:
+// https://fontawesome.com/how-to-use/on-the-web/styling/animating-icons
+const PageLoader = () => (
+    <div className="page-loader">
+        <em className="fas fa-circle-notch fa-spin fa-2x text-muted"></em>
+    </div>
+)
+
 export default PageLoader;

+ 37 - 37
components/Common/Scrollable.js

@@ -1,37 +1,37 @@
-// SLIMSCROLL
-// -----------------------------------
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-// Perfect Scrollbar
-import PerfectScrollbar from "react-perfect-scrollbar";
-
-// ensure rails are shown over the rest
-const fixRailsZIndex = ".ps__rail-y, ps__rail-x {z-index: 999999; }";
-
-const Scrollable = (props) => {
-	const scrollStyle = {
-		position: "relative",
-	};
-	if (props.height !== null) {
-		scrollStyle.maxHeight = props.height;
-	}
-	return (
-		<>
-			<style>{fixRailsZIndex}</style>
-			<PerfectScrollbar {...props} style={scrollStyle}>
-				{props.children}
-			</PerfectScrollbar>
-		</>
-	);
-};
-
-Scrollable.propTypes = {
-	/** height of the element */
-	height: PropTypes.string,
-};
-
-Scrollable.defaultProps = {
-	height: "250px",
-};
-
-export default Scrollable;
+// SLIMSCROLL
+// -----------------------------------
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+// Perfect Scrollbar
+import PerfectScrollbar from "react-perfect-scrollbar";
+
+// ensure rails are shown over the rest
+const fixRailsZIndex = ".ps__rail-y, ps__rail-x {z-index: 999999; }";
+
+const Scrollable = (props) => {
+	const scrollStyle = {
+		position: "relative",
+	};
+	if (props.height !== null) {
+		scrollStyle.maxHeight = props.height;
+	}
+	return (
+		<>
+			<style>{fixRailsZIndex}</style>
+			<PerfectScrollbar {...props} style={scrollStyle}>
+				{props.children}
+			</PerfectScrollbar>
+		</>
+	);
+};
+
+Scrollable.propTypes = {
+	/** height of the element */
+	height: PropTypes.string,
+};
+
+Scrollable.defaultProps = {
+	height: "250px",
+};
+
+export default Scrollable;

+ 76 - 76
components/Common/Sparklines.js

@@ -1,77 +1,77 @@
-// SPARKLINE
-// -----------------------------------
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import $ from 'jquery';
-
-const RESIZE_EVENT = 'resize.sparkline';
-
-/**
- * Wrapper for for jquery-sparkline plugin
- */
-export default class Sparkline extends Component {
-
-    static propTypes = {
-        /** sparkline options object */
-        options: PropTypes.object.isRequired,
-        /** tag to use, defaults to div */
-        tag: PropTypes.string,
-        /** values to display, allows array or csv string */
-        values: PropTypes.oneOfType([
-            PropTypes.string.isRequired,
-            PropTypes.array.isRequired
-        ])
-    }
-
-    static defaultProps = {
-        options: {},
-        tag: 'div'
-    }
-
-    state = {
-        values: this.props.values,
-        options: this.props.options
-    }
-
-    normalizeParams() {
-        let { options, values } = this.state;
-
-        options.disableHiddenCheck = true; // allow draw when initially is not visible
-        options.type = options.type || 'bar'; // default chart is bar
-        values = Array.isArray(values) ? values : values.split(','); // support array of csv strings
-
-        this.setState({ options, values });
-    }
-
-    componentDidMount() {
-        this.normalizeParams();
-        // Sparklines
-        require('jquery-sparkline/jquery.sparkline.min.js');
-
-        // init sparkline
-        $(this.element).sparkline(this.state.values, this.state.options);
-
-        // allow responsive
-        if (this.state.options.resize) {
-            $(window).on(RESIZE_EVENT, () => {
-                $(this.element).sparkline(this.state.values, this.state.options);
-            });
-        }
-    }
-
-    componentWillUnmount() {
-        $(window).off(RESIZE_EVENT);
-        $(this.element).sparkline('destroy');
-    }
-
-    setRef = node => {
-        this.element = node;
-    }
-
-    render() {
-        const {tag:Tag} = this.props;
-        return (
-            <Tag ref={this.setRef} {...this.props}></Tag>
-        )
-    }
+// SPARKLINE
+// -----------------------------------
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import $ from 'jquery';
+
+const RESIZE_EVENT = 'resize.sparkline';
+
+/**
+ * Wrapper for for jquery-sparkline plugin
+ */
+export default class Sparkline extends Component {
+
+    static propTypes = {
+        /** sparkline options object */
+        options: PropTypes.object.isRequired,
+        /** tag to use, defaults to div */
+        tag: PropTypes.string,
+        /** values to display, allows array or csv string */
+        values: PropTypes.oneOfType([
+            PropTypes.string.isRequired,
+            PropTypes.array.isRequired
+        ])
+    }
+
+    static defaultProps = {
+        options: {},
+        tag: 'div'
+    }
+
+    state = {
+        values: this.props.values,
+        options: this.props.options
+    }
+
+    normalizeParams() {
+        let { options, values } = this.state;
+
+        options.disableHiddenCheck = true; // allow draw when initially is not visible
+        options.type = options.type || 'bar'; // default chart is bar
+        values = Array.isArray(values) ? values : values.split(','); // support array of csv strings
+
+        this.setState({ options, values });
+    }
+
+    componentDidMount() {
+        this.normalizeParams();
+        // Sparklines
+        require('jquery-sparkline/jquery.sparkline.min.js');
+
+        // init sparkline
+        $(this.element).sparkline(this.state.values, this.state.options);
+
+        // allow responsive
+        if (this.state.options.resize) {
+            $(window).on(RESIZE_EVENT, () => {
+                $(this.element).sparkline(this.state.values, this.state.options);
+            });
+        }
+    }
+
+    componentWillUnmount() {
+        $(window).off(RESIZE_EVENT);
+        $(this.element).sparkline('destroy');
+    }
+
+    setRef = node => {
+        this.element = node;
+    }
+
+    render() {
+        const {tag:Tag} = this.props;
+        return (
+            <Tag ref={this.setRef} {...this.props}></Tag>
+        )
+    }
 }

+ 36 - 36
components/Common/Swal.js

@@ -1,36 +1,36 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-// Sweet Alert
-import swal from 'sweetalert';
-
-/**
- * Wrapper component for sweetalert plugin
- */
-const Swal = props => {
-
-    const handleClick = e => {
-        e.preventDefault();
-        // pass swal reference so is possible to chain popups
-        swal(props.options).then(p => props.callback(p, swal));
-    }
-
-    const { callback, ...rest } = props;
-    return (
-        <div {...rest} onClick={handleClick}>
-            {props.children}
-        </div>
-    )
-}
-
-Swal.propType = {
-    /** swal options object */
-    options: PropTypes.object.isRequired,
-    /** callback function for swal response */
-    callback: PropTypes.func
-}
-
-Swal.defaultProps = {
-    callback: () => {}
-}
-
-export default Swal;
+import React from 'react';
+import PropTypes from 'prop-types';
+// Sweet Alert
+import swal from 'sweetalert';
+
+/**
+ * Wrapper component for sweetalert plugin
+ */
+const Swal = props => {
+
+    const handleClick = e => {
+        e.preventDefault();
+        // pass swal reference so is possible to chain popups
+        swal(props.options).then(p => props.callback(p, swal));
+    }
+
+    const { callback, ...rest } = props;
+    return (
+        <div {...rest} onClick={handleClick}>
+            {props.children}
+        </div>
+    )
+}
+
+Swal.propType = {
+    /** swal options object */
+    options: PropTypes.object.isRequired,
+    /** callback function for swal response */
+    callback: PropTypes.func
+}
+
+Swal.defaultProps = {
+    callback: () => { }
+}
+
+export default Swal;

+ 81 - 81
components/Common/ToggleFullscreen.js

@@ -1,81 +1,81 @@
-// FULLSCREEN
-// -----------------------------------
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import screenfull from 'screenfull';
-
-const FULLSCREEN_ON_ICON = 'fa fa-expand';
-const FULLSCREEN_OFF_ICON = 'fa fa-compress';
-
-/**
- * Wrapper for screenfull plugin
- * Wraps child element and toggles
- * fullscreen mode on click
- */
-export default class ToggleFullscreen extends Component {
-    static propTypes = {
-        /** tag to use, defaults to A */
-        tag: PropTypes.string
-    };
-
-    static defaultProps = {
-        tag: 'a'
-    };
-
-    state = {
-        iconClass: FULLSCREEN_ON_ICON
-    };
-
-    componentDidMount() {
-        this.fsToggler = this.element;
-
-        // Not supported under IE
-        const ua = window.navigator.userAgent;
-        if (ua.indexOf('MSIE ') > 0 || !!ua.match(/Trident.*rv:11\./)) {
-            this.fsToggler.style.display = 'none';
-            return; // and abort
-        }
-
-        this.fsToggler.addEventListener('click', this.handleClisk);
-
-        if (screenfull.raw && screenfull.raw.fullscreenchange)
-            document.addEventListener(screenfull.raw.fullscreenchange, this.toggleFSIcon);
-    }
-
-    handleClisk = e => {
-        e.preventDefault();
-
-        if (screenfull.enabled) {
-            screenfull.toggle();
-
-            // Switch icon indicator
-            this.toggleFSIcon();
-        } else {
-            console.log('Fullscreen not enabled');
-        }
-    };
-
-    toggleFSIcon = () => {
-        this.setState({
-            iconClass: screenfull.isFullscreen ? FULLSCREEN_OFF_ICON : FULLSCREEN_ON_ICON
-        });
-    };
-
-    componentWillUnmount() {
-        this.fsToggler.removeEventListener('click', this.handleClisk);
-        document.removeEventListener(screenfull.raw.fullscreenchange, this.toggleFSIcon);
-    }
-
-    setRef = node => {
-        this.element = node;
-    };
-
-    render() {
-        const { tag: Tag } = this.props;
-        return (
-            <Tag ref={this.setRef} {...this.props}>
-                <em className={this.state.iconClass} />
-            </Tag>
-        );
-    }
-}
+// FULLSCREEN
+// -----------------------------------
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import screenfull from 'screenfull';
+
+const FULLSCREEN_ON_ICON = 'fa fa-expand';
+const FULLSCREEN_OFF_ICON = 'fa fa-compress';
+
+/**
+ * Wrapper for screenfull plugin
+ * Wraps child element and toggles
+ * fullscreen mode on click
+ */
+export default class ToggleFullscreen extends Component {
+    static propTypes = {
+        /** tag to use, defaults to A */
+        tag: PropTypes.string
+    };
+
+    static defaultProps = {
+        tag: 'a'
+    };
+
+    state = {
+        iconClass: FULLSCREEN_ON_ICON
+    };
+
+    componentDidMount() {
+        this.fsToggler = this.element;
+
+        // Not supported under IE
+        const ua = window.navigator.userAgent;
+        if (ua.indexOf('MSIE ') > 0 || !!ua.match(/Trident.*rv:11\./)) {
+            this.fsToggler.style.display = 'none';
+            return; // and abort
+        }
+
+        this.fsToggler.addEventListener('click', this.handleClisk);
+
+        if (screenfull.raw && screenfull.raw.fullscreenchange)
+            document.addEventListener(screenfull.raw.fullscreenchange, this.toggleFSIcon);
+    }
+
+    handleClisk = e => {
+        e.preventDefault();
+
+        if (screenfull.enabled) {
+            screenfull.toggle();
+
+            // Switch icon indicator
+            this.toggleFSIcon();
+        } else {
+            console.log('Fullscreen not enabled');
+        }
+    };
+
+    toggleFSIcon = () => {
+        this.setState({
+            iconClass: screenfull.isFullscreen ? FULLSCREEN_OFF_ICON : FULLSCREEN_ON_ICON
+        });
+    };
+
+    componentWillUnmount() {
+        this.fsToggler.removeEventListener('click', this.handleClisk);
+        document.removeEventListener(screenfull.raw.fullscreenchange, this.toggleFSIcon);
+    }
+
+    setRef = node => {
+        this.element = node;
+    };
+
+    render() {
+        const { tag: Tag } = this.props;
+        return (
+            <Tag ref={this.setRef} {...this.props}>
+                <em className={this.state.iconClass} />
+            </Tag>
+        );
+    }
+}

+ 47 - 47
components/Common/TooltipWrapper.js

@@ -1,47 +1,47 @@
-import React, { Component } from 'react';
-import { Tooltip } from 'reactstrap';
-
-// track id generation
-let idCounter = 0;
-// return unique id number
-const UUID = () => idCounter++;
-// reset to sync client/server rendering
-export const resetUUID = () => idCounter = 0;
-
-/**
- * Wrap an element and assign automatically an ID,
- * creates a handler to show/hide tooltips without
- * the hassle of creating new states and class methods.
- * Support only one child and simple text content.
- */
-
-class TooltipWrapper extends Component {
-    // static propTypes { content: PropTypes.string }
-    state = {
-        _id: 'id4tooltip_' + UUID(),
-        tooltipOpen: false
-    };
-    toggle = e => {
-        this.setState({ tooltipOpen: !this.state.tooltipOpen });
-    };
-    render() {
-        return [
-            <Tooltip
-                {...this.props}
-                isOpen={this.state.tooltipOpen}
-                toggle={this.toggle}
-                target={this.state._id}
-                placement={this.props.placement}
-                key="1"
-            >
-                {this.props.content}
-            </Tooltip>,
-            React.cloneElement(React.Children.only(this.props.children), {
-                id: this.state._id,
-                key: '2'
-            })
-        ];
-    }
-}
-
-export default TooltipWrapper;
+import React, { Component } from 'react';
+import { Tooltip } from 'reactstrap';
+
+// track id generation
+let idCounter = 0;
+// return unique id number
+const UUID = () => idCounter++;
+// reset to sync client/server rendering
+export const resetUUID = () => idCounter = 0;
+
+/**
+ * Wrap an element and assign automatically an ID,
+ * creates a handler to show/hide tooltips without
+ * the hassle of creating new states and class methods.
+ * Support only one child and simple text content.
+ */
+
+class TooltipWrapper extends Component {
+    // static propTypes { content: PropTypes.string }
+    state = {
+        _id: 'id4tooltip_' + UUID(),
+        tooltipOpen: false
+    };
+    toggle = e => {
+        this.setState({ tooltipOpen: !this.state.tooltipOpen });
+    };
+    render() {
+        return [
+            <Tooltip
+                {...this.props}
+                isOpen={this.state.tooltipOpen}
+                toggle={this.toggle}
+                target={this.state._id}
+                placement={this.props.placement}
+                key="1"
+            >
+                {this.props.content}
+            </Tooltip>,
+            React.cloneElement(React.Children.only(this.props.children), {
+                id: this.state._id,
+                key: '2'
+            })
+        ];
+    }
+}
+
+export default TooltipWrapper;

+ 145 - 145
components/Common/Translate.js

@@ -1,145 +1,145 @@
-import React, { useContext, useState } from 'react';
-import PropTypes from 'prop-types';
-
-const DEFAULT_LANGUAGE = 'en';
-const LANGUAGES_PATH = 'static/locales';
-const LANGUAGES_FILE = 'translations.json';
-const VARIABLE_REGEX = /\{([^}]+)\}/g;
-
-export const store = {
-    /* dictionaries */
-};
-
-/**
- * Set a dictionary for a specific language
- * @param  {String} lang language
- * @param  {Object} data dictionary
- * @return {Object}      dictionary added
- */
-export const setDict = (lang, data) => (store[lang] = data);
-
-/**
- * Get a dictionary for specific language
- * @param  {String} lang language
- * @return {Object}      dictionary
- */
-export const getDict = lang => store[lang];
-
-/**
- * Fetch a dictionary for specific language defined in JSON format
- * @param  {Strong} lang language
- * @return {Object}      dictionary loaded
- */
-export const fetchStore = async lang => {
-    if (!store[lang]) {
-        const res = await fetch(`/${LANGUAGES_PATH}/${lang}/${LANGUAGES_FILE}`);
-        store[lang] = await res.json();
-    }
-    return store[lang];
-};
-
-/**
- * Interpolates values given in 'params'
- * @param  {String} str    text
- * @param  {Object} params object to interpolate
- * @return {String}
- */
-const compile = (str = '', params) => {
-    const matches = str.match(VARIABLE_REGEX);
-    if (matches) {
-        matches
-            .map(v => v.replace(/\{|\}/g, ''))
-            .forEach(v => (str = str.replace('{' + v + '}', params[v])));
-    }
-    return str;
-};
-
-/**
- * Reads an object value using dot notation for nested keys
- * @param  {Object} obj  object to parse
- * @param  {String} skey key to search
- * @return {String|null}
- */
-export const accessKey = (obj, skey = '') => skey.split('.').reduce((a, b) => a && a[b], obj);
-
-/**
- * Returns the translated text for given key and interpolates values in 'params'
- * @param  {String} key    the key to search for
- * @param  {String} lang   language
- * @param  {Object} params object with params to interpolate
- * @return {[type]}        [description]
- */
-export const translateKey = (key, lang, params = {}) => compile(accessKey(getDict(lang), key));
-
-// =====================
-// REACT INTERFACE
-// =====================
-
-/**
- * Context used to handle translation in the component tree
- */
-const TranslateContext = React.createContext({
-    language: DEFAULT_LANGUAGE,
-    changeLanguage: () => {}
-});
-
-/**
- * Component provider to pass down context to child components
- * Must be used to wrap application
- */
-export class Provider extends React.Component {
-    changeLanguage = language => {
-        fetchStore(language).then(() => {
-            this.setState(state => ({
-                language
-            }));
-        });
-    };
-
-    state = {
-        language: DEFAULT_LANGUAGE,
-        changeLanguage: this.changeLanguage
-    };
-
-    constructor(props) {
-        super(props);
-        if (props.store) {
-            Object.keys(props.store).forEach(l => setDict(l, props.store[l]));
-        }
-    }
-
-    render() {
-        return (
-            <TranslateContext.Provider value={this.state}>
-                {this.props.children}
-            </TranslateContext.Provider>
-        );
-    }
-}
-
-/**
- * HOC to provide 'changeLanguage' and 't' method to WrappedComponent
- */
-export function withTranslation(WrappedComponent) {
-    return function TranslatedComponent(props) {
-        const { language, changeLanguage } = useContext(TranslateContext);
-        const t = (k, l, p) => translateKey(k, l || language, p);
-        return <WrappedComponent changeLanguage={changeLanguage} t={t} {...props} />;
-    };
-}
-
-/**
- * Component to translate a given key
- * If key is missing, render children without changes
- */
-export const Trans = props => {
-    const { language } = useContext(TranslateContext);
-    const { i18nKey, lang, params, children } = props;
-    return [translateKey(i18nKey, lang || language, params) || children];
-};
-
-Trans.proptypes = {
-    i18nKey: PropTypes.string.isRequired,
-    lang: PropTypes.string,
-    params: PropTypes.object
-};
+import React, { useContext, useState } from 'react';
+import PropTypes from 'prop-types';
+
+const DEFAULT_LANGUAGE = 'en';
+const LANGUAGES_PATH = 'static/locales';
+const LANGUAGES_FILE = 'translations.json';
+const VARIABLE_REGEX = /\{([^}]+)\}/g;
+
+export const store = {
+    /* dictionaries */
+};
+
+/**
+ * Set a dictionary for a specific language
+ * @param  {String} lang language
+ * @param  {Object} data dictionary
+ * @return {Object}      dictionary added
+ */
+export const setDict = (lang, data) => (store[lang] = data);
+
+/**
+ * Get a dictionary for specific language
+ * @param  {String} lang language
+ * @return {Object}      dictionary
+ */
+export const getDict = lang => store[lang];
+
+/**
+ * Fetch a dictionary for specific language defined in JSON format
+ * @param  {Strong} lang language
+ * @return {Object}      dictionary loaded
+ */
+export const fetchStore = async lang => {
+    if (!store[lang]) {
+        const res = await fetch(`/${LANGUAGES_PATH}/${lang}/${LANGUAGES_FILE}`);
+        store[lang] = await res.json();
+    }
+    return store[lang];
+};
+
+/**
+ * Interpolates values given in 'params'
+ * @param  {String} str    text
+ * @param  {Object} params object to interpolate
+ * @return {String}
+ */
+const compile = (str = '', params) => {
+    const matches = str.match(VARIABLE_REGEX);
+    if (matches) {
+        matches
+            .map(v => v.replace(/\{|\}/g, ''))
+            .forEach(v => (str = str.replace('{' + v + '}', params[v])));
+    }
+    return str;
+};
+
+/**
+ * Reads an object value using dot notation for nested keys
+ * @param  {Object} obj  object to parse
+ * @param  {String} skey key to search
+ * @return {String|null}
+ */
+export const accessKey = (obj, skey = '') => skey.split('.').reduce((a, b) => a && a[b], obj);
+
+/**
+ * Returns the translated text for given key and interpolates values in 'params'
+ * @param  {String} key    the key to search for
+ * @param  {String} lang   language
+ * @param  {Object} params object with params to interpolate
+ * @return {[type]}        [description]
+ */
+export const translateKey = (key, lang, params = {}) => compile(accessKey(getDict(lang), key));
+
+// =====================
+// REACT INTERFACE
+// =====================
+
+/**
+ * Context used to handle translation in the component tree
+ */
+const TranslateContext = React.createContext({
+    language: DEFAULT_LANGUAGE,
+    changeLanguage: () => {}
+});
+
+/**
+ * Component provider to pass down context to child components
+ * Must be used to wrap application
+ */
+export class Provider extends React.Component {
+    changeLanguage = language => {
+        fetchStore(language).then(() => {
+            this.setState(state => ({
+                language
+            }));
+        });
+    };
+
+    state = {
+        language: DEFAULT_LANGUAGE,
+        changeLanguage: this.changeLanguage
+    };
+
+    constructor(props) {
+        super(props);
+        if (props.store) {
+            Object.keys(props.store).forEach(l => setDict(l, props.store[l]));
+        }
+    }
+
+    render() {
+        return (
+            <TranslateContext.Provider value={this.state}>
+                {this.props.children}
+            </TranslateContext.Provider>
+        );
+    }
+}
+
+/**
+ * HOC to provide 'changeLanguage' and 't' method to WrappedComponent
+ */
+export function withTranslation(WrappedComponent) {
+    return function TranslatedComponent(props) {
+        const { language, changeLanguage } = useContext(TranslateContext);
+        const t = (k, l, p) => translateKey(k, l || language, p);
+        return <WrappedComponent changeLanguage={changeLanguage} t={t} {...props} />;
+    };
+}
+
+/**
+ * Component to translate a given key
+ * If key is missing, render children without changes
+ */
+export const Trans = props => {
+    const { language } = useContext(TranslateContext);
+    const { i18nKey, lang, params, children } = props;
+    return [translateKey(i18nKey, lang || language, params) || children];
+};
+
+Trans.proptypes = {
+    i18nKey: PropTypes.string.isRequired,
+    lang: PropTypes.string,
+    params: PropTypes.object
+};

+ 28 - 28
components/Common/constants.js

@@ -1,28 +1,28 @@
-// GLOBAL CONSTANTS
-// -----------------------------------
-
-export const APP_COLORS = {
-    'primary':                '#5d9cec',
-    'success':                '#27c24c',
-    'info':                   '#23b7e5',
-    'warning':                '#ff902b',
-    'danger':                 '#f05050',
-    'inverse':                '#131e26',
-    'green':                  '#37bc9b',
-    'pink':                   '#f532e5',
-    'purple':                 '#7266ba',
-    'dark':                   '#3a3f51',
-    'yellow':                 '#fad732',
-    'gray-darker':            '#232735',
-    'gray-dark':              '#3a3f51',
-    'gray':                   '#dde6e9',
-    'gray-light':             '#e4eaec',
-    'gray-lighter':           '#edf1f2'
-};
-
-export const APP_MEDIAQUERY = {
-    'desktopLG':             1200,
-    'desktop':                992,
-    'tablet':                 768,
-    'mobile':                 480
-};
+// GLOBAL CONSTANTS
+// -----------------------------------
+
+export const APP_COLORS = {
+    'primary':                '#5d9cec',
+    'success':                '#27c24c',
+    'info':                   '#23b7e5',
+    'warning':                '#ff902b',
+    'danger':                 '#f05050',
+    'inverse':                '#131e26',
+    'green':                  '#37bc9b',
+    'pink':                   '#f532e5',
+    'purple':                 '#7266ba',
+    'dark':                   '#3a3f51',
+    'yellow':                 '#fad732',
+    'gray-darker':            '#232735',
+    'gray-dark':              '#3a3f51',
+    'gray':                   '#dde6e9',
+    'gray-light':             '#e4eaec',
+    'gray-lighter':           '#edf1f2'
+};
+
+export const APP_MEDIAQUERY = {
+    'desktopLG':             1200,
+    'desktop':                992,
+    'tablet':                 768,
+    'mobile':                 480
+};

+ 48 - 48
components/DocPerbaikan/Riwayat.js

@@ -1,48 +1,48 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Keterangan</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data.length
-								? data.map((value) => (
-										<tr>
-											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
-											<td>{value.keterangan}</td>
-											<td>
-												{value.dokumen.map((e) => (
-													<>
-														<em className="fa-lg far fa-file-code"></em>
-														<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-															{e.judul}
-														</a>
-													</>
-												))}
-											</td>
-										</tr>
-								  ))
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Keterangan</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data.length
+								? data.map((value) => (
+										<tr>
+											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
+											<td>{value.keterangan}</td>
+											<td>
+												{value.dokumen.map((e) => (
+													<>
+														<em className="fa-lg far fa-file-code"></em>
+														<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+															{e.judul}
+														</a>
+													</>
+												))}
+											</td>
+										</tr>
+								  ))
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 52 - 52
components/Extras/calendar.events.js

@@ -1,52 +1,52 @@
-// Date for the calendar events (dummy data)
-var date = new Date();
-var d = date.getDate(),
-    m = date.getMonth(),
-    y = date.getFullYear();
-
-export default [
-    {
-        title: 'Jadwal Pemeriksaan - BI:88389',
-        start: new Date(y, m, 1),
-        backgroundColor: '#f56954', //red
-        borderColor: '#f56954' //red
-    },
-    {
-        title: 'Jadwal Pemeriksaan - BI:77589',
-        start: new Date(y, m, d - 5),
-        end: new Date(y, m, d - 2),
-        backgroundColor: '#f39c12', //yellow
-        borderColor: '#f39c12' //yellow
-    },
-    {
-        title: 'Jadwal Pemeriksaan - BI:36458',
-        start: new Date(y, m, d, 10, 30),
-        allDay: false,
-        backgroundColor: '#0073b7', //Blue
-        borderColor: '#0073b7' //Blue
-    },
-    {
-        title: 'Jadwal Pemeriksaan - BI:36KLP',
-        start: new Date(y, m, d, 12, 0),
-        end: new Date(y, m, d, 14, 0),
-        allDay: false,
-        backgroundColor: '#00c0ef', //Info (aqua)
-        borderColor: '#00c0ef' //Info (aqua)
-    },
-    {
-        title: 'Jadwal Pemeriksaan - BI:36589',
-        start: new Date(y, m, d + 1, 19, 0),
-        end: new Date(y, m, d + 1, 22, 30),
-        allDay: false,
-        backgroundColor: '#00a65a', //Success (green)
-        borderColor: '#00a65a' //Success (green)
-    },
-    {
-        title: 'Jadwal Pemeriksaan - BI:56989',
-        start: new Date(y, m, 28),
-        end: new Date(y, m, 29),
-        url: '//google.com/',
-        backgroundColor: '#3c8dbc', //Primary (light-blue)
-        borderColor: '#3c8dbc' //Primary (light-blue)
-    }
-];
+// Date for the calendar events (dummy data)
+var date = new Date();
+var d = date.getDate(),
+    m = date.getMonth(),
+    y = date.getFullYear();
+
+export default [
+    {
+        title: 'Jadwal Pemeriksaan - BI:88389',
+        start: new Date(y, m, 1),
+        backgroundColor: '#f56954', //red
+        borderColor: '#f56954' //red
+    },
+    {
+        title: 'Jadwal Pemeriksaan - BI:77589',
+        start: new Date(y, m, d - 5),
+        end: new Date(y, m, d - 2),
+        backgroundColor: '#f39c12', //yellow
+        borderColor: '#f39c12' //yellow
+    },
+    {
+        title: 'Jadwal Pemeriksaan - BI:36458',
+        start: new Date(y, m, d, 10, 30),
+        allDay: false,
+        backgroundColor: '#0073b7', //Blue
+        borderColor: '#0073b7' //Blue
+    },
+    {
+        title: 'Jadwal Pemeriksaan - BI:36KLP',
+        start: new Date(y, m, d, 12, 0),
+        end: new Date(y, m, d, 14, 0),
+        allDay: false,
+        backgroundColor: '#00c0ef', //Info (aqua)
+        borderColor: '#00c0ef' //Info (aqua)
+    },
+    {
+        title: 'Jadwal Pemeriksaan - BI:36589',
+        start: new Date(y, m, d + 1, 19, 0),
+        end: new Date(y, m, d + 1, 22, 30),
+        allDay: false,
+        backgroundColor: '#00a65a', //Success (green)
+        borderColor: '#00a65a' //Success (green)
+    },
+    {
+        title: 'Jadwal Pemeriksaan - BI:56989',
+        start: new Date(y, m, 28),
+        end: new Date(y, m, 29),
+        url: '//google.com/',
+        backgroundColor: '#3c8dbc', //Primary (light-blue)
+        borderColor: '#3c8dbc' //Primary (light-blue)
+    }
+];

+ 110 - 110
components/Forms/Validator.js

@@ -1,111 +1,111 @@
-// https://github.com/chriso/validator.js
-import validator from 'validator';
-
-/**
- * Helper methods to validate form inputs
- * using controlled components
- */
-const FormValidator = {
-    /**
-     * Validate input element
-     * @param element Dome element of the input
-     * Uses the following attributes
-     *     data-validate: array in json format with validation methods
-     *     data-param: used to provide arguments for certain methods.
-     */
-    validate(element) {
-
-        const isCheckbox = element.type === 'checkbox';
-        const value = isCheckbox ? element.checked : element.value;
-        const name = element.name;
-
-        if (!name) throw new Error('Input name must not be empty.');
-
-        // use getAttribute to support IE10+
-        const param = element.getAttribute('data-param');
-        const validations = JSON.parse(element.getAttribute('data-validate'));
-
-        let result = []
-        if(validations && validations.length) {
-            /*  Result of each validation must be true if the input is invalid
-                and false if valid. */
-            validations.forEach(m => {
-                switch (m) {
-                    case 'required':
-                        result[m] = isCheckbox ? value === false : validator.isEmpty(value)
-                        break;
-                    case 'email':
-                        result[m] = !validator.isEmail(value)
-                        break;
-                    case 'number':
-                        result[m] = !validator.isNumeric(value)
-                        break;
-                    case 'integer':
-                        result[m] = !validator.isInt(value)
-                        break;
-                    case 'alphanum':
-                        result[m] = !validator.isAlphanumeric(value)
-                        break;
-                    case 'url':
-                        result[m] = !validator.isURL(value)
-                        break;
-                    case 'equalto':
-                        // here we expect a valid ID as param
-                        const value2 = document.getElementById(param).value;
-                        result[m] = !validator.equals(value, value2)
-                        break;
-                    case 'minlen':
-                        result[m] = !validator.isLength(value, { min: param })
-                        break;
-                    case 'maxlen':
-                        result[m] = !validator.isLength(value, { max: param })
-                        break;
-                    case 'len':
-                        const [min, max] = JSON.parse(param)
-                        result[m] = !validator.isLength(value, { min, max })
-                        break;
-                    case 'min':
-                        result[m] = !validator.isInt(value, { min: validator.toInt(param) })
-                        break;
-                    case 'max':
-                        result[m] = !validator.isInt(value, { max: validator.toInt(param) })
-                        break;
-                    case 'list':
-                        const list = JSON.parse(param)
-                        result[m] = !validator.isIn(value, list)
-                        break;
-                    default:
-                        throw new Error('Unrecognized validator.');
-                }
-
-            })
-        }
-
-        return result;
-    },
-
-    /**
-     * Bulk validation of input elements.
-     * Used with form elements collection.
-     * @param  {Array} inputs Array for DOM element
-     * @return {Object}       Contains array of error and a flag to
-     *                        indicate if there was a validation error
-     */
-    bulkValidate(inputs) {
-        let errors = {},
-            hasError = false;
-
-        inputs.forEach(input => {
-            let result = this.validate(input)
-            errors = { ...errors, [input.name]: result }
-            if (!hasError) hasError = Object.keys(result).some(val => result[val])
-        })
-
-        return {
-            errors,
-            hasError
-        }
-    }
-}
-
+// https://github.com/chriso/validator.js
+import validator from 'validator';
+
+/**
+ * Helper methods to validate form inputs
+ * using controlled components
+ */
+const FormValidator = {
+    /**
+     * Validate input element
+     * @param element Dome element of the input
+     * Uses the following attributes
+     *     data-validate: array in json format with validation methods
+     *     data-param: used to provide arguments for certain methods.
+     */
+    validate(element) {
+
+        const isCheckbox = element.type === 'checkbox';
+        const value = isCheckbox ? element.checked : element.value;
+        const name = element.name;
+
+        if (!name) throw new Error('Input name must not be empty.');
+
+        // use getAttribute to support IE10+
+        const param = element.getAttribute('data-param');
+        const validations = JSON.parse(element.getAttribute('data-validate'));
+
+        let result = []
+        if(validations && validations.length) {
+            /*  Result of each validation must be true if the input is invalid
+                and false if valid. */
+            validations.forEach(m => {
+                switch (m) {
+                    case 'required':
+                        result[m] = isCheckbox ? value === false : validator.isEmpty(value)
+                        break;
+                    case 'email':
+                        result[m] = !validator.isEmail(value)
+                        break;
+                    case 'number':
+                        result[m] = !validator.isNumeric(value)
+                        break;
+                    case 'integer':
+                        result[m] = !validator.isInt(value)
+                        break;
+                    case 'alphanum':
+                        result[m] = !validator.isAlphanumeric(value)
+                        break;
+                    case 'url':
+                        result[m] = !validator.isURL(value)
+                        break;
+                    case 'equalto':
+                        // here we expect a valid ID as param
+                        const value2 = document.getElementById(param).value;
+                        result[m] = !validator.equals(value, value2)
+                        break;
+                    case 'minlen':
+                        result[m] = !validator.isLength(value, { min: param })
+                        break;
+                    case 'maxlen':
+                        result[m] = !validator.isLength(value, { max: param })
+                        break;
+                    case 'len':
+                        const [min, max] = JSON.parse(param)
+                        result[m] = !validator.isLength(value, { min, max })
+                        break;
+                    case 'min':
+                        result[m] = !validator.isInt(value, { min: validator.toInt(param) })
+                        break;
+                    case 'max':
+                        result[m] = !validator.isInt(value, { max: validator.toInt(param) })
+                        break;
+                    case 'list':
+                        const list = JSON.parse(param)
+                        result[m] = !validator.isIn(value, list)
+                        break;
+                    default:
+                        throw new Error('Unrecognized validator.');
+                }
+
+            })
+        }
+
+        return result;
+    },
+
+    /**
+     * Bulk validation of input elements.
+     * Used with form elements collection.
+     * @param  {Array} inputs Array for DOM element
+     * @return {Object}       Contains array of error and a flag to
+     *                        indicate if there was a validation error
+     */
+    bulkValidate(inputs) {
+        let errors = {},
+            hasError = false;
+
+        inputs.forEach(input => {
+            let result = this.validate(input)
+            errors = { ...errors, [input.name]: result }
+            if (!hasError) hasError = Object.keys(result).some(val => result[val])
+        })
+
+        return {
+            errors,
+            hasError
+        }
+    }
+}
+
 export default FormValidator;

+ 51 - 51
components/Keberatan/Riwayat.js

@@ -1,51 +1,51 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-import { API_URL } from "@/env";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Status</th>
-								<th>Keterangan Jawaban</th>
-								<th>Dokumen Jawaban</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>{data.status}</td>
-									<td>{data.keterangan}</td>
-									<td>
-										{data.dokumen.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+import { API_URL } from "@/env";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Status</th>
+								<th>Keterangan Jawaban</th>
+								<th>Dokumen Jawaban</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>{data.status}</td>
+									<td>{data.keterangan}</td>
+									<td>
+										{data.dokumen.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 65 - 65
components/Keberatan/TableSanksi.js

@@ -1,65 +1,65 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Datatable options={{ responsive: true }}>
-					<table className="table w-100">
-						<thead>
-							<tr>
-								<th>Nomor Sanksi</th>
-								<th>Keterangan Sanksi</th>
-								<th>Created</th>
-								<th>Status</th>
-								<th></th>
-							</tr>
-						</thead>
-						<tbody>
-							{listData.length
-								? listData.map((data) => {
-										return (
-											<tr key={data._id}>
-												<td>{data.no_sanksi}</td>
-												<td>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<h4 className="m-0">{data.laporan.pt.nama}</h4>
-																<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-												<td>{moment(data.createdAt).fromNow()}</td>
-												<td>{data.jawaban?.keberatan ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
-												<td>
-													<div className="ml-auto">
-														<Link
-															href={{
-																pathname: to,
-																query: { id: data._id },
-															}}
-														>
-															<Button color="primary" size="sm">
-																{linkName}
-															</Button>
-														</Link>
-													</div>
-												</td>
-											</tr>
-										);
-								  })
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Datatable options={{ responsive: false }}>
+					<table className="table w-100" data-order='[3,"asc"]'>
+						<thead>
+							<tr>
+								<th>Nomor Sanksi</th>
+								<th>Keterangan Sanksi</th>
+								<th>Created</th>
+								<th>Status</th>
+								<th></th>
+							</tr>
+						</thead>
+						<tbody>
+							{listData.length
+								? listData.map((data) => {
+									return (
+										<tr key={data._id}>
+											<td>{data.no_sanksi}</td>
+											<td>
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															<h4 className="m-0">{data.laporan.pt.nama}</h4>
+															<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+											<td>{moment(data.createdAt).fromNow()}</td>
+											<td>{data.jawaban?.keberatan ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
+											<td>
+												<div className="ml-auto">
+													<Link
+														href={{
+															pathname: to,
+															query: { id: data._id },
+														}}
+													>
+														<Button color="primary" size="sm">
+															{linkName}
+														</Button>
+													</Link>
+												</div>
+											</td>
+										</tr>
+									);
+								})
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 34 - 34
components/Layout/Base.js

@@ -1,34 +1,34 @@
-import React from 'react';
-
-import Head from './Head'
-import Header from './Header'
-import Sidebar from './Sidebar'
-import Offsidebar from './Offsidebar'
-import Footer from './Footer'
-import SettingsProvider from './SettingsProvider'
-import ThemesProvider from './ThemesProvider'
-
-const Base = props => (
-    <ThemesProvider>
-        <SettingsProvider>
-            <div className="wrapper">
-
-                <Head />
-
-                <Header />
-
-                <Sidebar />
-
-                <Offsidebar />
-
-                <section className="section-container">
-                    { props.children }
-                </section>
-
-                <Footer />
-            </div>
-        </SettingsProvider>
-    </ThemesProvider>
-)
-
-export default Base;
+import React from 'react';
+
+import Head from './Head'
+import Header from './Header'
+import Sidebar from './Sidebar'
+import Offsidebar from './Offsidebar'
+import Footer from './Footer'
+import SettingsProvider from './SettingsProvider'
+import ThemesProvider from './ThemesProvider'
+
+const Base = props => (
+    <ThemesProvider>
+        <SettingsProvider>
+            <div className="wrapper">
+
+                <Head />
+
+                <Header />
+
+                <Sidebar />
+
+                <Offsidebar />
+
+                <section className="section-container">
+                    { props.children }
+                </section>
+
+                <Footer />
+            </div>
+        </SettingsProvider>
+    </ThemesProvider>
+)
+
+export default Base;

+ 54 - 54
components/Layout/BaseHorizontal.js

@@ -1,54 +1,54 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import * as actions from '../../store/actions/actions';
-
-import Head from './Head';
-import HeaderHorizontal from './HeaderHorizontal';
-import Offsidebar from './Offsidebar';
-import Footer from './Footer';
-import SettingsProvider from './SettingsProvider';
-import ThemesProvider from './ThemesProvider';
-
-class BaseHorizontal extends Component {
-    /* Toggle Horizontal layout for demo purposes.
-        Set the 'horizontal' flag using redux in the settingsReducer
-        and remove bwloe methods so it gets rendered on the server
-    */
-    componentWillMount = () => this.props.actions.changeSetting('horizontal', true);
-    componentWillUnmount = () => this.props.actions.changeSetting('horizontal', false);
-
-    render() {
-        return (
-            <ThemesProvider>
-                <SettingsProvider>
-                    <div className="wrapper">
-                        <Head />
-
-                        <HeaderHorizontal />
-
-                        <Offsidebar />
-
-                        <section className="section-container">{this.props.children}</section>
-
-                        <Footer />
-                    </div>
-                </SettingsProvider>
-            </ThemesProvider>
-        );
-    }
-}
-
-BaseHorizontal.propTypes = {
-    actions: PropTypes.object,
-    settings: PropTypes.object
-};
-
-const mapStateToProps = state => ({ settings: state.settings });
-const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(actions, dispatch) });
-
-export default connect(
-    mapStateToProps,
-    mapDispatchToProps
-)(BaseHorizontal);
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import * as actions from '../../store/actions/actions';
+
+import Head from './Head';
+import HeaderHorizontal from './HeaderHorizontal';
+import Offsidebar from './Offsidebar';
+import Footer from './Footer';
+import SettingsProvider from './SettingsProvider';
+import ThemesProvider from './ThemesProvider';
+
+class BaseHorizontal extends Component {
+    /* Toggle Horizontal layout for demo purposes.
+        Set the 'horizontal' flag using redux in the settingsReducer
+        and remove bwloe methods so it gets rendered on the server
+    */
+    componentWillMount = () => this.props.actions.changeSetting('horizontal', true);
+    componentWillUnmount = () => this.props.actions.changeSetting('horizontal', false);
+
+    render() {
+        return (
+            <ThemesProvider>
+                <SettingsProvider>
+                    <div className="wrapper">
+                        <Head />
+
+                        <HeaderHorizontal />
+
+                        <Offsidebar />
+
+                        <section className="section-container">{this.props.children}</section>
+
+                        <Footer />
+                    </div>
+                </SettingsProvider>
+            </ThemesProvider>
+        );
+    }
+}
+
+BaseHorizontal.propTypes = {
+    actions: PropTypes.object,
+    settings: PropTypes.object
+};
+
+const mapStateToProps = state => ({ settings: state.settings });
+const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(actions, dispatch) });
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(BaseHorizontal);

+ 11 - 11
components/Layout/BasePage.js

@@ -1,11 +1,11 @@
-import React from 'react';
-import Head from './Head';
-
-const BasePage = props => (
-    <>
-        <Head />
-        <div className="wrapper">{props.children}</div>
-    </>
-);
-
-export default BasePage;
+import React from 'react';
+import Head from './Head';
+
+const BasePage = props => (
+    <>
+        <Head />
+        <div className="wrapper">{props.children}</div>
+    </>
+);
+
+export default BasePage;

+ 25 - 25
components/Layout/ContentWrapper.js

@@ -1,25 +1,25 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-/**
- * Wrapper element for template content
- */
-const ContentWrapper = props =>(
-    <div className="content-wrapper">
-        {props.unwrap ?
-            (<div className="unwrap">{props.children}</div>)
-            :
-            (props.children)
-        }
-    </div>
-)
-
-ContentWrapper.propTypes = {
-    /** add element with 'unwrap' class to expand content area */
-    unwrap: PropTypes.bool
-}
-ContentWrapper.defaultProps = {
-    unwrap: false
-}
-
-export default ContentWrapper;
+import React from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ * Wrapper element for template content
+ */
+const ContentWrapper = props =>(
+    <div className="content-wrapper">
+        {props.unwrap ?
+            (<div className="unwrap">{props.children}</div>)
+            :
+            (props.children)
+        }
+    </div>
+)
+
+ContentWrapper.propTypes = {
+    /** add element with 'unwrap' class to expand content area */
+    unwrap: PropTypes.bool
+}
+ContentWrapper.defaultProps = {
+    unwrap: false
+}
+
+export default ContentWrapper;

+ 16 - 16
components/Layout/Footer.js

@@ -1,16 +1,16 @@
-import React, { Component } from 'react';
-
-class Footer extends Component {
-
-    render() {
-        const year = new Date().getFullYear()
-        return (
-            <footer className="footer-container">
-                <span>Sidali Dikti &copy; {year}</span>
-            </footer>
-        );
-    }
-
-}
-
-export default Footer;
+import React, { Component } from 'react';
+
+class Footer extends Component {
+
+    render() {
+        const year = new Date().getFullYear()
+        return (
+            <footer className="footer-container">
+                <span>Sidali Dikti &copy; {year}</span>
+            </footer>
+        );
+    }
+
+}
+
+export default Footer;

+ 21 - 21
components/Layout/Head.js

@@ -1,21 +1,21 @@
-import React from "react";
-import NextHead from "next/head";
-import PropTypes from "prop-types";
-
-const defaultDescription = "";
-
-const Head = (props) => (
-	<NextHead>
-		<meta charSet="UTF-8" />
-		<title>DiktiRistek</title>
-		<meta name="description" content={props.description || defaultDescription} />
-		<meta name="viewport" content="width=device-width, initial-scale=1" />
-		<link rel="icon" href="/static/img/logo-single.png" />
-	</NextHead>
-);
-
-Head.propTypes = {
-	description: PropTypes.string,
-};
-
-export default Head;
+import React from "react";
+import NextHead from "next/head";
+import PropTypes from "prop-types";
+
+const defaultDescription = "";
+
+const Head = (props) => (
+	<NextHead>
+		<meta charSet="UTF-8" />
+		<title>DiktiRistek</title>
+		<meta name="description" content={props.description || defaultDescription} />
+		<meta name="viewport" content="width=device-width, initial-scale=1" />
+		<link rel="icon" href="/static/img/logo-single.png" />
+	</NextHead>
+);
+
+Head.propTypes = {
+	description: PropTypes.string,
+};
+
+export default Head;

+ 259 - 259
components/Layout/HeaderHorizontal.js

@@ -1,259 +1,259 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import Link from 'next/link';
-import {
-    UncontrolledDropdown,
-    DropdownToggle,
-    DropdownMenu,
-    DropdownItem,
-    ListGroup,
-    ListGroupItem,
-    Nav,
-    Collapse,
-    NavItem,
-    NavLink,
-    NavbarToggler
-} from 'reactstrap';
-
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import * as actions from '../../store/actions/actions';
-
-import ToggleFullscreen from '../Common/ToggleFullscreen';
-import HeaderSearch from './HeaderSearch';
-
-import Menu from './Menu.js';
-
-class HeaderHorizontal extends Component {
-    state = {
-        navSearchOpen: false,
-        isOpen: false
-    };
-
-    toggleNavSearch = e => {
-        e.preventDefault();
-        this.setState({
-            navSearchOpen: !this.state.navSearchOpen
-        });
-    };
-
-    closeNavSearch = e => {
-        e.preventDefault();
-        this.setState({
-            navSearchOpen: false
-        });
-    };
-
-    toggle = () => {
-        this.setState({
-            isOpen: !this.state.isOpen
-        });
-    };
-
-    toggleOffsidebar = e => {
-        e.preventDefault();
-        this.props.actions.toggleSetting('offsidebarOpen');
-    };
-
-    /** map menu config to string to determine which element to render */
-    itemType = item => {
-        if (item.heading) return 'heading';
-        if (!item.submenu) return 'menu';
-        if (item.submenu) return 'submenu';
-    };
-
-    render() {
-        return (
-            <header className="topnavbar-wrapper">
-                {/* START Top Navbar */}
-                <nav className="navbar topnavbar navbar-expand-lg navbar-light">
-                    {/* START navbar header */}
-                    <div className="navbar-header">
-                        <a className="navbar-brand" href="#/">
-                            <div className="brand-logo">
-                                {/* <img
-                                    className="img-fluid"
-                                    src="/static/img/logo-inner.png"
-                                    alt="App Logo"
-                                /> */}
-                            </div>
-                            <div className="brand-logo-collapsed">
-                                <img
-                                    className="img-fluid"
-                                    src="/static/img/logo-single.png"
-                                    alt="App Logo"
-                                />
-                            </div>
-                        </a>
-                        <NavbarToggler onClick={this.toggle} />
-                    </div>
-                    {/* END navbar header */}
-                    {/* START Nav wrapper */}
-                    <Collapse isOpen={this.state.isOpen} navbar>
-                        <Nav navbar className="mr-auto flex-column flex-lg-row">
-                            {Menu.map((item, i) => {
-                                if (this.itemType(item) === 'menu') {
-                                    return (
-                                        <NavItem key={i}>
-                                            <Link href={item.path}>
-                                                <NavLink>{item.name}</NavLink>
-                                            </Link>
-                                        </NavItem>
-                                    );
-                                }
-                                if (this.itemType(item) === 'submenu') {
-                                    return (
-                                        <UncontrolledDropdown nav inNavbar key={i}>
-                                            <DropdownToggle nav>{item.name}</DropdownToggle>
-                                            <DropdownMenu className="animated fadeIn">
-                                                {item.submenu.map((sitem, si) => {
-                                                    return (
-                                                        <Link href={sitem.path} key={si}>
-                                                            <DropdownItem>
-                                                                {sitem.name}
-                                                            </DropdownItem>
-                                                        </Link>
-                                                    );
-                                                })}
-                                            </DropdownMenu>
-                                        </UncontrolledDropdown>
-                                    );
-                                }
-                            })}
-                            {/* END Left navbar */}
-                        </Nav>
-                        <Nav className="flex-row" navbar>
-                            {/* Search icon */}
-                            <NavItem>
-                                <NavLink onClick={this.toggleNavSearch}>
-                                    <em className="icon-magnifier" />
-                                </NavLink>
-                            </NavItem>
-                            {/* Fullscreen (only desktops) */}
-                            <NavItem className="d-none d-md-block">
-                                <ToggleFullscreen className="nav-link" />
-                            </NavItem>
-                            {/* START Alert menu */}
-                            <UncontrolledDropdown nav inNavbar className="dropdown-list">
-                                <DropdownToggle nav className="dropdown-toggle-nocaret">
-                                    <em className="icon-bell" />
-                                    <span className="badge badge-danger">11</span>
-                                </DropdownToggle>
-                                {/* START Dropdown menu */}
-                                <DropdownMenu
-                                    right
-                                    className="dropdown-menu-right animated flipInX"
-                                >
-                                    <DropdownItem>
-                                        {/* START list group */}
-                                        <ListGroup>
-                                            <ListGroupItem
-                                                action
-                                                tag="a"
-                                                href=""
-                                                onClick={e => e.preventDefault()}
-                                            >
-                                                <div className="media">
-                                                    <div className="align-self-start mr-2">
-                                                        <em className="fab fa-twitter fa-2x text-info" />
-                                                    </div>
-                                                    <div className="media-body">
-                                                        <p className="m-0">New followers</p>
-                                                        <p className="m-0 text-muted text-sm">
-                                                            1 new follower
-                                                        </p>
-                                                    </div>
-                                                </div>
-                                            </ListGroupItem>
-                                            <ListGroupItem
-                                                action
-                                                tag="a"
-                                                href=""
-                                                onClick={e => e.preventDefault()}
-                                            >
-                                                <div className="media">
-                                                    <div className="align-self-start mr-2">
-                                                        <em className="fa fa-envelope fa-2x text-warning" />
-                                                    </div>
-                                                    <div className="media-body">
-                                                        <p className="m-0">New e-mails</p>
-                                                        <p className="m-0 text-muted text-sm">
-                                                            You have 10 new emails
-                                                        </p>
-                                                    </div>
-                                                </div>
-                                            </ListGroupItem>
-                                            <ListGroupItem
-                                                action
-                                                tag="a"
-                                                href=""
-                                                onClick={e => e.preventDefault()}
-                                            >
-                                                <div className="media">
-                                                    <div className="align-self-start mr-2">
-                                                        <em className="fa fa-tasks fa-2x text-success" />
-                                                    </div>
-                                                    <div className="media-body">
-                                                        <p className="m-0">Pending Tasks</p>
-                                                        <p className="m-0 text-muted text-sm">
-                                                            11 pending task
-                                                        </p>
-                                                    </div>
-                                                </div>
-                                            </ListGroupItem>
-                                            <ListGroupItem
-                                                action
-                                                tag="a"
-                                                href=""
-                                                onClick={e => e.preventDefault()}
-                                            >
-                                                <span className="d-flex align-items-center">
-                                                    <span className="text-sm">
-                                                        More notifications
-                                                    </span>
-                                                    <span className="badge badge-danger ml-auto">
-                                                        14
-                                                    </span>
-                                                </span>
-                                            </ListGroupItem>
-                                        </ListGroup>
-                                        {/* END list group */}
-                                    </DropdownItem>
-                                </DropdownMenu>
-                                {/* END Dropdown menu */}
-                            </UncontrolledDropdown>
-                            {/* END Alert menu */}
-                            {/* START Offsidebar button */}
-                            <NavItem>
-                                <NavLink href="" onClick={this.toggleOffsidebar}>
-                                    <em className="icon-notebook" />
-                                </NavLink>
-                            </NavItem>
-                            {/* END Offsidebar menu */}
-                        </Nav>
-                    </Collapse>
-                    {/* END Nav wrapper */}
-                    {/* START Search form */}
-                    <HeaderSearch isOpen={this.state.navSearchOpen} onClose={this.closeNavSearch} />
-                    {/* END Search form */}
-                </nav>
-                {/* END Top Navbar */}
-            </header>
-        );
-    }
-}
-
-HeaderHorizontal.propTypes = {
-    actions: PropTypes.object,
-    settings: PropTypes.object
-};
-
-const mapStateToProps = state => ({ settings: state.settings });
-const mapDispatchToProps = dispatch => ({
-    actions: bindActionCreators(actions, dispatch)
-});
-
-export default connect(
-    mapStateToProps,
-    mapDispatchToProps
-)(HeaderHorizontal);
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import Link from 'next/link';
+import {
+    UncontrolledDropdown,
+    DropdownToggle,
+    DropdownMenu,
+    DropdownItem,
+    ListGroup,
+    ListGroupItem,
+    Nav,
+    Collapse,
+    NavItem,
+    NavLink,
+    NavbarToggler
+} from 'reactstrap';
+
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import * as actions from '../../store/actions/actions';
+
+import ToggleFullscreen from '../Common/ToggleFullscreen';
+import HeaderSearch from './HeaderSearch';
+
+import Menu from './Menu.js';
+
+class HeaderHorizontal extends Component {
+    state = {
+        navSearchOpen: false,
+        isOpen: false
+    };
+
+    toggleNavSearch = e => {
+        e.preventDefault();
+        this.setState({
+            navSearchOpen: !this.state.navSearchOpen
+        });
+    };
+
+    closeNavSearch = e => {
+        e.preventDefault();
+        this.setState({
+            navSearchOpen: false
+        });
+    };
+
+    toggle = () => {
+        this.setState({
+            isOpen: !this.state.isOpen
+        });
+    };
+
+    toggleOffsidebar = e => {
+        e.preventDefault();
+        this.props.actions.toggleSetting('offsidebarOpen');
+    };
+
+    /** map menu config to string to determine which element to render */
+    itemType = item => {
+        if (item.heading) return 'heading';
+        if (!item.submenu) return 'menu';
+        if (item.submenu) return 'submenu';
+    };
+
+    render() {
+        return (
+            <header className="topnavbar-wrapper">
+                {/* START Top Navbar */}
+                <nav className="navbar topnavbar navbar-expand-lg navbar-light">
+                    {/* START navbar header */}
+                    <div className="navbar-header">
+                        <a className="navbar-brand" href="#/">
+                            <div className="brand-logo">
+                                {/* <img
+                                    className="img-fluid"
+                                    src="/static/img/logo-inner.png"
+                                    alt="App Logo"
+                                /> */}
+                            </div>
+                            <div className="brand-logo-collapsed">
+                                <img
+                                    className="img-fluid"
+                                    src="/static/img/logo-single.png"
+                                    alt="App Logo"
+                                />
+                            </div>
+                        </a>
+                        <NavbarToggler onClick={this.toggle} />
+                    </div>
+                    {/* END navbar header */}
+                    {/* START Nav wrapper */}
+                    <Collapse isOpen={this.state.isOpen} navbar>
+                        <Nav navbar className="mr-auto flex-column flex-lg-row">
+                            {Menu.map((item, i) => {
+                                if (this.itemType(item) === 'menu') {
+                                    return (
+                                        <NavItem key={i}>
+                                            <Link href={item.path}>
+                                                <NavLink>{item.name}</NavLink>
+                                            </Link>
+                                        </NavItem>
+                                    );
+                                }
+                                if (this.itemType(item) === 'submenu') {
+                                    return (
+                                        <UncontrolledDropdown nav inNavbar key={i}>
+                                            <DropdownToggle nav>{item.name}</DropdownToggle>
+                                            <DropdownMenu className="animated fadeIn">
+                                                {item.submenu.map((sitem, si) => {
+                                                    return (
+                                                        <Link href={sitem.path} key={si}>
+                                                            <DropdownItem>
+                                                                {sitem.name}
+                                                            </DropdownItem>
+                                                        </Link>
+                                                    );
+                                                })}
+                                            </DropdownMenu>
+                                        </UncontrolledDropdown>
+                                    );
+                                }
+                            })}
+                            {/* END Left navbar */}
+                        </Nav>
+                        <Nav className="flex-row" navbar>
+                            {/* Search icon */}
+                            <NavItem>
+                                <NavLink onClick={this.toggleNavSearch}>
+                                    <em className="icon-magnifier" />
+                                </NavLink>
+                            </NavItem>
+                            {/* Fullscreen (only desktops) */}
+                            <NavItem className="d-none d-md-block">
+                                <ToggleFullscreen className="nav-link" />
+                            </NavItem>
+                            {/* START Alert menu */}
+                            <UncontrolledDropdown nav inNavbar className="dropdown-list">
+                                <DropdownToggle nav className="dropdown-toggle-nocaret">
+                                    <em className="icon-bell" />
+                                    <span className="badge badge-danger">11</span>
+                                </DropdownToggle>
+                                {/* START Dropdown menu */}
+                                <DropdownMenu
+                                    right
+                                    className="dropdown-menu-right animated flipInX"
+                                >
+                                    <DropdownItem>
+                                        {/* START list group */}
+                                        <ListGroup>
+                                            <ListGroupItem
+                                                action
+                                                tag="a"
+                                                href=""
+                                                onClick={e => e.preventDefault()}
+                                            >
+                                                <div className="media">
+                                                    <div className="align-self-start mr-2">
+                                                        <em className="fab fa-twitter fa-2x text-info" />
+                                                    </div>
+                                                    <div className="media-body">
+                                                        <p className="m-0">New followers</p>
+                                                        <p className="m-0 text-muted text-sm">
+                                                            1 new follower
+                                                        </p>
+                                                    </div>
+                                                </div>
+                                            </ListGroupItem>
+                                            <ListGroupItem
+                                                action
+                                                tag="a"
+                                                href=""
+                                                onClick={e => e.preventDefault()}
+                                            >
+                                                <div className="media">
+                                                    <div className="align-self-start mr-2">
+                                                        <em className="fa fa-envelope fa-2x text-warning" />
+                                                    </div>
+                                                    <div className="media-body">
+                                                        <p className="m-0">New e-mails</p>
+                                                        <p className="m-0 text-muted text-sm">
+                                                            You have 10 new emails
+                                                        </p>
+                                                    </div>
+                                                </div>
+                                            </ListGroupItem>
+                                            <ListGroupItem
+                                                action
+                                                tag="a"
+                                                href=""
+                                                onClick={e => e.preventDefault()}
+                                            >
+                                                <div className="media">
+                                                    <div className="align-self-start mr-2">
+                                                        <em className="fa fa-tasks fa-2x text-success" />
+                                                    </div>
+                                                    <div className="media-body">
+                                                        <p className="m-0">Pending Tasks</p>
+                                                        <p className="m-0 text-muted text-sm">
+                                                            11 pending task
+                                                        </p>
+                                                    </div>
+                                                </div>
+                                            </ListGroupItem>
+                                            <ListGroupItem
+                                                action
+                                                tag="a"
+                                                href=""
+                                                onClick={e => e.preventDefault()}
+                                            >
+                                                <span className="d-flex align-items-center">
+                                                    <span className="text-sm">
+                                                        More notifications
+                                                    </span>
+                                                    <span className="badge badge-danger ml-auto">
+                                                        14
+                                                    </span>
+                                                </span>
+                                            </ListGroupItem>
+                                        </ListGroup>
+                                        {/* END list group */}
+                                    </DropdownItem>
+                                </DropdownMenu>
+                                {/* END Dropdown menu */}
+                            </UncontrolledDropdown>
+                            {/* END Alert menu */}
+                            {/* START Offsidebar button */}
+                            <NavItem>
+                                <NavLink href="" onClick={this.toggleOffsidebar}>
+                                    <em className="icon-notebook" />
+                                </NavLink>
+                            </NavItem>
+                            {/* END Offsidebar menu */}
+                        </Nav>
+                    </Collapse>
+                    {/* END Nav wrapper */}
+                    {/* START Search form */}
+                    <HeaderSearch isOpen={this.state.navSearchOpen} onClose={this.closeNavSearch} />
+                    {/* END Search form */}
+                </nav>
+                {/* END Top Navbar */}
+            </header>
+        );
+    }
+}
+
+HeaderHorizontal.propTypes = {
+    actions: PropTypes.object,
+    settings: PropTypes.object
+};
+
+const mapStateToProps = state => ({ settings: state.settings });
+const mapDispatchToProps = dispatch => ({
+    actions: bindActionCreators(actions, dispatch)
+});
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(HeaderHorizontal);

+ 51 - 51
components/Layout/HeaderSearch.js

@@ -1,51 +1,51 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-
-class HeaderSearch extends Component {
-    componentDidMount() {
-        document.addEventListener('keydown', this.closeNavSearchKey);
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener('keydown', this.closeNavSearchKey);
-    }
-
-    setInputSearch = isOpen => input => {
-        if (input) input[isOpen ? 'focus' : 'blur']();
-    };
-
-    closeNavSearchKey = e => {
-        if (e.keyCode === 27) this.props.onClose(e);
-    };
-
-    render() {
-        const { isOpen, onClose } = this.props;
-        return (
-            <form
-                className={'navbar-form ' + (isOpen ? 'open' : '')}
-                role="search"
-                action="search.html"
-            >
-                <div className="form-group">
-                    <input
-                        ref={this.setInputSearch(isOpen)}
-                        className="form-control"
-                        type="text"
-                        placeholder="Type and hit enter ..."
-                    />
-                    <div className="fa fa-times navbar-form-close" onClick={onClose} />
-                </div>
-                <button className="d-none" type="submit">
-                    Submit
-                </button>
-            </form>
-        );
-    }
-}
-
-HeaderSearch.propTypes = {
-    isOpen: PropTypes.bool,
-    onClose: PropTypes.func
-};
-
-export default HeaderSearch;
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+class HeaderSearch extends Component {
+    componentDidMount() {
+        document.addEventListener('keydown', this.closeNavSearchKey);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener('keydown', this.closeNavSearchKey);
+    }
+
+    setInputSearch = isOpen => input => {
+        if (input) input[isOpen ? 'focus' : 'blur']();
+    };
+
+    closeNavSearchKey = e => {
+        if (e.keyCode === 27) this.props.onClose(e);
+    };
+
+    render() {
+        const { isOpen, onClose } = this.props;
+        return (
+            <form
+                className={'navbar-form ' + (isOpen ? 'open' : '')}
+                role="search"
+                action="search.html"
+            >
+                <div className="form-group">
+                    <input
+                        ref={this.setInputSearch(isOpen)}
+                        className="form-control"
+                        type="text"
+                        placeholder="Type and hit enter ..."
+                    />
+                    <div className="fa fa-times navbar-form-close" onClick={onClose} />
+                </div>
+                <button className="d-none" type="submit">
+                    Submit
+                </button>
+            </form>
+        );
+    }
+}
+
+HeaderSearch.propTypes = {
+    isOpen: PropTypes.bool,
+    onClose: PropTypes.func
+};
+
+export default HeaderSearch;

+ 87 - 87
components/Layout/Menu.js

@@ -1,87 +1,87 @@
-const Menu = [
-	{
-		heading: "Main Navigation",
-		translate: "sidebar.heading.HEADER",
-	},
-	{
-		name: "Pemantauan",
-		path: "/app/pemantauan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PEMANTAUAN",
-	},
-	{
-		name: "Laporan Delegasi",
-		path: "/app/laporan-delegasi",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.LAPORAN_DELEGASI",
-	},
-	{
-		name: "Pelaporan",
-		path: "/app/pelaporan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PELAPORAN",
-	},
-	// {
-	// 	name: "Pelaporan",
-	// 	icon: "icon-notebook",
-	// 	translate: "sidebar.nav.LAPORAN",
-	// 	submenu: [
-	// 		{
-	// 			name: "List Laporan",
-	// 			path: "/app/pelaporan",
-	// 		},
-	// 		{
-	// 			name: "Laporan ditutup",
-	// 			path: "/app/laporan-ditutup",
-	// 		},
-	// 	],
-	// },
-	{
-		name: "Penjadwalan Evaluasi",
-		path: "/app/penjadwalan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PENJADWALAN",
-	},
-	{
-		name: "Pemeriksaan",
-		path: "/app/pemeriksaan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PEMERIKSAAN",
-	},
-	{
-		name: "Sanksi",
-		path: "/app/sanksi",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.SANKSI",
-	},
-	{
-		heading: "Dikti Ristek/LLDIKTI",
-		translate: "sidebar.heading.DIKTI_RISTEK",
-	},
-	{
-		name: "Keberatan",
-		path: "/app/keberatan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.KEBERATAN",
-	},
-	{
-		name: "Banding",
-		path: "/app/banding",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.BANDING",
-	},
-	{
-		name: "Pencabutan Sanksi",
-		path: "/app/pencabutan-sanksi",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PENCABUTAN_SANKSI",
-	},
-	{
-		name: "Pemantauan Perbaikan",
-		path: "/app/pemantauan-perbaikan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PEMANTAUAN_PERBAIKAN",
-	},
-];
-
-export default Menu;
+const Menu = [
+	{
+		heading: "Main Navigation",
+		translate: "sidebar.heading.HEADER",
+	},
+	{
+		name: "Pemantauan",
+		path: "/app/pemantauan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PEMANTAUAN",
+	},
+	{
+		name: "Laporan Delegasi",
+		path: "/app/laporan-delegasi",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.LAPORAN_DELEGASI",
+	},
+	{
+		name: "Pelaporan",
+		path: "/app/pelaporan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PELAPORAN",
+	},
+	// {
+	// 	name: "Pelaporan",
+	// 	icon: "icon-notebook",
+	// 	translate: "sidebar.nav.LAPORAN",
+	// 	submenu: [
+	// 		{
+	// 			name: "List Laporan",
+	// 			path: "/app/pelaporan",
+	// 		},
+	// 		{
+	// 			name: "Laporan ditutup",
+	// 			path: "/app/laporan-ditutup",
+	// 		},
+	// 	],
+	// },
+	{
+		name: "Penjadwalan Evaluasi",
+		path: "/app/penjadwalan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PENJADWALAN",
+	},
+	{
+		name: "Pemeriksaan",
+		path: "/app/pemeriksaan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PEMERIKSAAN",
+	},
+	{
+		name: "Sanksi",
+		path: "/app/sanksi",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.SANKSI",
+	},
+	{
+		heading: "Dikti Ristek/LLDIKTI",
+		translate: "sidebar.heading.DIKTI_RISTEK",
+	},
+	{
+		name: "Keberatan",
+		path: "/app/keberatan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.KEBERATAN",
+	},
+	{
+		name: "Banding",
+		path: "/app/banding",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.BANDING",
+	},
+	{
+		name: "Pencabutan Sanksi",
+		path: "/app/pencabutan-sanksi",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PENCABUTAN_SANKSI",
+	},
+	{
+		name: "Pemantauan Perbaikan",
+		path: "/app/pemantauan-perbaikan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PEMANTAUAN_PERBAIKAN",
+	},
+];
+
+export default Menu;

+ 54 - 54
components/Layout/MenuPT.js

@@ -1,54 +1,54 @@
-const MenuPT = [
-	{
-		heading: "Main Navigation",
-		translate: "sidebar.heading.HEADER",
-	},
-	{
-		name: "Pemantauan",
-		path: "/pt/pemantauan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PT_PEMANTAUAN",
-	},
-	{
-		name: "Pengajuan Keberatan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PENGAJUAN_KEBERATAN",
-		submenu: [
-			{
-				name: "a. Permohonan Keberatan",
-				path: "/pt/keberatan",
-			},
-			{
-				name: "b. Jawaban keberatan",
-				path: "/pt/jawaban-keberatan",
-			},
-			{
-				name: "c. Jawaban banding",
-				path: "/pt/jawaban-banding",
-			},
-		],
-	},
-	{
-		name: "Pencabutan Sanksi",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PENGAJUAN_KEBERATAN",
-		submenu: [
-			{
-				name: "a. Permohonan",
-				path: "/pt/pencabutan-sanksi",
-			},
-			{
-				name: "b. Jawaban",
-				path: "/pt/jawaban-pencabutan-sanksi",
-			},
-		],
-	},
-	{
-		name: "Dokumen Perbaikan",
-		path: "/pt/dokumen-perbaikan",
-		icon: "icon-notebook",
-		translate: "sidebar.nav.PT_DOKUMEN_PERBAIKAN",
-	},
-];
-
-export default MenuPT;
+const MenuPT = [
+	{
+		heading: "Main Navigation",
+		translate: "sidebar.heading.HEADER",
+	},
+	{
+		name: "Pemantauan",
+		path: "/pt/pemantauan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PT_PEMANTAUAN",
+	},
+	{
+		name: "Pengajuan Keberatan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PENGAJUAN_KEBERATAN",
+		submenu: [
+			{
+				name: "a. Permohonan Keberatan",
+				path: "/pt/keberatan",
+			},
+			{
+				name: "b. Jawaban keberatan",
+				path: "/pt/jawaban-keberatan",
+			},
+			{
+				name: "c. Jawaban banding",
+				path: "/pt/jawaban-banding",
+			},
+		],
+	},
+	{
+		name: "Pencabutan Sanksi",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PENGAJUAN_KEBERATAN",
+		submenu: [
+			{
+				name: "a. Permohonan",
+				path: "/pt/pencabutan-sanksi",
+			},
+			{
+				name: "b. Jawaban",
+				path: "/pt/jawaban-pencabutan-sanksi",
+			},
+		],
+	},
+	{
+		name: "Dokumen Perbaikan",
+		path: "/pt/dokumen-perbaikan",
+		icon: "icon-notebook",
+		translate: "sidebar.nav.PT_DOKUMEN_PERBAIKAN",
+	},
+];
+
+export default MenuPT;

+ 398 - 398
components/Layout/Offsidebar.js

@@ -1,398 +1,398 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-
-import { connect } from "react-redux";
-import { bindActionCreators } from "redux";
-import * as actions from "../../store/actions/actions";
-
-import { TabContent, TabPane, Nav, NavItem, NavLink } from "reactstrap";
-
-class Offsidebar extends Component {
-	state = {
-		activeTab: "settings",
-		offsidebarReady: false,
-	};
-
-	componentDidMount() {
-		// When mounted display the offsidebar
-		window.requestAnimationFrame(() => this.setState({ offsidebarReady: true }));
-	}
-
-	toggle = (tab) => {
-		if (this.state.activeTab !== tab) {
-			this.setState({
-				activeTab: tab,
-			});
-		}
-	};
-
-	handleSettingCheckbox = (event) => {
-		this.props.actions.changeSetting(event.target.name, event.target.checked);
-	};
-
-	handleThemeRadio = (event) => {
-		this.props.actions.changeTheme(event.target.value);
-	};
-
-	render() {
-		return (
-			this.state.offsidebarReady && (
-				<aside className="offsidebar">
-					{/* START Off Sidebar (right) */}
-					<nav>
-						<div>
-							{/* Nav tabs */}
-							<Nav tabs justified>
-								<NavItem>
-									<NavLink
-										className={this.state.activeTab === "settings" ? "active" : ""}
-										onClick={() => {
-											this.toggle("settings");
-										}}
-									>
-										<em className="icon-equalizer fa-lg"></em>
-									</NavLink>
-								</NavItem>
-								{/* <NavItem>
-                                <NavLink className={ this.state.activeTab === 'chat' ? 'active':'' }
-                                    onClick={() => { this.toggle('chat'); }}
-                                >
-                                    <em className="icon-user fa-lg"></em>
-                                </NavLink>
-                            </NavItem> */}
-							</Nav>
-							{/* Tab panes */}
-							<TabContent activeTab={this.state.activeTab}>
-								<TabPane tabId="settings">
-									<h3 className="text-center text-thin mt-4">Settings</h3>
-									<div className="p-2">
-										<h4 className="text-muted text-thin">Themes</h4>
-										<div className="row row-flush mb-2">
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-a"} value="theme-a" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-info"></span>
-															<span className="color bg-info-light"></span>
-														</span>
-														<span className="color bg-white"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-b"} value="theme-b" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-green"></span>
-															<span className="color bg-green-light"></span>
-														</span>
-														<span className="color bg-white"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-c"} value="theme-c" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-purple"></span>
-															<span className="color bg-purple-light"></span>
-														</span>
-														<span className="color bg-white"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-d"} value="theme-d" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-danger"></span>
-															<span className="color bg-danger-light"></span>
-														</span>
-														<span className="color bg-white"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-e"} value="theme-e" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-info-dark"></span>
-															<span className="color bg-info"></span>
-														</span>
-														<span className="color bg-gray-dark"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-f"} value="theme-f" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-green-dark"></span>
-															<span className="color bg-green"></span>
-														</span>
-														<span className="color bg-gray-dark"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-g"} value="theme-g" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-purple-dark"></span>
-															<span className="color bg-purple"></span>
-														</span>
-														<span className="color bg-gray-dark"></span>
-													</label>
-												</div>
-											</div>
-											<div className="col-3 mb-3">
-												<div className="setting-color">
-													<label>
-														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-h"} value="theme-h" onChange={this.handleThemeRadio} />
-														<span className="icon-check"></span>
-														<span className="split">
-															<span className="color bg-danger-dark"></span>
-															<span className="color bg-danger"></span>
-														</span>
-														<span className="color bg-gray-dark"></span>
-													</label>
-												</div>
-											</div>
-										</div>
-									</div>
-									{/* <div className="p-2">
-										<h4 className="text-muted text-thin">Layout</h4>
-										<div className="clearfix">
-											<p className="float-left">Fixed</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-fixed" type="checkbox" name="isFixed" checked={this.props.settings.isFixed} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-										<div className="clearfix">
-											<p className="float-left">Boxed</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-boxed" type="checkbox" name="isBoxed" checked={this.props.settings.isBoxed} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-									</div>
-									<div className="p-2">
-										<h4 className="text-muted text-thin">Aside</h4>
-										<div className="clearfix">
-											<p className="float-left">Collapsed</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-collapsed" type="checkbox" name="isCollapsed" checked={this.props.settings.isCollapsed} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-										<div className="clearfix">
-											<p className="float-left">Collapsed Text</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-collapsed-text" type="checkbox" name="isCollapsedText" checked={this.props.settings.isCollapsedText} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-										<div className="clearfix">
-											<p className="float-left">Float</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-float" type="checkbox" name="isFloat" checked={this.props.settings.isFloat} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-										<div className="clearfix">
-											<p className="float-left">Hover</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-hover" type="checkbox" name="asideHover" checked={this.props.settings.asideHover} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-										<div className="clearfix">
-											<p className="float-left">Show Scrollbar</p>
-											<div className="float-right">
-												<label className="switch">
-													<input id="chk-scrollbar" type="checkbox" name="asideScrollbar" checked={this.props.settings.asideScrollbar} onChange={this.handleSettingCheckbox} />
-													<span></span>
-												</label>
-											</div>
-										</div>
-									</div> */}
-								</TabPane>
-								<TabPane tabId="chat">
-									<h3 className="text-center text-thin mt-4">Connections</h3>
-									<div className="list-group">
-										{/* START list title */}
-										<div className="list-group-item border-0">
-											<small className="text-muted">ONLINE</small>
-										</div>
-										{/* END list title */}
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/05.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Juan Sims</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-success circle-lg"></span>
-												</div>
-											</div>
-										</div>
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/06.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Maureen Jenkins</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-success circle-lg"></span>
-												</div>
-											</div>
-										</div>
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/07.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Billie Dunn</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-danger circle-lg"></span>
-												</div>
-											</div>
-										</div>
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/08.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Tomothy Roberts</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-warning circle-lg"></span>
-												</div>
-											</div>
-										</div>
-										{/* START list title */}
-										<div className="list-group-item border-0">
-											<small className="text-muted">OFFLINE</small>
-										</div>
-										{/* END list title */}
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/09.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Lawrence Robinson</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-warning circle-lg"></span>
-												</div>
-											</div>
-										</div>
-										<div className="list-group-item list-group-item-action border-0">
-											<div className="media">
-												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/10.jpg" alt="User avatar" />
-												<div className="media-body text-truncate">
-													<a href="">
-														<strong>Tyrone Owens</strong>
-													</a>
-													<br />
-													<small className="text-muted">Designeer</small>
-												</div>
-												<div className="ml-auto">
-													<span className="circle bg-warning circle-lg"></span>
-												</div>
-											</div>
-										</div>
-									</div>
-									<div className="px-3 py-4 text-center">
-										{/* Optional link to list more users */}
-										<a className="btn btn-purple btn-sm" href="" title="See more contacts">
-											<strong>Load more..</strong>
-										</a>
-									</div>
-									{/* Extra items */}
-									<div className="px-3 py-2">
-										<p>
-											<small className="text-muted">Tasks completion</small>
-										</p>
-										<div className="progress progress-xs m-0">
-											<div className="progress-bar bg-success" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style={{ width: "80%" }}>
-												<span className="sr-only">80% Complete</span>
-											</div>
-										</div>
-									</div>
-									<div className="px-3 py-2">
-										<p>
-											<small className="text-muted">Upload quota</small>
-										</p>
-										<div className="progress progress-xs m-0">
-											<div className="progress-bar bg-warning" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style={{ width: "40%" }}>
-												<span className="sr-only">40% Complete</span>
-											</div>
-										</div>
-									</div>
-								</TabPane>
-							</TabContent>
-						</div>
-					</nav>
-					{/* END Off Sidebar (right) */}
-				</aside>
-			)
-		);
-	}
-}
-
-Offsidebar.propTypes = {
-	actions: PropTypes.object,
-	settings: PropTypes.object,
-	theme: PropTypes.object,
-};
-
-const mapStateToProps = (state) => ({ settings: state.settings, theme: state.theme });
-const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(actions, dispatch) });
-
-export default connect(mapStateToProps, mapDispatchToProps)(Offsidebar);
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { connect } from "react-redux";
+import { bindActionCreators } from "redux";
+import * as actions from "../../store/actions/actions";
+
+import { TabContent, TabPane, Nav, NavItem, NavLink } from "reactstrap";
+
+class Offsidebar extends Component {
+	state = {
+		activeTab: "settings",
+		offsidebarReady: false,
+	};
+
+	componentDidMount() {
+		// When mounted display the offsidebar
+		window.requestAnimationFrame(() => this.setState({ offsidebarReady: true }));
+	}
+
+	toggle = (tab) => {
+		if (this.state.activeTab !== tab) {
+			this.setState({
+				activeTab: tab,
+			});
+		}
+	};
+
+	handleSettingCheckbox = (event) => {
+		this.props.actions.changeSetting(event.target.name, event.target.checked);
+	};
+
+	handleThemeRadio = (event) => {
+		this.props.actions.changeTheme(event.target.value);
+	};
+
+	render() {
+		return (
+			this.state.offsidebarReady && (
+				<aside className="offsidebar">
+					{/* START Off Sidebar (right) */}
+					<nav>
+						<div>
+							{/* Nav tabs */}
+							<Nav tabs justified>
+								<NavItem>
+									<NavLink
+										className={this.state.activeTab === "settings" ? "active" : ""}
+										onClick={() => {
+											this.toggle("settings");
+										}}
+									>
+										<em className="icon-equalizer fa-lg"></em>
+									</NavLink>
+								</NavItem>
+								{/* <NavItem>
+                                <NavLink className={ this.state.activeTab === 'chat' ? 'active':'' }
+                                    onClick={() => { this.toggle('chat'); }}
+                                >
+                                    <em className="icon-user fa-lg"></em>
+                                </NavLink>
+                            </NavItem> */}
+							</Nav>
+							{/* Tab panes */}
+							<TabContent activeTab={this.state.activeTab}>
+								<TabPane tabId="settings">
+									<h3 className="text-center text-thin mt-4">Settings</h3>
+									<div className="p-2">
+										<h4 className="text-muted text-thin">Themes</h4>
+										<div className="row row-flush mb-2">
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-a"} value="theme-a" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-info"></span>
+															<span className="color bg-info-light"></span>
+														</span>
+														<span className="color bg-white"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-b"} value="theme-b" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-green"></span>
+															<span className="color bg-green-light"></span>
+														</span>
+														<span className="color bg-white"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-c"} value="theme-c" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-purple"></span>
+															<span className="color bg-purple-light"></span>
+														</span>
+														<span className="color bg-white"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-d"} value="theme-d" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-danger"></span>
+															<span className="color bg-danger-light"></span>
+														</span>
+														<span className="color bg-white"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-e"} value="theme-e" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-info-dark"></span>
+															<span className="color bg-info"></span>
+														</span>
+														<span className="color bg-gray-dark"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-f"} value="theme-f" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-green-dark"></span>
+															<span className="color bg-green"></span>
+														</span>
+														<span className="color bg-gray-dark"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-g"} value="theme-g" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-purple-dark"></span>
+															<span className="color bg-purple"></span>
+														</span>
+														<span className="color bg-gray-dark"></span>
+													</label>
+												</div>
+											</div>
+											<div className="col-3 mb-3">
+												<div className="setting-color">
+													<label>
+														<input type="radio" name="setting-theme" checked={this.props.theme.name === "theme-h"} value="theme-h" onChange={this.handleThemeRadio} />
+														<span className="icon-check"></span>
+														<span className="split">
+															<span className="color bg-danger-dark"></span>
+															<span className="color bg-danger"></span>
+														</span>
+														<span className="color bg-gray-dark"></span>
+													</label>
+												</div>
+											</div>
+										</div>
+									</div>
+									{/* <div className="p-2">
+										<h4 className="text-muted text-thin">Layout</h4>
+										<div className="clearfix">
+											<p className="float-left">Fixed</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-fixed" type="checkbox" name="isFixed" checked={this.props.settings.isFixed} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+										<div className="clearfix">
+											<p className="float-left">Boxed</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-boxed" type="checkbox" name="isBoxed" checked={this.props.settings.isBoxed} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+									</div>
+									<div className="p-2">
+										<h4 className="text-muted text-thin">Aside</h4>
+										<div className="clearfix">
+											<p className="float-left">Collapsed</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-collapsed" type="checkbox" name="isCollapsed" checked={this.props.settings.isCollapsed} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+										<div className="clearfix">
+											<p className="float-left">Collapsed Text</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-collapsed-text" type="checkbox" name="isCollapsedText" checked={this.props.settings.isCollapsedText} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+										<div className="clearfix">
+											<p className="float-left">Float</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-float" type="checkbox" name="isFloat" checked={this.props.settings.isFloat} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+										<div className="clearfix">
+											<p className="float-left">Hover</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-hover" type="checkbox" name="asideHover" checked={this.props.settings.asideHover} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+										<div className="clearfix">
+											<p className="float-left">Show Scrollbar</p>
+											<div className="float-right">
+												<label className="switch">
+													<input id="chk-scrollbar" type="checkbox" name="asideScrollbar" checked={this.props.settings.asideScrollbar} onChange={this.handleSettingCheckbox} />
+													<span></span>
+												</label>
+											</div>
+										</div>
+									</div> */}
+								</TabPane>
+								<TabPane tabId="chat">
+									<h3 className="text-center text-thin mt-4">Connections</h3>
+									<div className="list-group">
+										{/* START list title */}
+										<div className="list-group-item border-0">
+											<small className="text-muted">ONLINE</small>
+										</div>
+										{/* END list title */}
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/05.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Juan Sims</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-success circle-lg"></span>
+												</div>
+											</div>
+										</div>
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/06.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Maureen Jenkins</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-success circle-lg"></span>
+												</div>
+											</div>
+										</div>
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/07.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Billie Dunn</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-danger circle-lg"></span>
+												</div>
+											</div>
+										</div>
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/08.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Tomothy Roberts</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-warning circle-lg"></span>
+												</div>
+											</div>
+										</div>
+										{/* START list title */}
+										<div className="list-group-item border-0">
+											<small className="text-muted">OFFLINE</small>
+										</div>
+										{/* END list title */}
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/09.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Lawrence Robinson</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-warning circle-lg"></span>
+												</div>
+											</div>
+										</div>
+										<div className="list-group-item list-group-item-action border-0">
+											<div className="media">
+												<img className="align-self-center mr-3 rounded-circle thumb48" src="/static/img/user/10.jpg" alt="User avatar" />
+												<div className="media-body text-truncate">
+													<a href="">
+														<strong>Tyrone Owens</strong>
+													</a>
+													<br />
+													<small className="text-muted">Designeer</small>
+												</div>
+												<div className="ml-auto">
+													<span className="circle bg-warning circle-lg"></span>
+												</div>
+											</div>
+										</div>
+									</div>
+									<div className="px-3 py-4 text-center">
+										{/* Optional link to list more users */}
+										<a className="btn btn-purple btn-sm" href="" title="See more contacts">
+											<strong>Load more..</strong>
+										</a>
+									</div>
+									{/* Extra items */}
+									<div className="px-3 py-2">
+										<p>
+											<small className="text-muted">Tasks completion</small>
+										</p>
+										<div className="progress progress-xs m-0">
+											<div className="progress-bar bg-success" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style={{ width: "80%" }}>
+												<span className="sr-only">80% Complete</span>
+											</div>
+										</div>
+									</div>
+									<div className="px-3 py-2">
+										<p>
+											<small className="text-muted">Upload quota</small>
+										</p>
+										<div className="progress progress-xs m-0">
+											<div className="progress-bar bg-warning" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style={{ width: "40%" }}>
+												<span className="sr-only">40% Complete</span>
+											</div>
+										</div>
+									</div>
+								</TabPane>
+							</TabContent>
+						</div>
+					</nav>
+					{/* END Off Sidebar (right) */}
+				</aside>
+			)
+		);
+	}
+}
+
+Offsidebar.propTypes = {
+	actions: PropTypes.object,
+	settings: PropTypes.object,
+	theme: PropTypes.object,
+};
+
+const mapStateToProps = (state) => ({ settings: state.settings, theme: state.theme });
+const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(actions, dispatch) });
+
+export default connect(mapStateToProps, mapDispatchToProps)(Offsidebar);

+ 34 - 34
components/Layout/SettingsProvider.js

@@ -1,34 +1,34 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-
-const getClasses = settings => {
-    let c = [];
-
-    if (settings.isFixed) c.push('layout-fixed');
-    if (settings.isBoxed) c.push('layout-boxed');
-    if (settings.isCollapsed) c.push('aside-collapsed');
-    if (settings.isCollapsedText) c.push('aside-collapsed-text');
-    if (settings.isFloat) c.push('aside-float');
-    if (settings.asideHover) c.push('aside-hover');
-    if (settings.offsidebarOpen) c.push('offsidebar-open');
-    if (settings.asideToggled) c.push('aside-toggled');
-    // layout horizontal
-    if (settings.horizontal) c.push('layout-h');
-
-    return c.join(' ');
-};
-
-const SettingsProvider = props => (
-    <div id="__settings_provider" className={getClasses(props.settings)}>
-        {props.children}
-    </div>
-);
-
-SettingsProvider.propTypes = {
-    settings: PropTypes.object
-};
-
-export default connect(
-    state => ({ settings: state.settings })
-)(SettingsProvider);
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+
+const getClasses = settings => {
+    let c = [];
+
+    if (settings.isFixed) c.push('layout-fixed');
+    if (settings.isBoxed) c.push('layout-boxed');
+    if (settings.isCollapsed) c.push('aside-collapsed');
+    if (settings.isCollapsedText) c.push('aside-collapsed-text');
+    if (settings.isFloat) c.push('aside-float');
+    if (settings.asideHover) c.push('aside-hover');
+    if (settings.offsidebarOpen) c.push('offsidebar-open');
+    if (settings.asideToggled) c.push('aside-toggled');
+    // layout horizontal
+    if (settings.horizontal) c.push('layout-h');
+
+    return c.join(' ');
+};
+
+const SettingsProvider = props => (
+    <div id="__settings_provider" className={getClasses(props.settings)}>
+        {props.children}
+    </div>
+);
+
+SettingsProvider.propTypes = {
+    settings: PropTypes.object
+};
+
+export default connect(
+    state => ({ settings: state.settings })
+)(SettingsProvider);

+ 326 - 326
components/Layout/Sidebar.js

@@ -1,326 +1,326 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { withTranslation, Trans } from "@/components/Common/Translate";
-import Link from "next/link";
-import Router, { withRouter } from "next/router";
-import { Collapse, Badge } from "reactstrap";
-
-import { connect } from "react-redux";
-import { bindActionCreators } from "redux";
-import * as actions from "../../store/actions/actions";
-
-import SidebarUserBlock from "./SidebarUserBlock";
-// import { getUser } from "@/actions/auth";
-
-import Menu from "./Menu.js";
-import MenuLLDIKTI from "./MenuLLDIKTI.js";
-import MenuPT from "./MenuPT.js";
-// localStorage.getItem("user");
-// import Menu from './MenuPT.js';
-
-// Helper to check for parrent of an given elements
-const parents = (element, selector) => {
-	if (typeof selector !== "string") {
-		return null;
-	}
-
-	const parents = [];
-	let ancestor = element.parentNode;
-
-	while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== 3 /*NODE_TEXT*/) {
-		if (ancestor.matches(selector)) {
-			parents.push(ancestor);
-		}
-
-		ancestor = ancestor.parentNode;
-	}
-	return parents;
-};
-// Helper to get outerHeight of a dom element
-const outerHeight = (elem, includeMargin) => {
-	const style = getComputedStyle(elem);
-	const margins = includeMargin ? parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10) : 0;
-	return elem.offsetHeight + margins;
-};
-
-/**
-    Component to display headings on sidebar
-*/
-const SidebarItemHeader = ({ item }) => (
-	<li className="nav-heading">
-		<span>
-			<Trans i18nKey={item.translate}>{item.heading}</Trans>
-		</span>
-	</li>
-);
-
-/**
-    Normal items for the sidebar
-*/
-const SidebarItem = ({ item, isActive, className, onMouseEnter }) => (
-	<li className={isActive ? "active" : ""} onMouseEnter={onMouseEnter}>
-		<Link href={item.path} as={item.as}>
-			<a title={item.name}>
-				{item.label && (
-					<Badge tag="div" className="float-right" color={item.label.color}>
-						{item.label.value}
-					</Badge>
-				)}
-				{item.icon && <em className={item.icon} />}
-				<span>
-					<Trans i18nKey={item.translate}>{item.name}</Trans>
-				</span>
-			</a>
-		</Link>
-	</li>
-);
-
-/**
-    Build a sub menu with items inside and attach collapse behavior
-*/
-const SidebarSubItem = ({ item, isActive, handler, children, isOpen, onMouseEnter }) => (
-	<li className={isActive ? "active" : ""}>
-		<div className="nav-item" onClick={handler} onMouseEnter={onMouseEnter}>
-			{item.label && (
-				<Badge tag="div" className="float-right" color={item.label.color}>
-					{item.label.value}
-				</Badge>
-			)}
-			{item.icon && <em className={item.icon} />}
-			<span>
-				<Trans i18nKey={item.translate}>{item.name}</Trans>
-			</span>
-		</div>
-		<Collapse isOpen={isOpen}>
-			<ul id={item.path} className="sidebar-nav sidebar-subnav">
-				{children}
-			</ul>
-		</Collapse>
-	</li>
-);
-
-/**
-    Component used to display a header on menu when using collapsed/hover mode
-*/
-const SidebarSubHeader = ({ item }) => <li className="sidebar-subnav-header">{item.name}</li>;
-
-const SidebarBackdrop = ({ closeFloatingNav }) => <div className="sidebar-backdrop" onClick={closeFloatingNav} />;
-
-const FloatingNav = ({ item, target, routeActive, isFixed, closeFloatingNav }) => {
-	let asideContainer = document.querySelector(".aside-container");
-	let asideInner = asideContainer.firstElementChild; /*('.aside-inner')*/
-	let sidebar = asideInner.firstElementChild; /*('.sidebar')*/
-
-	let mar = parseInt(getComputedStyle(asideInner)["padding-top"], 0) + parseInt(getComputedStyle(asideContainer)["padding-top"], 0);
-	let itemTop = target.parentElement.offsetTop + mar - sidebar.scrollTop;
-	let vwHeight = document.body.clientHeight;
-
-	const setPositionStyle = (el) => {
-		if (!el) return;
-		el.style.position = isFixed ? "fixed" : "absolute";
-		el.style.top = itemTop + "px";
-		el.style.bottom = outerHeight(el, true) + itemTop > vwHeight ? 0 : "auto";
-	};
-
-	return (
-		<ul id={item.path} ref={setPositionStyle} className="sidebar-nav sidebar-subnav nav-floating" onMouseLeave={closeFloatingNav}>
-			<SidebarSubHeader item={item} />
-			{item.submenu.map((subitem, i) => (
-				<SidebarItem key={i} item={subitem} isActive={routeActive(subitem.path)} />
-			))}
-		</ul>
-	);
-};
-
-/**
-    The main sidebar component
-*/
-class Sidebar extends Component {
-	menu = [];
-	state = {
-		collapse: {},
-		showSidebarBackdrop: false,
-		currentFloatingItem: null,
-		currentFloatingItemTarget: null,
-		pathname: this.props.router.pathname,
-	};
-
-	async componentDidMount() {
-		// const user = await getUser();
-		const user = this.props.user;
-		this.menu = user.role.id === 2022 ? MenuPT : user.role.id === 2021 ? MenuLLDIKTI : Menu;
-		// prepare the flags to handle menu collapsed states
-		this.buildCollapseList();
-
-		// Listen for routes changes in order to hide the sidebar on mobile
-		Router.events.on("routeChangeStart", this.handleRouteChange);
-		Router.events.on("routeChangeComplete", this.handleRouteComplete);
-
-		// Attach event listener to automatically close sidebar when click outside
-		document.addEventListener("click", this.closeSidebarOnExternalClicks);
-	}
-
-	handleRouteComplete = (pathname) => {
-		this.setState({
-			pathname,
-		});
-	};
-
-	handleRouteChange = () => {
-		this.closeFloatingNav();
-		this.closeSidebar();
-	};
-
-	componentWillUnmount() {
-		document.removeEventListener("click", this.closeSidebarOnExternalClicks);
-		Router.events.off("routeChangeStart", this.handleRouteChange);
-		Router.events.off("routeChangeComplete", this.handleRouteComplete);
-	}
-
-	closeSidebar = () => {
-		this.props.actions.toggleSetting("asideToggled");
-	};
-
-	closeSidebarOnExternalClicks = (e) => {
-		// don't check if sidebar not visible
-		if (!this.props.settings.asideToggled) return;
-
-		if (
-			!parents(e.target, ".aside-container").length && // if not child of sidebar
-			!parents(e.target, ".topnavbar-wrapper").length && // if not child of header
-			!e.target.matches("#user-block-toggle") && // user block toggle anchor
-			!e.target.parentElement.matches("#user-block-toggle") // user block toggle icon
-		) {
-			this.closeSidebar();
-		}
-	};
-
-	/** prepare initial state of collapse menus.*/
-	buildCollapseList = () => {
-		let collapse = {};
-		this.menu
-			.filter(({ heading }) => !heading)
-			.forEach(({ name, path, submenu }) => {
-				collapse[name] = this.routeActive(submenu ? submenu.map(({ path }) => path) : path);
-			});
-		this.setState({ collapse });
-	};
-
-	routeActive = (paths) => {
-		const currpath = this.state.pathname;
-		paths = Array.isArray(paths) ? paths : [paths];
-		return paths.some((p) => (p === "/" ? currpath === p : currpath.indexOf(p) > -1));
-	};
-
-	toggleItemCollapse = (stateName) => () => {
-		for (let c in this.state.collapse) {
-			if (this.state.collapse[c] === true && c !== stateName)
-				this.setState({
-					collapse: {
-						[c]: false,
-					},
-				});
-		}
-		this.setState({
-			collapse: {
-				[stateName]: !this.state.collapse[stateName],
-			},
-		});
-	};
-
-	getSubRoutes = (item) => item.submenu.map(({ path }) => path);
-
-	/** map menu config to string to determine which element to render */
-	itemType = (item) => {
-		if (item.heading) return "heading";
-		if (!item.submenu) return "menu";
-		if (item.submenu) return "submenu";
-	};
-
-	shouldUseFloatingNav = () => {
-		return this.props.settings.isCollapsed || this.props.settings.isCollapsedText || this.props.settings.asideHover;
-	};
-
-	showFloatingNav = (item) => (e) => {
-		if (this.shouldUseFloatingNav())
-			this.setState({
-				currentFloatingItem: item,
-				currentFloatingItemTarget: e.currentTarget,
-				showSidebarBackdrop: true,
-			});
-	};
-
-	closeFloatingNav = () => {
-		this.setState({
-			currentFloatingItem: null,
-			currentFloatingItemTarget: null,
-			showSidebarBackdrop: false,
-		});
-	};
-
-	render() {
-		return (
-			<>
-				<aside className="aside-container">
-					{/* START Sidebar (left) */}
-					<div className="aside-inner">
-						<nav className={"sidebar " + (this.props.settings.asideScrollbar ? "show-scrollbar" : "")}>
-							{/* START sidebar nav */}
-							<ul className="sidebar-nav">
-								{/* START user info */}
-								<li className="has-user-block">
-									<SidebarUserBlock />
-								</li>
-								{/* END user info */}
-
-								{/* Iterates over all sidebar items */}
-								{this.menu.map((item, i) => {
-									// heading
-									if (this.itemType(item) === "heading") return <SidebarItemHeader item={item} key={i} />;
-									else {
-										if (this.itemType(item) === "menu") return <SidebarItem isActive={this.routeActive(item.path)} item={item} key={i} onMouseEnter={this.closeFloatingNav} />;
-										if (this.itemType(item) === "submenu")
-											return [
-												<SidebarSubItem
-													item={item}
-													isOpen={this.state.collapse[item.name]}
-													handler={this.toggleItemCollapse(item.name)}
-													isActive={this.routeActive(this.getSubRoutes(item))}
-													key={i}
-													onMouseEnter={this.showFloatingNav(item)}
-												>
-													<SidebarSubHeader item={item} key={i} />
-													{item.submenu.map((subitem, i) => (
-														<SidebarItem key={i} item={subitem} isActive={this.routeActive(subitem.path)} />
-													))}
-												</SidebarSubItem>,
-											];
-									}
-									return null; // unrecognized item
-								})}
-							</ul>
-							{/* END sidebar nav */}
-						</nav>
-					</div>
-					{/* END Sidebar (left) */}
-					{this.state.currentFloatingItem && this.state.currentFloatingItem.submenu && (
-						<FloatingNav item={this.state.currentFloatingItem} target={this.state.currentFloatingItemTarget} routeActive={this.routeActive} isFixed={this.props.settings.isFixed} closeFloatingNav={this.closeFloatingNav} />
-					)}
-				</aside>
-				{this.state.showSidebarBackdrop && <SidebarBackdrop closeFloatingNav={this.closeFloatingNav} />}
-			</>
-		);
-	}
-}
-
-Sidebar.propTypes = {
-	actions: PropTypes.object,
-	settings: PropTypes.object,
-};
-
-const mapStateToProps = (state) => ({ settings: state.settings, user: state.user });
-const mapDispatchToProps = (dispatch) => ({
-	actions: bindActionCreators(actions, dispatch),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(withRouter(withTranslation(Sidebar)));
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { withTranslation, Trans } from "@/components/Common/Translate";
+import Link from "next/link";
+import Router, { withRouter } from "next/router";
+import { Collapse, Badge } from "reactstrap";
+
+import { connect } from "react-redux";
+import { bindActionCreators } from "redux";
+import * as actions from "../../store/actions/actions";
+
+import SidebarUserBlock from "./SidebarUserBlock";
+// import { getUser } from "@/actions/auth";
+
+import Menu from "./Menu.js";
+import MenuLLDIKTI from "./MenuLLDIKTI.js";
+import MenuPT from "./MenuPT.js";
+// localStorage.getItem("user");
+// import Menu from './MenuPT.js';
+
+// Helper to check for parrent of an given elements
+const parents = (element, selector) => {
+	if (typeof selector !== "string") {
+		return null;
+	}
+
+	const parents = [];
+	let ancestor = element.parentNode;
+
+	while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== 3 /*NODE_TEXT*/) {
+		if (ancestor.matches(selector)) {
+			parents.push(ancestor);
+		}
+
+		ancestor = ancestor.parentNode;
+	}
+	return parents;
+};
+// Helper to get outerHeight of a dom element
+const outerHeight = (elem, includeMargin) => {
+	const style = getComputedStyle(elem);
+	const margins = includeMargin ? parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10) : 0;
+	return elem.offsetHeight + margins;
+};
+
+/**
+    Component to display headings on sidebar
+*/
+const SidebarItemHeader = ({ item }) => (
+	<li className="nav-heading">
+		<span>
+			<Trans i18nKey={item.translate}>{item.heading}</Trans>
+		</span>
+	</li>
+);
+
+/**
+    Normal items for the sidebar
+*/
+const SidebarItem = ({ item, isActive, className, onMouseEnter }) => (
+	<li className={isActive ? "active" : ""} onMouseEnter={onMouseEnter}>
+		<Link href={item.path} as={item.as}>
+			<a title={item.name}>
+				{item.label && (
+					<Badge tag="div" className="float-right" color={item.label.color}>
+						{item.label.value}
+					</Badge>
+				)}
+				{item.icon && <em className={item.icon} />}
+				<span>
+					<Trans i18nKey={item.translate}>{item.name}</Trans>
+				</span>
+			</a>
+		</Link>
+	</li>
+);
+
+/**
+    Build a sub menu with items inside and attach collapse behavior
+*/
+const SidebarSubItem = ({ item, isActive, handler, children, isOpen, onMouseEnter }) => (
+	<li className={isActive ? "active" : ""}>
+		<div className="nav-item" onClick={handler} onMouseEnter={onMouseEnter}>
+			{item.label && (
+				<Badge tag="div" className="float-right" color={item.label.color}>
+					{item.label.value}
+				</Badge>
+			)}
+			{item.icon && <em className={item.icon} />}
+			<span>
+				<Trans i18nKey={item.translate}>{item.name}</Trans>
+			</span>
+		</div>
+		<Collapse isOpen={isOpen}>
+			<ul id={item.path} className="sidebar-nav sidebar-subnav">
+				{children}
+			</ul>
+		</Collapse>
+	</li>
+);
+
+/**
+    Component used to display a header on menu when using collapsed/hover mode
+*/
+const SidebarSubHeader = ({ item }) => <li className="sidebar-subnav-header">{item.name}</li>;
+
+const SidebarBackdrop = ({ closeFloatingNav }) => <div className="sidebar-backdrop" onClick={closeFloatingNav} />;
+
+const FloatingNav = ({ item, target, routeActive, isFixed, closeFloatingNav }) => {
+	let asideContainer = document.querySelector(".aside-container");
+	let asideInner = asideContainer.firstElementChild; /*('.aside-inner')*/
+	let sidebar = asideInner.firstElementChild; /*('.sidebar')*/
+
+	let mar = parseInt(getComputedStyle(asideInner)["padding-top"], 0) + parseInt(getComputedStyle(asideContainer)["padding-top"], 0);
+	let itemTop = target.parentElement.offsetTop + mar - sidebar.scrollTop;
+	let vwHeight = document.body.clientHeight;
+
+	const setPositionStyle = (el) => {
+		if (!el) return;
+		el.style.position = isFixed ? "fixed" : "absolute";
+		el.style.top = itemTop + "px";
+		el.style.bottom = outerHeight(el, true) + itemTop > vwHeight ? 0 : "auto";
+	};
+
+	return (
+		<ul id={item.path} ref={setPositionStyle} className="sidebar-nav sidebar-subnav nav-floating" onMouseLeave={closeFloatingNav}>
+			<SidebarSubHeader item={item} />
+			{item.submenu.map((subitem, i) => (
+				<SidebarItem key={i} item={subitem} isActive={routeActive(subitem.path)} />
+			))}
+		</ul>
+	);
+};
+
+/**
+    The main sidebar component
+*/
+class Sidebar extends Component {
+	menu = [];
+	state = {
+		collapse: {},
+		showSidebarBackdrop: false,
+		currentFloatingItem: null,
+		currentFloatingItemTarget: null,
+		pathname: this.props.router.pathname,
+	};
+
+	async componentDidMount() {
+		// const user = await getUser();
+		const user = this.props.user;
+		this.menu = user.role.id === 2022 ? MenuPT : user.role.id === 2021 ? MenuLLDIKTI : Menu;
+		// prepare the flags to handle menu collapsed states
+		this.buildCollapseList();
+
+		// Listen for routes changes in order to hide the sidebar on mobile
+		Router.events.on("routeChangeStart", this.handleRouteChange);
+		Router.events.on("routeChangeComplete", this.handleRouteComplete);
+
+		// Attach event listener to automatically close sidebar when click outside
+		document.addEventListener("click", this.closeSidebarOnExternalClicks);
+	}
+
+	handleRouteComplete = (pathname) => {
+		this.setState({
+			pathname,
+		});
+	};
+
+	handleRouteChange = () => {
+		this.closeFloatingNav();
+		this.closeSidebar();
+	};
+
+	componentWillUnmount() {
+		document.removeEventListener("click", this.closeSidebarOnExternalClicks);
+		Router.events.off("routeChangeStart", this.handleRouteChange);
+		Router.events.off("routeChangeComplete", this.handleRouteComplete);
+	}
+
+	closeSidebar = () => {
+		this.props.actions.toggleSetting("asideToggled");
+	};
+
+	closeSidebarOnExternalClicks = (e) => {
+		// don't check if sidebar not visible
+		if (!this.props.settings.asideToggled) return;
+
+		if (
+			!parents(e.target, ".aside-container").length && // if not child of sidebar
+			!parents(e.target, ".topnavbar-wrapper").length && // if not child of header
+			!e.target.matches("#user-block-toggle") && // user block toggle anchor
+			!e.target.parentElement.matches("#user-block-toggle") // user block toggle icon
+		) {
+			this.closeSidebar();
+		}
+	};
+
+	/** prepare initial state of collapse menus.*/
+	buildCollapseList = () => {
+		let collapse = {};
+		this.menu
+			.filter(({ heading }) => !heading)
+			.forEach(({ name, path, submenu }) => {
+				collapse[name] = this.routeActive(submenu ? submenu.map(({ path }) => path) : path);
+			});
+		this.setState({ collapse });
+	};
+
+	routeActive = (paths) => {
+		const currpath = this.state.pathname;
+		paths = Array.isArray(paths) ? paths : [paths];
+		return paths.some((p) => (p === "/" ? currpath === p : currpath.indexOf(p) > -1));
+	};
+
+	toggleItemCollapse = (stateName) => () => {
+		for (let c in this.state.collapse) {
+			if (this.state.collapse[c] === true && c !== stateName)
+				this.setState({
+					collapse: {
+						[c]: false,
+					},
+				});
+		}
+		this.setState({
+			collapse: {
+				[stateName]: !this.state.collapse[stateName],
+			},
+		});
+	};
+
+	getSubRoutes = (item) => item.submenu.map(({ path }) => path);
+
+	/** map menu config to string to determine which element to render */
+	itemType = (item) => {
+		if (item.heading) return "heading";
+		if (!item.submenu) return "menu";
+		if (item.submenu) return "submenu";
+	};
+
+	shouldUseFloatingNav = () => {
+		return this.props.settings.isCollapsed || this.props.settings.isCollapsedText || this.props.settings.asideHover;
+	};
+
+	showFloatingNav = (item) => (e) => {
+		if (this.shouldUseFloatingNav())
+			this.setState({
+				currentFloatingItem: item,
+				currentFloatingItemTarget: e.currentTarget,
+				showSidebarBackdrop: true,
+			});
+	};
+
+	closeFloatingNav = () => {
+		this.setState({
+			currentFloatingItem: null,
+			currentFloatingItemTarget: null,
+			showSidebarBackdrop: false,
+		});
+	};
+
+	render() {
+		return (
+			<>
+				<aside className="aside-container">
+					{/* START Sidebar (left) */}
+					<div className="aside-inner">
+						<nav className={"sidebar " + (this.props.settings.asideScrollbar ? "show-scrollbar" : "")}>
+							{/* START sidebar nav */}
+							<ul className="sidebar-nav">
+								{/* START user info */}
+								<li className="has-user-block">
+									<SidebarUserBlock />
+								</li>
+								{/* END user info */}
+
+								{/* Iterates over all sidebar items */}
+								{this.menu.map((item, i) => {
+									// heading
+									if (this.itemType(item) === "heading") return <SidebarItemHeader item={item} key={i} />;
+									else {
+										if (this.itemType(item) === "menu") return <SidebarItem isActive={this.routeActive(item.path)} item={item} key={i} onMouseEnter={this.closeFloatingNav} />;
+										if (this.itemType(item) === "submenu")
+											return [
+												<SidebarSubItem
+													item={item}
+													isOpen={this.state.collapse[item.name]}
+													handler={this.toggleItemCollapse(item.name)}
+													isActive={this.routeActive(this.getSubRoutes(item))}
+													key={i}
+													onMouseEnter={this.showFloatingNav(item)}
+												>
+													<SidebarSubHeader item={item} key={i} />
+													{item.submenu.map((subitem, i) => (
+														<SidebarItem key={i} item={subitem} isActive={this.routeActive(subitem.path)} />
+													))}
+												</SidebarSubItem>,
+											];
+									}
+									return null; // unrecognized item
+								})}
+							</ul>
+							{/* END sidebar nav */}
+						</nav>
+					</div>
+					{/* END Sidebar (left) */}
+					{this.state.currentFloatingItem && this.state.currentFloatingItem.submenu && (
+						<FloatingNav item={this.state.currentFloatingItem} target={this.state.currentFloatingItemTarget} routeActive={this.routeActive} isFixed={this.props.settings.isFixed} closeFloatingNav={this.closeFloatingNav} />
+					)}
+				</aside>
+				{this.state.showSidebarBackdrop && <SidebarBackdrop closeFloatingNav={this.closeFloatingNav} />}
+			</>
+		);
+	}
+}
+
+Sidebar.propTypes = {
+	actions: PropTypes.object,
+	settings: PropTypes.object,
+};
+
+const mapStateToProps = (state) => ({ settings: state.settings, user: state.user });
+const mapDispatchToProps = (dispatch) => ({
+	actions: bindActionCreators(actions, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(withRouter(withTranslation(Sidebar)));

+ 56 - 56
components/Layout/SidebarUserBlock.js

@@ -1,56 +1,56 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { Collapse } from "reactstrap";
-
-import { connect } from "react-redux";
-
-class SidebarUserBlock extends Component {
-	state = {
-		showUserBlock: true,
-		user: {},
-		role: {},
-	};
-
-	async componentDidMount() {
-		const user = this.props.user;
-		this.setState({ user, role: user.role });
-	}
-
-	componentDidUpdate(oldProps) {
-		if (oldProps.showUserBlock !== this.props.showUserBlock) {
-			this.setState({ showUserBlock: this.props.showUserBlock });
-		}
-	}
-
-	render() {
-		const { user, role } = this.state;
-		return (
-			<Collapse id="user-block" isOpen={this.state.showUserBlock}>
-				<div>
-					<div className="item user-block">
-						{/* User picture */}
-						<div className="user-block-picture">
-							<div className="user-block-status">
-								<img className="rounded-circle" src={role.id === 2022 ? "/static/img/univ-avatar.png" : "/static/img/logo-single.png"} alt="Avatar" width="60" height="60" />
-								<div className="circle bg-success circle-lg"></div>
-							</div>
-						</div>
-						{/* Name and Job */}
-						<div className="user-block-info">
-							<span className="user-block-name">{user.nama}</span>
-							<span className="user-block-role">{role.nama}</span>
-						</div>
-					</div>
-				</div>
-			</Collapse>
-		);
-	}
-}
-
-SidebarUserBlock.propTypes = {
-	showUserBlock: PropTypes.bool,
-};
-
-const mapStateToProps = (state) => ({ showUserBlock: state.settings.showUserBlock, user: state.user });
-
-export default connect(mapStateToProps)(SidebarUserBlock);
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { Collapse } from "reactstrap";
+
+import { connect } from "react-redux";
+
+class SidebarUserBlock extends Component {
+	state = {
+		showUserBlock: true,
+		user: {},
+		role: {},
+	};
+
+	async componentDidMount() {
+		const user = this.props.user;
+		this.setState({ user, role: user.role });
+	}
+
+	componentDidUpdate(oldProps) {
+		if (oldProps.showUserBlock !== this.props.showUserBlock) {
+			this.setState({ showUserBlock: this.props.showUserBlock });
+		}
+	}
+
+	render() {
+		const { user, role } = this.state;
+		return (
+			<Collapse id="user-block" isOpen={this.state.showUserBlock}>
+				<div>
+					<div className="item user-block">
+						{/* User picture */}
+						<div className="user-block-picture">
+							<div className="user-block-status">
+								<img className="rounded-circle" src={role.id === 2022 ? "/static/img/univ-avatar.png" : "/static/img/logo-single.png"} alt="Avatar" width="60" height="60" />
+								<div className="circle bg-success circle-lg"></div>
+							</div>
+						</div>
+						{/* Name and Job */}
+						<div className="user-block-info">
+							<span className="user-block-name">{user.nama}</span>
+							<span className="user-block-role">{role.nama}</span>
+						</div>
+					</div>
+				</div>
+			</Collapse>
+		);
+	}
+}
+
+SidebarUserBlock.propTypes = {
+	showUserBlock: PropTypes.bool,
+};
+
+const mapStateToProps = (state) => ({ showUserBlock: state.settings.showUserBlock, user: state.user });
+
+export default connect(mapStateToProps)(SidebarUserBlock);

+ 26 - 26
components/Layout/ThemesProvider.js

@@ -1,26 +1,26 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { connect } from "react-redux";
-
-// Import all theme into main css chunk
-// todo: import dynamically
-import "../../styles/themes/theme-a.scss";
-import "../../styles/themes/theme-b.scss";
-import "../../styles/themes/theme-c.scss";
-import "../../styles/themes/theme-d.scss";
-import "../../styles/themes/theme-e.scss";
-import "../../styles/themes/theme-f.scss";
-import "../../styles/themes/theme-g.scss";
-import "../../styles/themes/theme-h.scss";
-
-const ThemesProvider = (props) => (
-	<div id="__themes_provider" className={props.theme.name}>
-		{props.children}
-	</div>
-);
-
-ThemesProvider.propTypes = {
-	theme: PropTypes.object,
-};
-
-export default connect((state) => ({ theme: state.theme }))(ThemesProvider);
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+// Import all theme into main css chunk
+// todo: import dynamically
+import "../../styles/themes/theme-a.scss";
+import "../../styles/themes/theme-b.scss";
+import "../../styles/themes/theme-c.scss";
+import "../../styles/themes/theme-d.scss";
+import "../../styles/themes/theme-e.scss";
+import "../../styles/themes/theme-f.scss";
+import "../../styles/themes/theme-g.scss";
+import "../../styles/themes/theme-h.scss";
+
+const ThemesProvider = (props) => (
+	<div id="__themes_provider" className={props.theme.name}>
+		{props.children}
+	</div>
+);
+
+ThemesProvider.propTypes = {
+	theme: PropTypes.object,
+};
+
+export default connect((state) => ({ theme: state.theme }))(ThemesProvider);

+ 83 - 83
components/Main/CaseProgress.js

@@ -1,83 +1,83 @@
-import { Progress } from "reactstrap";
-import Sparkline from "@/components/Common/Sparklines";
-
-function CaseProgress() {
-	return (
-		<div className="card b">
-			<div className="card-body bb">
-				<p>Overvall progress</p>
-				<div className="d-flex align-items-center mb-2">
-					<div className="w-100">
-						<Progress className="progress-xs m0" color="info" value={20} />
-					</div>
-					<div className="ml-auto">
-						<div className="col wd-xxs text-right">
-							<div className="text-bold text-muted">20%</div>
-						</div>
-					</div>
-				</div>
-			</div>
-			<div className="card-body">
-				<p>Metrics</p>
-				<div className="row text-center">
-					<div className="col-6 col-lg-6 col-xl-6">
-						<Sparkline
-							values={[20, 80]}
-							options={{
-								type: "pie",
-								height: "50",
-								sliceColors: ["#edf1f2", "#23b7e5"],
-							}}
-							className="sparkline"
-						/>
-						<p className="mt-3">Open Case</p>
-					</div>
-					<div className="col-6 col-lg-6 col-xl-6">
-						<Sparkline
-							values={[80, 20]}
-							options={{
-								type: "pie",
-								height: "50",
-								sliceColors: ["#edf1f2", "#27c24c"],
-							}}
-							className="sparkline"
-						/>
-						<p className="mt-3">Close Case</p>
-					</div>
-				</div>
-			</div>
-			<table className="table bb">
-				<tbody>
-					<tr>
-						<td>
-							<strong>Open Case</strong>
-						</td>
-						<td>80</td>
-					</tr>
-					<tr>
-						<td>
-							<strong>Close Case</strong>
-						</td>
-						<td>20</td>
-					</tr>
-					<tr>
-						<td>
-							<strong>Performance</strong>
-						</td>
-						<td>
-							<em className="far fa-smile fa-lg text-warning"></em>
-						</td>
-					</tr>
-					<tr>
-						<td>
-							<strong>Last Case Closed</strong>
-						</td>
-						<td>BI:1107 - 12/01/2016</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	);
-}
-
-export default CaseProgress;
+import { Progress } from "reactstrap";
+import Sparkline from "@/components/Common/Sparklines";
+
+function CaseProgress() {
+	return (
+		<div className="card b">
+			<div className="card-body bb">
+				<p>Overvall progress</p>
+				<div className="d-flex align-items-center mb-2">
+					<div className="w-100">
+						<Progress className="progress-xs m0" color="info" value={20} />
+					</div>
+					<div className="ml-auto">
+						<div className="col wd-xxs text-right">
+							<div className="text-bold text-muted">20%</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div className="card-body">
+				<p>Metrics</p>
+				<div className="row text-center">
+					<div className="col-6 col-lg-6 col-xl-6">
+						<Sparkline
+							values={[20, 80]}
+							options={{
+								type: "pie",
+								height: "50",
+								sliceColors: ["#edf1f2", "#23b7e5"],
+							}}
+							className="sparkline"
+						/>
+						<p className="mt-3">Open Case</p>
+					</div>
+					<div className="col-6 col-lg-6 col-xl-6">
+						<Sparkline
+							values={[80, 20]}
+							options={{
+								type: "pie",
+								height: "50",
+								sliceColors: ["#edf1f2", "#27c24c"],
+							}}
+							className="sparkline"
+						/>
+						<p className="mt-3">Close Case</p>
+					</div>
+				</div>
+			</div>
+			<table className="table bb">
+				<tbody>
+					<tr>
+						<td>
+							<strong>Open Case</strong>
+						</td>
+						<td>80</td>
+					</tr>
+					<tr>
+						<td>
+							<strong>Close Case</strong>
+						</td>
+						<td>20</td>
+					</tr>
+					<tr>
+						<td>
+							<strong>Performance</strong>
+						</td>
+						<td>
+							<em className="far fa-smile fa-lg text-warning"></em>
+						</td>
+					</tr>
+					<tr>
+						<td>
+							<strong>Last Case Closed</strong>
+						</td>
+						<td>BI:1107 - 12/01/2016</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	);
+}
+
+export default CaseProgress;

+ 162 - 162
components/Main/DetailLaporan.js

@@ -1,162 +1,162 @@
-import Scrollable from "@/components/Common/Scrollable";
-import moment from "moment";
-import { Col, FormGroup } from "reactstrap";
-import { useSelector } from "react-redux";
-
-function DetailLaporan({ data, noTitle = false, noStatus = false }) {
-	const user = useSelector((state) => state.user);
-	return (
-		<>
-			{(!data.user.isPrivate || user?.role.id === 2020) && (
-				<>
-					{noTitle ? (
-						""
-					) : (
-						<div className="header-1">
-							<h2 className="card-title-1">Identitas Pelapor - {data.user.isPublic ? "Umum" : "Internal"}</h2>
-						</div>
-					)}
-					{data.user.nama && (
-						<FormGroup row>
-							<Col md="4">Nama Pelapor:</Col>
-							<Col md="8">
-								<strong>{data.user.nama}</strong>
-							</Col>
-						</FormGroup>
-					)}
-					<FormGroup row>
-						<Col md="4">Nomor yang dapat dihubungi:</Col>
-						<Col md="8">
-							<strong>
-								{data.user.no_hp} {data.user.verified && "(Terverifikasi)"}
-							</strong>
-						</Col>
-					</FormGroup>
-					{data.user.email && (
-						<FormGroup row>
-							<Col md="4">Email:</Col>
-							<Col md="8">
-								<strong>{data.user.email}</strong>
-							</Col>
-						</FormGroup>
-					)}
-
-					{data.user.isPublic && (
-						<>
-							{data.user.alamat && (
-								<FormGroup row>
-									<Col md="4">Alamat:</Col>
-									<Col md="8">
-										<strong>{data.user.alamat}</strong>
-									</Col>
-								</FormGroup>
-							)}
-							{data.user.foto && (
-								<FormGroup row>
-									<Col md="4">Foto Kartu Identitas:</Col>
-									<Col md="8">
-										<img src={data.user.foto.path} height={200} alt="Foto Identitas" />
-									</Col>
-								</FormGroup>
-							)}
-						</>
-					)}
-					{data.user.isPrivate && (
-						<FormGroup row>
-							<Col md="4">Dirahasiakan</Col>
-							<Col md="8">
-								<strong>Ya</strong>
-							</Col>
-						</FormGroup>
-					)}
-				</>
-			)}
-			{noTitle ? (
-				""
-			) : (
-				<div className="header-1">
-					<h2 className="card-title-1">Detail Laporan</h2>
-				</div>
-			)}
-			<form className="form-horizontal">
-				<FormGroup row>
-					<Col md="4">Nomor Laporan:</Col>
-					<Col md="8">
-						<strong>{data.no_laporan}</strong>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Nama Perguruan Tinggi yang Dilaporkan:</Col>
-					<Col md="8">
-						<strong>{data.pt.nama}</strong>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Jenis Pelanggaran:</Col>
-					<Col md="8">
-						<Scrollable height="125px" className="list-group">
-							<ul>
-								{data.pelanggaran.map((e) => (
-									<li>{e.pelanggaran}</li>
-								))}
-							</ul>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Keterangan Laporan:</Col>
-					<Col md="8">
-						<Scrollable height="100px" className="list-group">
-							<p>{data.keterangan}</p>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Dibuat Pada:</Col>
-					<Col md="8">
-						<strong>{moment(data.createdAt).format("D MMMM YYYY")}</strong>
-					</Col>
-				</FormGroup>
-				{!noStatus && data.aktif ? (
-					<FormGroup row>
-						<Col md="4">Status:</Col>
-						<Col md="8">
-							<div className="badge badge-info">{data.role_data === "dikti" ? "Ditindaklanjuti DIKTI" : "Ditindaklanjuti LLDIKTI"}</div>
-						</Col>
-					</FormGroup>
-				) : (
-					""
-				)}
-				{/* <FormGroup row>
-					<Col md="4">Prioritas:</Col>
-					<Col md="8">{data.level == 3 ? <div className="badge badge-success">Tinggi</div> : data.level == 2 ? <div className="badge badge-info">Sedang</div> : <div className="badge badge-warning">Rendah</div>}</Col>
-				</FormGroup> */}
-				<FormGroup row>
-					<Col md="4">Dokumen Pendukung:</Col>
-					<Col md="8">
-						<Scrollable height="120px" className="list-group">
-							<table className="table table-bordered bg-transparent">
-								<tbody>
-									{data.dokumen.map((e, index) => (
-										<tr key={`files-${index}`}>
-											<td>
-												<em className="fa-lg far fa-file-code"></em>
-											</td>
-											<td>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</td>
-										</tr>
-									))}
-								</tbody>
-							</table>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-			</form>
-		</>
-	);
-}
-
-export default DetailLaporan;
+import Scrollable from "@/components/Common/Scrollable";
+import moment from "moment";
+import { Col, FormGroup } from "reactstrap";
+import { useSelector } from "react-redux";
+
+function DetailLaporan({ data, noTitle = false, noStatus = false }) {
+	const user = useSelector((state) => state.user);
+	return (
+		<>
+			{(!data.user.isPrivate || user?.role.id === 2020) && (
+				<>
+					{noTitle ? (
+						""
+					) : (
+						<div className="header-1">
+							<h2 className="card-title-1">Identitas Pelapor - {data.user.isPublic ? "Umum" : "Internal"}</h2>
+						</div>
+					)}
+					{data.user.nama && (
+						<FormGroup row>
+							<Col md="4">Nama Pelapor:</Col>
+							<Col md="8">
+								<strong>{data.user.nama}</strong>
+							</Col>
+						</FormGroup>
+					)}
+					<FormGroup row>
+						<Col md="4">Nomor yang dapat dihubungi:</Col>
+						<Col md="8">
+							<strong>
+								{data.user.no_hp} {data.user.verified && "(Terverifikasi)"}
+							</strong>
+						</Col>
+					</FormGroup>
+					{data.user.email && (
+						<FormGroup row>
+							<Col md="4">Email:</Col>
+							<Col md="8">
+								<strong>{data.user.email}</strong>
+							</Col>
+						</FormGroup>
+					)}
+
+					{data.user.isPublic && (
+						<>
+							{data.user.alamat && (
+								<FormGroup row>
+									<Col md="4">Alamat:</Col>
+									<Col md="8">
+										<strong>{data.user.alamat}</strong>
+									</Col>
+								</FormGroup>
+							)}
+							{data.user.foto && (
+								<FormGroup row>
+									<Col md="4">Foto Kartu Identitas:</Col>
+									<Col md="8">
+										<img src={data.user.foto.path} height={200} alt="Foto Identitas" />
+									</Col>
+								</FormGroup>
+							)}
+						</>
+					)}
+					{data.user.isPrivate && (
+						<FormGroup row>
+							<Col md="4">Dirahasiakan</Col>
+							<Col md="8">
+								<strong>Ya</strong>
+							</Col>
+						</FormGroup>
+					)}
+				</>
+			)}
+			{noTitle ? (
+				""
+			) : (
+				<div className="header-1">
+					<h2 className="card-title-1">Detail Laporan</h2>
+				</div>
+			)}
+			<form className="form-horizontal">
+				<FormGroup row>
+					<Col md="4">Nomor Laporan:</Col>
+					<Col md="8">
+						<strong>{data.no_laporan}</strong>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Nama Perguruan Tinggi yang Dilaporkan:</Col>
+					<Col md="8">
+						<strong>{data.pt.nama}</strong>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Jenis Pelanggaran:</Col>
+					<Col md="8">
+						<Scrollable height="125px" className="list-group">
+							<ul>
+								{data.pelanggaran.map((e) => (
+									<li>{e.pelanggaran}</li>
+								))}
+							</ul>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Keterangan Laporan:</Col>
+					<Col md="8">
+						<Scrollable height="100px" className="list-group">
+							<p>{data.keterangan}</p>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Dibuat Pada:</Col>
+					<Col md="8">
+						<strong>{moment(data.createdAt).format("D MMMM YYYY")}</strong>
+					</Col>
+				</FormGroup>
+				{!noStatus && data.aktif ? (
+					<FormGroup row>
+						<Col md="4">Status:</Col>
+						<Col md="8">
+							<div className="badge badge-info">{data.role_data === "dikti" ? "Ditindaklanjuti DIKTI" : "Ditindaklanjuti LLDIKTI"}</div>
+						</Col>
+					</FormGroup>
+				) : (
+					""
+				)}
+				{/* <FormGroup row>
+					<Col md="4">Prioritas:</Col>
+					<Col md="8">{data.level == 3 ? <div className="badge badge-success">Tinggi</div> : data.level == 2 ? <div className="badge badge-info">Sedang</div> : <div className="badge badge-warning">Rendah</div>}</Col>
+				</FormGroup> */}
+				<FormGroup row>
+					<Col md="4">Dokumen Pendukung:</Col>
+					<Col md="8">
+						<Scrollable height="120px" className="list-group">
+							<table className="table table-bordered bg-transparent">
+								<tbody>
+									{data.dokumen.map((e, index) => (
+										<tr key={`files-${index}`}>
+											<td>
+												<em className="fa-lg far fa-file-code"></em>
+											</td>
+											<td>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</td>
+										</tr>
+									))}
+								</tbody>
+							</table>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+			</form>
+		</>
+	);
+}
+
+export default DetailLaporan;

+ 31 - 31
components/Main/DetailPT.js

@@ -1,31 +1,31 @@
-function DetailPT({ data }) {
-	return (
-		<div className="card card-default">
-			<div className="card-body">
-				<div className="text-center">
-					<h3 className="mt-0">{data.nama}</h3>
-					<p>{data.sk_pendirian}</p>
-					<p>Pembina: {data.pembina.nama}</p>
-					<p>{`${data.alamat.jalan} ${data.alamat.rt ? `rt ${data.alamat.rt}` : ""} ${data.alamat.rw ? `rt ${data.alamat.rw}` : ""}, ${data.alamat.kab_kota.nama}, ${data.propinsi.nama}`}</p>
-				</div>
-				<hr />
-				<ul className="list-unstyled px-4">
-					<li>
-						<em className="fa fa-globe fa-fw mr-3"></em>
-						<a href={`https://${data.website}`}>{data.website}</a>
-					</li>
-					<li>
-						<em className="fa fa-phone fa-fw mr-3"></em>
-						{data.telepon}
-					</li>
-					<li>
-						<em className="fa fa-at fa-fw mr-3"></em>
-						<a href={`mailto:${data.email}`}>{data.email}</a>
-					</li>
-				</ul>
-			</div>
-		</div>
-	);
-}
-
-export default DetailPT;
+function DetailPT({ data }) {
+	return (
+		<div className="card card-default">
+			<div className="card-body">
+				<div className="text-center">
+					<h3 className="mt-0">{data.nama}</h3>
+					<p>{data.sk_pendirian}</p>
+					<p>Pembina: {data.pembina.nama}</p>
+					<p>{`${data.alamat.jalan} ${data.alamat.rt ? `rt ${data.alamat.rt}` : ""} ${data.alamat.rw ? `rt ${data.alamat.rw}` : ""}, ${data.alamat.kab_kota.nama}, ${data.propinsi.nama}`}</p>
+				</div>
+				<hr />
+				<ul className="list-unstyled px-4">
+					<li>
+						<em className="fa fa-globe fa-fw mr-3"></em>
+						<a href={`https://${data.website}`}>{data.website}</a>
+					</li>
+					<li>
+						<em className="fa fa-phone fa-fw mr-3"></em>
+						{data.telepon}
+					</li>
+					<li>
+						<em className="fa fa-at fa-fw mr-3"></em>
+						<a href={`mailto:${data.email}`}>{data.email}</a>
+					</li>
+				</ul>
+			</div>
+		</div>
+	);
+}
+
+export default DetailPT;

+ 109 - 109
components/Main/DetailSanksi.js

@@ -1,109 +1,109 @@
-import Scrollable from "@/components/Common/Scrollable";
-import moment from "moment";
-import { Col, FormGroup, Table } from "reactstrap";
-import { API_URL } from "@/env";
-
-function DetailSanksi({ data, noTitle = false }) {
-	return (
-		<>
-			{noTitle ? "" : <p className="lead bb">Detail Sanksi</p>}
-			<form className="form-horizontal">
-				<FormGroup row>
-					<Col md="4">Nomor Sanksi:</Col>
-					<Col md="8">
-						<strong>{data.no_sanksi}</strong>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Nama Perguruan Tinggi:</Col>
-					<Col md="8">
-						<strong>{data.laporan.pt.nama}</strong>
-					</Col>
-				</FormGroup>
-
-				<FormGroup row>
-					<Col md="4">Keterangan:</Col>
-					<Col md="8">
-						<Scrollable height="100px" className="list-group">
-							<p>{data.keterangan}</p>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Dibuat Pada:</Col>
-					<Col md="8">
-						<strong>{moment(data.createdAt).format("D MMMM YYYY")}</strong>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md="4">Dokumen Sanksi:</Col>
-					<Col md="8">
-						<Scrollable height="120px" className="list-group">
-							<table className="table table-bordered bg-transparent">
-								<tbody>
-									{data.dokumen.map((e) => (
-										<tr>
-											<td>
-												<em className="fa-lg far fa-file-code"></em>
-											</td>
-											<td>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</td>
-										</tr>
-									))}
-								</tbody>
-							</table>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-				<FormGroup row>
-					<Col md={12}>
-						<div className="card b">
-							<div className="card-body bb">
-								<Table responsive>
-									<thead>
-										<tr>
-											<th>Jenis Pelanggaran</th>
-											<th>Sanksi</th>
-										</tr>
-									</thead>
-									<tbody>
-										{data.pelanggaran.map((jp, index) => (
-											<tr key={jp._id}>
-												<td width={50}>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<p>{jp.pelanggaran}</p>
-																<p>TMT : {jp.tmt_bulan} Bulan</p>
-																<p>Jenis Sanksi Administratif : {jp.label_sanksi}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-												<td width={50}>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<p>{jp.sanksi}</p>
-																<p>Keterangan : {jp.keterangan_sanksi}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-											</tr>
-										))}
-									</tbody>
-								</Table>
-							</div>
-						</div>
-					</Col>
-				</FormGroup>
-			</form>
-		</>
-	);
-}
-
-export default DetailSanksi;
+import Scrollable from "@/components/Common/Scrollable";
+import moment from "moment";
+import { Col, FormGroup, Table } from "reactstrap";
+import { API_URL } from "@/env";
+
+function DetailSanksi({ data, noTitle = false }) {
+	return (
+		<>
+			{noTitle ? "" : <p className="lead bb">Detail Sanksi</p>}
+			<form className="form-horizontal">
+				<FormGroup row>
+					<Col md="4">Nomor Sanksi:</Col>
+					<Col md="8">
+						<strong>{data.no_sanksi}</strong>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Nama Perguruan Tinggi:</Col>
+					<Col md="8">
+						<strong>{data.laporan.pt.nama}</strong>
+					</Col>
+				</FormGroup>
+
+				<FormGroup row>
+					<Col md="4">Keterangan:</Col>
+					<Col md="8">
+						<Scrollable height="100px" className="list-group">
+							<p>{data.keterangan}</p>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Dibuat Pada:</Col>
+					<Col md="8">
+						<strong>{moment(data.createdAt).format("D MMMM YYYY")}</strong>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md="4">Dokumen Sanksi:</Col>
+					<Col md="8">
+						<Scrollable height="120px" className="list-group">
+							<table className="table table-bordered bg-transparent">
+								<tbody>
+									{data.dokumen.map((e) => (
+										<tr>
+											<td>
+												<em className="fa-lg far fa-file-code"></em>
+											</td>
+											<td>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</td>
+										</tr>
+									))}
+								</tbody>
+							</table>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+				<FormGroup row>
+					<Col md={12}>
+						<div className="card b">
+							<div className="card-body bb">
+								<Table responsive>
+									<thead>
+										<tr>
+											<th>Jenis Pelanggaran</th>
+											<th>Sanksi</th>
+										</tr>
+									</thead>
+									<tbody>
+										{data.pelanggaran.map((jp, index) => (
+											<tr key={jp._id}>
+												<td width={50}>
+													<div className="media align-items-center">
+														<div className="media-body d-flex">
+															<div>
+																<p>{jp.pelanggaran}</p>
+																<p>TMT : {jp.tmt_bulan} Bulan</p>
+																<p>Jenis Sanksi Administratif : {jp.label_sanksi}</p>
+															</div>
+														</div>
+													</div>
+												</td>
+												<td width={50}>
+													<div className="media align-items-center">
+														<div className="media-body d-flex">
+															<div>
+																<p>{jp.sanksi}</p>
+																<p>Keterangan : {jp.keterangan_sanksi}</p>
+															</div>
+														</div>
+													</div>
+												</td>
+											</tr>
+										))}
+									</tbody>
+								</Table>
+							</div>
+						</div>
+					</Col>
+				</FormGroup>
+			</form>
+		</>
+	);
+}
+
+export default DetailSanksi;

+ 18 - 18
components/Main/Header.js

@@ -1,18 +1,18 @@
-function Header({ data }) {
-	const styleHeaderText = {
-		color: "brown",
-	};
-
-	return (
-		<div className="bg-cover" style={{ backgroundImage: "url(/static/img/profile-bg.png)" }}>
-			<div className="p-4 text-center" style={styleHeaderText}>
-				<img className="img-thumbnail rounded-circle thumb128" src="/static/img/univ-avatar.png" alt="Avatar" />
-				<h3 className="m-0">{data.nama}</h3>
-				<p>{data.sk_pendirian}</p>
-				<p>{`${data.alamat.jalan} ${data.alamat.rt ? `rt ${data.alamat.rt}` : ""} ${data.alamat.rw ? `rt ${data.alamat.rw}` : ""}, ${data.alamat.kab_kota.nama}, ${data.propinsi.nama}`}</p>
-			</div>
-		</div>
-	);
-}
-
-export default Header;
+function Header({ data }) {
+	const styleHeaderText = {
+		color: "brown",
+	};
+
+	return (
+		<div className="bg-cover" style={{ backgroundImage: "url(/static/img/profile-bg.png)" }}>
+			<div className="p-4 text-center" style={styleHeaderText}>
+				<img className="img-thumbnail rounded-circle thumb128" src="/static/img/univ-avatar.png" alt="Avatar" />
+				<h3 className="m-0">{data.nama}</h3>
+				<p>{data.sk_pendirian}</p>
+				<p>{`${data.alamat.jalan} ${data.alamat.rt ? `rt ${data.alamat.rt}` : ""} ${data.alamat.rw ? `rt ${data.alamat.rw}` : ""}, ${data.alamat.kab_kota.nama}, ${data.propinsi.nama}`}</p>
+			</div>
+		</div>
+	);
+}
+
+export default Header;

+ 1 - 1
components/Main/Login.js

@@ -87,7 +87,7 @@ class Login extends Component {
 
 	render() {
 		return (
-			<Card className="card card-flat">
+			<Card className="card-login-over">
 				<img className="img-login-1" src="/static/img/logo-login.png" alt="Logo" />
 				<CardBody className="card-body">
 					{" "}

+ 37 - 37
components/Main/PermohonanPT.js

@@ -1,37 +1,37 @@
-import Scrollable from "@/components/Common/Scrollable";
-import { Col, FormGroup } from "reactstrap";
-
-function PermohonanPT({ data, title = null }) {
-	return (
-		<>
-			<p className="lead bb">{title || "Permohonan dari PT"}</p>
-			<form className="form-horizontal">
-				<FormGroup row>
-					<Col md="4">Dokumen Permohonan:</Col>
-					<Col md="8">
-						<Scrollable height="120px" className="list-group">
-							<table className="table table-bordered bg-transparent">
-								<tbody>
-									{data.dokumen.map((e) => (
-										<tr>
-											<td>
-												<em className="fa-lg far fa-file-code"></em>
-											</td>
-											<td>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</td>
-										</tr>
-									))}
-								</tbody>
-							</table>
-						</Scrollable>
-					</Col>
-				</FormGroup>
-			</form>
-		</>
-	);
-}
-
-export default PermohonanPT;
+import Scrollable from "@/components/Common/Scrollable";
+import { Col, FormGroup } from "reactstrap";
+
+function PermohonanPT({ data, title = null }) {
+	return (
+		<>
+			<p className="lead bb">{title || "Permohonan dari PT"}</p>
+			<form className="form-horizontal">
+				<FormGroup row>
+					<Col md="4">Dokumen Permohonan:</Col>
+					<Col md="8">
+						<Scrollable height="120px" className="list-group">
+							<table className="table table-bordered bg-transparent">
+								<tbody>
+									{data.dokumen.map((e) => (
+										<tr>
+											<td>
+												<em className="fa-lg far fa-file-code"></em>
+											</td>
+											<td>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</td>
+										</tr>
+									))}
+								</tbody>
+							</table>
+						</Scrollable>
+					</Col>
+				</FormGroup>
+			</form>
+		</>
+	);
+}
+
+export default PermohonanPT;

+ 81 - 81
components/Main/PublicPage.js

@@ -1,81 +1,81 @@
-import React, { Component } from "react";
-import BasePage from "@/components/Layout/BasePage";
-import { getPT } from "@/actions/PT";
-import { getPelanggaran } from "@/actions/pelanggaran";
-import Select from "react-select";
-import AsyncSelect from "react-select/async";
-import { Row, Col, FormGroup, Input, Card, CardBody, Button, CustomInput, Navbar, NavItem, NavLink, NavbarBrand, NavbarToggler, Nav, Collapse } from "reactstrap";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-
-const menu = [
-	{
-		title: "Home",
-		path: "/app",
-	},
-	{
-		title: "Membuat Laporan",
-		path: "/laporan/new",
-	},
-	{
-		title: "Pemantauan",
-		path: "/pemantauan",
-	},
-	{
-		title: "Login",
-		path: "/login",
-	},
-];
-class PublicPage extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			isOpen: false,
-			inputValue: "",
-			stat: "Waiting to add files..",
-			pelaporanNumber: Math.floor(Date.now() * Math.random()),
-			nama: "",
-			alamat: "",
-			no_hp: "",
-			email: "",
-			fileIdentitas: null,
-			pelanggaran: [],
-			selectedPerguruanTinggi: {},
-			selectedJenis: [],
-			keteranganLaporan: "",
-			files: [],
-		};
-	}
-
-	render() {
-		return (
-			<div>
-				<Navbar color="info" expand="md" dark>
-					<NavbarBrand href="/">
-						<img className="img-fluid" src="/static/img/logo-single.png" alt="App Logo" /> Aldila Dikti
-					</NavbarBrand>
-					<NavbarToggler onClick={this.toggleCollapse} />
-					<Collapse isOpen={this.state.isOpen} navbar>
-						<Nav className="ml-auto" navbar>
-							{menu.map((e) => (
-								<NavItem active={e.path === this.props.pathname ? true : false}>
-									<NavLink href={e.path}>{e.title}</NavLink>
-								</NavItem>
-							))}
-						</Nav>
-					</Collapse>
-				</Navbar>
-				<ContentWrapper>
-					<Row>
-						<Col lg={8} className="block-center d-block ">
-							{this.props.children}
-						</Col>
-					</Row>
-				</ContentWrapper>
-			</div>
-		);
-	}
-}
-
-PublicPage.Layout = BasePage;
-
-export default PublicPage;
+import React, { Component } from "react";
+import BasePage from "@/components/Layout/BasePage";
+import { getPT } from "@/actions/PT";
+import { getPelanggaran } from "@/actions/pelanggaran";
+import Select from "react-select";
+import AsyncSelect from "react-select/async";
+import { Row, Col, FormGroup, Input, Card, CardBody, Button, CustomInput, Navbar, NavItem, NavLink, NavbarBrand, NavbarToggler, Nav, Collapse } from "reactstrap";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+
+const menu = [
+	{
+		title: "Home",
+		path: "/app",
+	},
+	{
+		title: "Membuat Laporan",
+		path: "/laporan/new",
+	},
+	{
+		title: "Pemantauan",
+		path: "/pemantauan",
+	},
+	{
+		title: "Login",
+		path: "/login",
+	},
+];
+class PublicPage extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			isOpen: false,
+			inputValue: "",
+			stat: "Waiting to add files..",
+			pelaporanNumber: Math.floor(Date.now() * Math.random()),
+			nama: "",
+			alamat: "",
+			no_hp: "",
+			email: "",
+			fileIdentitas: null,
+			pelanggaran: [],
+			selectedPerguruanTinggi: {},
+			selectedJenis: [],
+			keteranganLaporan: "",
+			files: [],
+		};
+	}
+
+	render() {
+		return (
+			<div>
+				<Navbar color="info" expand="md" dark>
+					<NavbarBrand href="/">
+						<img className="img-fluid" src="/static/img/logo-single.png" alt="App Logo" /> Aldila Dikti
+					</NavbarBrand>
+					<NavbarToggler onClick={this.toggleCollapse} />
+					<Collapse isOpen={this.state.isOpen} navbar>
+						<Nav className="ml-auto" navbar>
+							{menu.map((e) => (
+								<NavItem active={e.path === this.props.pathname ? true : false}>
+									<NavLink href={e.path}>{e.title}</NavLink>
+								</NavItem>
+							))}
+						</Nav>
+					</Collapse>
+				</Navbar>
+				<ContentWrapper>
+					<Row>
+						<Col lg={8} className="block-center d-block ">
+							{this.props.children}
+						</Col>
+					</Row>
+				</ContentWrapper>
+			</div>
+		);
+	}
+}
+
+PublicPage.Layout = BasePage;
+
+export default PublicPage;

+ 39 - 39
components/Main/RiwayatEvaluasi.js

@@ -1,39 +1,39 @@
-import Datatable from "@/components/Tables/Datatable";
-
-function RiwayatEvaluasi({ listData }) {
-	return (
-		<Datatable options={{ responsive: true }}>
-			<table className="table table-striped my-4 w-100">
-				<thead>
-					<tr>
-						<th>Tanggal</th>
-						<th>Judul Dokumen</th>
-						<th>File Pendukung</th>
-					</tr>
-				</thead>
-				<tbody>
-					{listData.map((data) => (
-						<tr>
-							<td>{data.tanggal}</td>
-							<td>{data.judul_dokumen}</td>
-							<td>
-								{data.file
-									.map((e) => (
-										<>
-											<em className="fa-lg far fa-file-code"></em>
-											<a className="text-muted" href="">
-												database.controller.js
-											</a>
-										</>
-									))
-									.join(",")}
-							</td>
-						</tr>
-					))}
-				</tbody>
-			</table>
-		</Datatable>
-	);
-}
-
-export default RiwayatEvaluasi;
+import Datatable from "@/components/Tables/Datatable";
+
+function RiwayatEvaluasi({ listData }) {
+	return (
+		<Datatable options={{ responsive: true }}>
+			<table className="table table-striped my-4 w-100">
+				<thead>
+					<tr>
+						<th>Tanggal</th>
+						<th>Judul Dokumen</th>
+						<th>File Pendukung</th>
+					</tr>
+				</thead>
+				<tbody>
+					{listData.map((data) => (
+						<tr>
+							<td>{data.tanggal}</td>
+							<td>{data.judul_dokumen}</td>
+							<td>
+								{data.file
+									.map((e) => (
+										<>
+											<em className="fa-lg far fa-file-code"></em>
+											<a className="text-muted" href="">
+												database.controller.js
+											</a>
+										</>
+									))
+									.join(",")}
+							</td>
+						</tr>
+					))}
+				</tbody>
+			</table>
+		</Datatable>
+	);
+}
+
+export default RiwayatEvaluasi;

+ 82 - 73
components/Main/TableLaporan.js

@@ -1,73 +1,82 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableLaporan({ listData, to, linkName, status = false, noBy = false }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				{listData && (
-					<Datatable options={{ responsive: false }}>
-						<table className="table w-100">
-							<thead>
-								<tr>
-									<th>No.Laporan</th>
-									<th>Deskripsi Laporan</th>
-									{status ? <th>Status</th> : ""}
-									{!noBy && <th>Dibuat Oleh</th>}
-									<th>Created</th>
-									<th></th>
-								</tr>
-							</thead>
-							<tbody>
-								{listData.map((data) => {
-									return (
-										<tr key={data._id}>
-											<td>{data.no_laporan}</td>
-											<td className="text-nowrap">
-												<div className="media align-items-center">
-													<div className="media-body d-flex">
-														<div>
-															<h4 className="m-0">{data.pt.nama}</h4>
-															<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
-														</div>
-													</div>
-												</div>
-											</td>
-											{status ? (
-												<td>
-													<div className="badge badge-info">{data.role_data === "dikti" ? "Ditindaklanjuti DIKTI" : "Ditindaklanjuti LLDIKTI"}</div>
-												</td>
-											) : (
-												""
-											)}
-											{!noBy && <td>{data.user.isPrivate ? "" : data.user.nama}</td>}
-											<td>{moment(data.createdAt).fromNow()}</td>
-											<td>
-												<div className="ml-auto">
-													<Link
-														href={{
-															pathname: to,
-															query: { id: data._id },
-														}}
-													>
-														<Button color="primary" size="sm">
-															{linkName}
-														</Button>
-													</Link>
-												</div>
-											</td>
-										</tr>
-									);
-								})}
-							</tbody>
-						</table>
-					</Datatable>
-				)}
-			</div>
-		</div>
-	);
-}
-
-export default TableLaporan;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableLaporan({ listData, to, linkName, status = false, noBy = false }) {
+	return (
+		<div className="card b ">
+			<div className="card-body card-over">
+				{listData && (
+					<Datatable options={{ responsive: false, ordering: true, keys: true }}>
+						<table className="table w-100" data-order='[[3, "desc"]]'>
+							<thead>
+								<tr>
+									<th>No.Laporan</th>
+									<th>Deskripsi Laporan</th>
+									{status && <th>Status</th>}
+									{!noBy && <th>Dibuat Oleh</th>}
+									{/* <th>Created</th> */}
+									<th></th>
+								</tr>
+							</thead>
+							<tbody>
+								{listData.map((data) => {
+									return (
+										<tr key={data._id}>
+											<td>{data.no_laporan}</td>
+											<td className="text-nowrap">
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															<h4 className="m-0">{data.pt.nama}</h4>
+															<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+											{status ? (
+												<td>
+													<div className="badge badge-info">{data.role_data === "dikti" ? "Ditindaklanjuti DIKTI" : "Ditindaklanjuti LLDIKTI"}</div>
+												</td>
+											) : (
+												""
+											)}
+											<td className="text-nowrap">
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															{!noBy && <h4>{data.user.isPrivate ? "" : data.user.nama}</h4>}
+															<p>{moment(data.createdAt).format("MM-DD-YYYY")}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+
+											<td>
+												<div className="ml-auto">
+													<Link
+														href={{
+															pathname: to,
+															query: { id: data._id },
+														}}
+													>
+														<Button color="primary" size="sm">
+															{linkName}
+														</Button>
+													</Link>
+												</div>
+											</td>
+										</tr>
+									);
+								})}
+							</tbody>
+						</table>
+					</Datatable>
+				)}
+			</div>
+		</div>
+	);
+}
+
+export default TableLaporan;

+ 66 - 66
components/Main/TableSanksi.js

@@ -1,66 +1,66 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				{listData && (
-					<Datatable options={{ responsive: false }}>
-						<table className="table w-100">
-							<thead>
-								<tr>
-									<th>Nomor Sanksi</th>
-									<th>Keterangan Sanksi</th>
-									{/* {listData?.user.nama && <th>Dibuat Oleh</th>} */}
-									<th>Created</th>
-									<th></th>
-								</tr>
-							</thead>
-							<tbody>
-								{listData.length
-									? listData.map((data) => {
-											return (
-												<tr key={data._id}>
-													<td>{data.no_sanksi}</td>
-													<td>
-														<div className="media align-items-center">
-															<div className="media-body d-flex">
-																<div>
-																	<h4 className="m-0">{data.laporan.pt.nama}</h4>
-																	<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
-																</div>
-															</div>
-														</div>
-													</td>
-													<td>{moment(data.createdAt).fromNow()}</td>
-													<td>
-														<div className="ml-auto">
-															<Link
-																href={{
-																	pathname: to,
-																	query: { id: data._id },
-																}}
-															>
-																<Button color="primary" size="sm">
-																	{linkName}
-																</Button>
-															</Link>
-														</div>
-													</td>
-												</tr>
-											);
-									  })
-									: ""}
-							</tbody>
-						</table>
-					</Datatable>
-				)}
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body">
+				{listData && (
+					<Datatable options={{ responsive: false }}>
+						<table className="table w-100">
+							<thead>
+								<tr>
+									<th>Nomor Sanksi</th>
+									<th>Keterangan Sanksi</th>
+									{/* {listData?.user.nama && <th>Dibuat Oleh</th>} */}
+									<th>Created</th>
+									<th></th>
+								</tr>
+							</thead>
+							<tbody>
+								{listData.length
+									? listData.map((data) => {
+											return (
+												<tr key={data._id}>
+													<td>{data.no_sanksi}</td>
+													<td>
+														<div className="media align-items-center">
+															<div className="media-body d-flex">
+																<div>
+																	<h4 className="m-0">{data.laporan.pt.nama}</h4>
+																	<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
+																</div>
+															</div>
+														</div>
+													</td>
+													<td>{moment(data.createdAt).fromNow()}</td>
+													<td>
+														<div className="ml-auto">
+															<Link
+																href={{
+																	pathname: to,
+																	query: { id: data._id },
+																}}
+															>
+																<Button color="primary" size="sm">
+																	{linkName}
+																</Button>
+															</Link>
+														</div>
+													</td>
+												</tr>
+											);
+									  })
+									: ""}
+							</tbody>
+						</table>
+					</Datatable>
+				)}
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 100 - 100
components/Main/Timeline.js

@@ -1,100 +1,100 @@
-import moment from "moment";
-import { useSelector } from "react-redux";
-
-function Timeline({ data, noFile = false, noJadwal = false }) {
-	const date = data && [...new Set(data.map((e) => moment(e.createdAt).format("DD MMMM YYYY")))];
-	const user = useSelector((state) => state.user);
-	return (
-		<ul className="timeline-alt">
-			{date.map((value) => (
-				<>
-					<li className="timeline-separator" data-datetime={value}></li>
-					{data
-						.filter((e) => moment(e.createdAt).format("DD MMMM YYYY") === value)
-						.map((data, i) => (
-							<>
-								<li className={data.user.role?.id === 2022 ? "timeline-inverted" : ""}>
-									<div className={`timeline-badge ${data.user.role?.id === 2022 ? " danger" : "info"}`}>
-										<em className={`fas fa-${data.user.role?.id === 2022 ? "graduation-cap" : "file"}`}></em>
-									</div>
-
-									<div className="timeline-card">
-										<div className="popover right">
-											<div className="arrow"></div>
-											<div className="popover-body">
-												<div className="d-flex align-items-center mb-3">
-													<img
-														className="mr-3 rounded-circle thumb48"
-														src={`/static/img${data.user.role?.id === 2022 ? "/univ-avatar.png" : data.user.isPublic ? "/user/user.png" : "/logo-single.png"}`}
-														alt="Avatar"
-													/>
-													<p className="m-0">
-														<strong>
-															{!data.user.isPrivate && data.user.nama ? data.user.nama : "Rahasia"} - {data.user.isPublic ? "Umum" : data.user.role.nama}
-														</strong>
-														<br />
-														{data.keterangan}
-														<br />
-														{data.alasan && (
-															<>
-																{data.alasan}
-																<br />
-															</>
-														)}
-														{data.sanksi?.no_sanksi ? `No. Sanksi ${data.sanksi.no_sanksi}` : `No. Laporan ${data.laporan.no_laporan}`}
-														<p className="text-muted m-0">{moment(data.createdAt).format("hh:mm")}</p>
-													</p>
-												</div>
-												{!data.user.isPublic && user?.role.id !== 2022 && data.jawaban && (
-													<>
-														<p className="text-muted my-2">Jawaban</p>
-														<div className="p-2">
-															<b>{data.jawaban}</b>
-														</div>
-													</>
-												)}
-												{!data.user.isPublic && data.jadwal && (
-													<>
-														<p className="text-muted my-2">Jadwal Pemeriksaan</p>
-														<div className="p-2">
-															Tanggal {moment(data.jadwal.dari_tanggal).format("DD MMMM YYYY")} - {moment(data.jadwal.sampai_tanggal).format("DD MMMM YYYY")}
-														</div>
-													</>
-												)}
-												{!data.user.isPublic && data.dokumen?.length ? (
-													<>
-														<p className="text-muted my-2">Dokumen</p>
-														{data.dokumen.map((e) => (
-															<div className="media bb p-2">
-																<div className="media-body">
-																	<p className="m-0">
-																		<a href={e.path} target="_blank" download={e.judul}>
-																			<strong>{e.judul}</strong>
-																		</a>
-																	</p>
-																</div>
-															</div>
-														))}
-													</>
-												) : (
-													""
-												)}
-											</div>
-										</div>
-									</div>
-								</li>
-							</>
-						))}
-				</>
-			))}
-
-			<li className="timeline-end">
-				<a className="timeline-badge">
-					<em className="fa fa-plus"></em>
-				</a>
-			</li>
-		</ul>
-	);
-}
-
-export default Timeline;
+import moment from "moment";
+import { useSelector } from "react-redux";
+
+function Timeline({ data, noFile = false, noJadwal = false }) {
+	const date = data && [...new Set(data.map((e) => moment(e.createdAt).format("DD MMMM YYYY")))];
+	const user = useSelector((state) => state.user);
+	return (
+		<ul className="timeline-alt">
+			{date.map((value) => (
+				<>
+					<li className="timeline-separator" data-datetime={value}></li>
+					{data
+						.filter((e) => moment(e.createdAt).format("DD MMMM YYYY") === value)
+						.map((data, i) => (
+							<>
+								<li className={data.user.role?.id === 2022 ? "timeline-inverted" : ""}>
+									<div className={`timeline-badge ${data.user.role?.id === 2022 ? " danger" : "info"}`}>
+										<em className={`fas fa-${data.user.role?.id === 2022 ? "graduation-cap" : "file"}`}></em>
+									</div>
+
+									<div className="timeline-card">
+										<div className="popover right">
+											<div className="arrow"></div>
+											<div className="popover-body">
+												<div className="d-flex align-items-center mb-3">
+													<img
+														className="mr-3 rounded-circle thumb48"
+														src={`/static/img${data.user.role?.id === 2022 ? "/univ-avatar.png" : data.user.isPublic ? "/user/user.png" : "/logo-single.png"}`}
+														alt="Avatar"
+													/>
+													<p className="m-0">
+														<strong>
+															{!data.user.isPrivate && data.user.nama ? data.user.nama : "Rahasia"} - {data.user.isPublic ? "Umum" : data.user.role.nama}
+														</strong>
+														<br />
+														{data.keterangan}
+														<br />
+														{data.alasan && (
+															<>
+																{data.alasan}
+																<br />
+															</>
+														)}
+														{data.sanksi?.no_sanksi ? `No. Sanksi ${data.sanksi.no_sanksi}` : `No. Laporan ${data.laporan.no_laporan}`}
+														<p className="text-muted m-0">{moment(data.createdAt).format("hh:mm")}</p>
+													</p>
+												</div>
+												{!data.user.isPublic && user?.role.id !== 2022 && data.jawaban && (
+													<>
+														<p className="text-muted my-2">Jawaban</p>
+														<div className="p-2">
+															<b>{data.jawaban}</b>
+														</div>
+													</>
+												)}
+												{!data.user.isPublic && data.jadwal && (
+													<>
+														<p className="text-muted my-2">Jadwal Pemeriksaan</p>
+														<div className="p-2">
+															Tanggal {moment(data.jadwal.dari_tanggal).format("DD MMMM YYYY")} - {moment(data.jadwal.sampai_tanggal).format("DD MMMM YYYY")}
+														</div>
+													</>
+												)}
+												{!data.user.isPublic && data.dokumen?.length ? (
+													<>
+														<p className="text-muted my-2">Dokumen</p>
+														{data.dokumen.map((e) => (
+															<div className="media bb p-2">
+																<div className="media-body">
+																	<p className="m-0">
+																		<a href={e.path} target="_blank" download={e.judul}>
+																			<strong>{e.judul}</strong>
+																		</a>
+																	</p>
+																</div>
+															</div>
+														))}
+													</>
+												) : (
+													""
+												)}
+											</div>
+										</div>
+									</div>
+								</li>
+							</>
+						))}
+				</>
+			))}
+
+			<li className="timeline-end">
+				<a className="timeline-badge">
+					<em className="fa fa-plus"></em>
+				</a>
+			</li>
+		</ul>
+	);
+}
+
+export default Timeline;

+ 55 - 55
components/Maps/VectorMap.js

@@ -1,56 +1,56 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import $ from 'jquery';
-
-import './vector-map.scss';
-
-/** Wrapper component for jquery-vectormap plugin */
-class VectorMap extends Component {
-
-    static propTypes = {
-        /** series entry of options object */
-        series: PropTypes.object.isRequired,
-        /** markers entry of options object */
-        markers: PropTypes.array.isRequired,
-        /** jvectormap options object */
-        options: PropTypes.object.isRequired,
-        /** height of the container element */
-        height: PropTypes.string
-    }
-
-    static defaultProps = {
-        height: '300px'
-    }
-
-    componentDidMount() {
-        // jquery Vector Map
-        require('ika.jvectormap/jquery-jvectormap-1.2.2.min.js');
-        require('ika.jvectormap/jquery-jvectormap-world-mill-en.js');
-        require('ika.jvectormap/jquery-jvectormap-us-mill-en.js');
-        require('ika.jvectormap/jquery-jvectormap-1.2.2.css');
-
-        window.requestAnimationFrame(() => this.drawMap());
-
-    }
-
-    drawMap() {
-        this.props.options.markers = this.props.markers;
-        this.props.options.series = this.props.series;
-        $(this.mapElement).vectorMap(this.props.options);
-    }
-
-    componentWillUnmount() {
-        const map = $(this.mapElement).vectorMap('get', 'mapObject');
-        map.remove()
-    }
-
-    setRef = node => this.mapElement = node
-
-    render() {
-        return (
-            <div ref={this.setRef} style={{height: this.props.height}}/>
-        )
-    }
-}
-
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import $ from 'jquery';
+
+import './vector-map.scss';
+
+/** Wrapper component for jquery-vectormap plugin */
+class VectorMap extends Component {
+
+    static propTypes = {
+        /** series entry of options object */
+        series: PropTypes.object.isRequired,
+        /** markers entry of options object */
+        markers: PropTypes.array.isRequired,
+        /** jvectormap options object */
+        options: PropTypes.object.isRequired,
+        /** height of the container element */
+        height: PropTypes.string
+    }
+
+    static defaultProps = {
+        height: '300px'
+    }
+
+    componentDidMount() {
+        // jquery Vector Map
+        require('ika.jvectormap/jquery-jvectormap-1.2.2.min.js');
+        require('ika.jvectormap/jquery-jvectormap-world-mill-en.js');
+        require('ika.jvectormap/jquery-jvectormap-us-mill-en.js');
+        require('ika.jvectormap/jquery-jvectormap-1.2.2.css');
+
+        window.requestAnimationFrame(() => this.drawMap());
+
+    }
+
+    drawMap() {
+        this.props.options.markers = this.props.markers;
+        this.props.options.series = this.props.series;
+        $(this.mapElement).vectorMap(this.props.options);
+    }
+
+    componentWillUnmount() {
+        const map = $(this.mapElement).vectorMap('get', 'mapObject');
+        map.remove()
+    }
+
+    setRef = node => this.mapElement = node
+
+    render() {
+        return (
+            <div ref={this.setRef} style={{height: this.props.height}}/>
+        )
+    }
+}
+
 export default VectorMap;

+ 45 - 45
components/Maps/vector-map.scss

@@ -1,45 +1,45 @@
-/* ========================================================================
-     Component: vector-map
- ========================================================================== */
-
-$vmap-label-bg: #313232;
-$vmap-zoom-ctrl-bg: #515253;
-
-body {
-    // adds priority
-
-    .jvectormap-label {
-        position: absolute;
-        display: none;
-        border: solid 1px $vmap-label-bg;
-        border-radius: 2px;
-        background: $vmap-label-bg;
-        color: white;
-        padding: 3px 6px;
-        opacity: 0.9;
-        z-index: 1100;
-    }
-
-    .jvectormap-zoomin, .jvectormap-zoomout {
-        position: absolute;
-        left: 10px;
-        width: 22px;
-        height: 22px;
-        border-radius: 2px;
-        background: $vmap-zoom-ctrl-bg;
-        padding: 5px;
-        color: white;
-        cursor: pointer;
-        line-height: 10px;
-        text-align: center;
-    }
-
-    .jvectormap-zoomin {
-        top: 10px;
-    }
-
-    .jvectormap-zoomout {
-        top: 30px;
-    }
-
-}
+/* ========================================================================
+     Component: vector-map
+ ========================================================================== */
+
+$vmap-label-bg: #313232;
+$vmap-zoom-ctrl-bg: #515253;
+
+body {
+    // adds priority
+
+    .jvectormap-label {
+        position: absolute;
+        display: none;
+        border: solid 1px $vmap-label-bg;
+        border-radius: 2px;
+        background: $vmap-label-bg;
+        color: white;
+        padding: 3px 6px;
+        opacity: 0.9;
+        z-index: 1100;
+    }
+
+    .jvectormap-zoomin, .jvectormap-zoomout {
+        position: absolute;
+        left: 10px;
+        width: 22px;
+        height: 22px;
+        border-radius: 2px;
+        background: $vmap-zoom-ctrl-bg;
+        padding: 5px;
+        color: white;
+        cursor: pointer;
+        line-height: 10px;
+        text-align: center;
+    }
+
+    .jvectormap-zoomin {
+        top: 10px;
+    }
+
+    .jvectormap-zoomout {
+        top: 30px;
+    }
+
+}

+ 49 - 49
components/PT/CabutSanksi/Riwayat.js

@@ -1,49 +1,49 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-import { API_URL } from "@/env";
-
-function Riwayat({ data }) {
-	const { cabut_sanksi } = data.sanksi;
-	console.log(data);
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{cabut_sanksi ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{cabut_sanksi.files.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={API_URL + e.path} target="_blank" download={e.name}>
-													{e.name}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+import { API_URL } from "@/env";
+
+function Riwayat({ data }) {
+	const { cabut_sanksi } = data.sanksi;
+	console.log(data);
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{cabut_sanksi ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{cabut_sanksi.files.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={API_URL + e.path} target="_blank" download={e.name}>
+													{e.name}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 55 - 55
components/PT/CabutSanksi/TableSanksiJawaban.js

@@ -1,55 +1,55 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.sanksi.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.pt.nama}</h4>
-													{/* <small className="text-muted">0742/O/1990 - www.satyagama.ac.id - info@satyagama.ac.id</small> */}
-													<p>{data.sanksi.description.length > 70 ? data.sanksi.description.substring(0, 70) + "..." : data.sanksi.description}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.sanksi.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.sanksi.cabut_sanksi?.jawaban ? (
-											<Link href={{ pathname: to, query: { noSanksi: data.sanksi.no_sanksi } }}>
-												<Button color="primary">{linkName}</Button>
-											</Link>
-										) : (
-											<div className="badge-info badge">Menunggu Jawaban</div>
-										)}
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.sanksi.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.pt.nama}</h4>
+													{/* <small className="text-muted">0742/O/1990 - www.satyagama.ac.id - info@satyagama.ac.id</small> */}
+													<p>{data.sanksi.description.length > 70 ? data.sanksi.description.substring(0, 70) + "..." : data.sanksi.description}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.sanksi.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.sanksi.cabut_sanksi?.jawaban ? (
+											<Link href={{ pathname: to, query: { noSanksi: data.sanksi.no_sanksi } }}>
+												<Button color="primary">{linkName}</Button>
+											</Link>
+										) : (
+											<div className="badge-info badge">Menunggu Jawaban</div>
+										)}
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 48 - 48
components/PT/DocPerbaikan/Riwayat.js

@@ -1,48 +1,48 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Ketarangan</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data.length
-								? data.map((value) => (
-										<tr>
-											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
-											<td>{value.keterangan}</td>
-											<td>
-												{value.dokumen.map((e) => (
-													<>
-														<em className="fa-lg far fa-file-code"></em>
-														<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-															{e.judul}
-														</a>
-													</>
-												))}
-											</td>
-										</tr>
-								  ))
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Ketarangan</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data.length
+								? data.map((value) => (
+										<tr>
+											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
+											<td>{value.keterangan}</td>
+											<td>
+												{value.dokumen.map((e) => (
+													<>
+														<em className="fa-lg far fa-file-code"></em>
+														<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+															{e.judul}
+														</a>
+													</>
+												))}
+											</td>
+										</tr>
+								  ))
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 58 - 58
components/PT/JawabanBanding/DetailJawaban.js

@@ -1,58 +1,58 @@
-import { FormGroup, Button } from "reactstrap";
-import Scrollable from "@/components/Common/Scrollable";
-import Link from "next/link";
-
-function DetailJawaban({ data, sanksiId }) {
-	return (
-		<>
-			<p className="lead bb">Jawaban Permohonan Banding</p>
-			<form className="form-horizontal">
-				<FormGroup>
-					<label md="4">Jawaban:</label>
-					<div md="8">
-						<h3>{data.status}</h3>
-					</div>
-				</FormGroup>
-				{data.dokumen.length ? (
-					<FormGroup>
-						<label md="4">Dokumen Jawaban:</label>
-						<div md="8">
-							<Scrollable height="120px" className="list-group">
-								<table className="table table-bordered bg-transparent">
-									<tbody>
-										{data.dokumen.map((e) => (
-											<tr>
-												<td>
-													<em className="fa-lg far fa-file-code"></em>
-												</td>
-												<td>
-													<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-														{e.judul}
-													</a>
-												</td>
-											</tr>
-										))}
-									</tbody>
-								</table>
-							</Scrollable>
-						</div>
-					</FormGroup>
-				) : (
-					""
-				)}
-			</form>
-			{data.status === "Ditolak" && (
-				<Link
-					href={{
-						pathname: "/pt/dokumen-perbaikan/detail",
-						query: { id: sanksiId },
-					}}
-				>
-					<Button color="primary">Perbaiki Dokumen</Button>
-				</Link>
-			)}
-		</>
-	);
-}
-
-export default DetailJawaban;
+import { FormGroup, Button } from "reactstrap";
+import Scrollable from "@/components/Common/Scrollable";
+import Link from "next/link";
+
+function DetailJawaban({ data, sanksiId }) {
+	return (
+		<>
+			<p className="lead bb">Jawaban Permohonan Banding</p>
+			<form className="form-horizontal">
+				<FormGroup>
+					<label md="4">Jawaban:</label>
+					<div md="8">
+						<h3>{data.status}</h3>
+					</div>
+				</FormGroup>
+				{data.dokumen.length ? (
+					<FormGroup>
+						<label md="4">Dokumen Jawaban:</label>
+						<div md="8">
+							<Scrollable height="120px" className="list-group">
+								<table className="table table-bordered bg-transparent">
+									<tbody>
+										{data.dokumen.map((e) => (
+											<tr>
+												<td>
+													<em className="fa-lg far fa-file-code"></em>
+												</td>
+												<td>
+													<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+														{e.judul}
+													</a>
+												</td>
+											</tr>
+										))}
+									</tbody>
+								</table>
+							</Scrollable>
+						</div>
+					</FormGroup>
+				) : (
+					""
+				)}
+			</form>
+			{data.status === "Ditolak" && (
+				<Link
+					href={{
+						pathname: "/pt/dokumen-perbaikan/detail",
+						query: { id: sanksiId },
+					}}
+				>
+					<Button color="primary">Perbaiki Dokumen</Button>
+				</Link>
+			)}
+		</>
+	);
+}
+
+export default DetailJawaban;

+ 54 - 54
components/PT/JawabanBanding/TableSanksiJawaban.js

@@ -1,54 +1,54 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.laporan.pt.nama}</h4>
-													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.jawaban?.banding ? (
-											<Link href={{ pathname: to, query: { id: data._id } }}>
-												<Button color="primary">{linkName}</Button>
-											</Link>
-										) : (
-											<div className="badge-info badge">Menunggu Jawaban</div>
-										)}
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.laporan.pt.nama}</h4>
+													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.jawaban?.banding ? (
+											<Link href={{ pathname: to, query: { id: data._id } }}>
+												<Button color="primary">{linkName}</Button>
+											</Link>
+										) : (
+											<div className="badge-info badge">Menunggu Jawaban</div>
+										)}
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 49 - 49
components/PT/JawabanKeberatan/DetailJawaban.js

@@ -1,49 +1,49 @@
-import { FormGroup } from "reactstrap";
-import Scrollable from "@/components/Common/Scrollable";
-
-function DetailJawaban({ data }) {
-	return (
-		<>
-			<p className="lead bb">Jawaban Permohonan Keberatan</p>
-			<form className="form-horizontal">
-				<FormGroup>
-					<label md="4">Jawaban:</label>
-					<div md="8">
-						<h3>{data.status}</h3>
-					</div>
-				</FormGroup>
-				<FormGroup>
-					<label md="4">Keterangan:</label>
-					<div md="8">
-						<p>{data.keterangan}</p>
-					</div>
-				</FormGroup>
-				<FormGroup>
-					<label md="4">Dokumen Jawaban:</label>
-					<div md="8">
-						<Scrollable height="120px" className="list-group">
-							<table className="table table-bordered bg-transparent">
-								<tbody>
-									{data.dokumen.map((e) => (
-										<tr>
-											<td>
-												<em className="fa-lg far fa-file-code"></em>
-											</td>
-											<td>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</td>
-										</tr>
-									))}
-								</tbody>
-							</table>
-						</Scrollable>
-					</div>
-				</FormGroup>
-			</form>
-		</>
-	);
-}
-
-export default DetailJawaban;
+import { FormGroup } from "reactstrap";
+import Scrollable from "@/components/Common/Scrollable";
+
+function DetailJawaban({ data }) {
+	return (
+		<>
+			<p className="lead bb">Jawaban Permohonan Keberatan</p>
+			<form className="form-horizontal">
+				<FormGroup>
+					<label md="4">Jawaban:</label>
+					<div md="8">
+						<h3>{data.status}</h3>
+					</div>
+				</FormGroup>
+				<FormGroup>
+					<label md="4">Keterangan:</label>
+					<div md="8">
+						<p>{data.keterangan}</p>
+					</div>
+				</FormGroup>
+				<FormGroup>
+					<label md="4">Dokumen Jawaban:</label>
+					<div md="8">
+						<Scrollable height="120px" className="list-group">
+							<table className="table table-bordered bg-transparent">
+								<tbody>
+									{data.dokumen.map((e) => (
+										<tr>
+											<td>
+												<em className="fa-lg far fa-file-code"></em>
+											</td>
+											<td>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</td>
+										</tr>
+									))}
+								</tbody>
+							</table>
+						</Scrollable>
+					</div>
+				</FormGroup>
+			</form>
+		</>
+	);
+}
+
+export default DetailJawaban;

+ 215 - 215
components/PT/JawabanKeberatan/ModalPermohonan.js

@@ -1,215 +1,215 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import { Row, Col, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
-import { addBanding } from "@/actions/banding";
-import { connect } from "react-redux";
-import { toast } from "react-toastify";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const evaluasiSchema = Yup.object().shape({
-	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Harus ada").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-export class ModalPermohonan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			modal1: false,
-			files: [],
-		};
-	}
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	toggleModal1 = () => {
-		this.props.toggleModal(false);
-		this.setState({
-			modal1: !this.state.modal1,
-		});
-	};
-
-	onSubmit = async (data) => {
-		const { query, token } = this.props;
-		const { id } = query;
-		const formdata = new FormData();
-		if (data.dokumen.length > 0) {
-			data.dokumen.forEach((e) => {
-				formdata.append("dokumen", e);
-			});
-		}
-		this.setState({
-			modal1: !this.state.modal1,
-		});
-
-		const toastid = toast.loading("Please wait...");
-		const added = await addBanding(token, id, formdata);
-
-		if (!added) {
-			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
-		} else {
-			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
-			Router.push({
-				pathname: "/pt/jawaban-banding",
-			});
-		}
-	};
-
-	render() {
-		const { files } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-		return (
-			<>
-				<Modal isOpen={this.props.modal} toggle={this.props.toggleModal}>
-					<ModalBody>Apakah anda akan mengajukan banding?</ModalBody>
-					<ModalFooter>
-						<Button color="primary" onClick={this.toggleModal1}>
-							Ya
-						</Button>{" "}
-						<Button color="secondary" onClick={this.props.toggleModal}>
-							Tidak
-						</Button>
-					</ModalFooter>
-				</Modal>
-				<Modal isOpen={this.state.modal1} toggle={this.toggleModal1}>
-					<ModalHeader toggle={this.toggleModal1}>Upload Dokumen Banding</ModalHeader>
-					<Formik
-						initialValues={{
-							dokumen: [],
-						}}
-						validationSchema={evaluasiSchema}
-						onSubmit={this.onSubmit}
-					>
-						<Form className="form-horizontal">
-							<ModalBody>
-								<FormGroup>
-									<label>Dalam hal mengajukan permohonan banding maka wajib mengunggah surat permohonan banding & dokumen pendukungnya</label>
-									<div>
-										<Field name="dokumen">
-											{({ field, form, meta }) => (
-												<DropzoneWrapper
-													className=""
-													onDrop={(e) => {
-														this.onDrop(e);
-														form.setFieldValue(field.name, e);
-													}}
-												>
-													{({ getRootProps, getInputProps, isDragActive }) => {
-														return (
-															<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-																<input {...getInputProps()} />
-																<div className="dropzone-previews flex">
-																	{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}
-																</div>
-																<div className="d-flex align-items-center">
-																	<small className="ml-auto">
-																		<button
-																			type="button"
-																			className="btn btn-link"
-																			onClick={(e) => {
-																				this.clearFiles(e);
-																				form.setFieldValue(field.name, []);
-																			}}
-																		>
-																			Clear files
-																		</button>
-																	</small>
-																</div>
-															</div>
-														);
-													}}
-												</DropzoneWrapper>
-											)}
-										</Field>
-										<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-										<p className="mrgn-top-5">
-											Ukuran setiap dokumen maksimal 15mb
-										</p>
-									</div>
-								</FormGroup>
-							</ModalBody>
-							<ModalFooter>
-								<Button color="primary" type="submit">
-									Kirim
-								</Button>
-							</ModalFooter>
-						</Form>
-					</Formik>
-				</Modal>
-			</>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(ModalPermohonan);
+import React, { Component } from "react";
+import Router from "next/router";
+import { Row, Col, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
+import { addBanding } from "@/actions/banding";
+import { connect } from "react-redux";
+import { toast } from "react-toastify";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const evaluasiSchema = Yup.object().shape({
+	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Harus ada").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+export class ModalPermohonan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			modal1: false,
+			files: [],
+		};
+	}
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	toggleModal1 = () => {
+		this.props.toggleModal(false);
+		this.setState({
+			modal1: !this.state.modal1,
+		});
+	};
+
+	onSubmit = async (data) => {
+		const { query, token } = this.props;
+		const { id } = query;
+		const formdata = new FormData();
+		if (data.dokumen.length > 0) {
+			data.dokumen.forEach((e) => {
+				formdata.append("dokumen", e);
+			});
+		}
+		this.setState({
+			modal1: !this.state.modal1,
+		});
+
+		const toastid = toast.loading("Please wait...");
+		const added = await addBanding(token, id, formdata);
+
+		if (!added) {
+			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
+		} else {
+			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
+			Router.push({
+				pathname: "/pt/jawaban-banding",
+			});
+		}
+	};
+
+	render() {
+		const { files } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+		return (
+			<>
+				<Modal isOpen={this.props.modal} toggle={this.props.toggleModal}>
+					<ModalBody>Apakah anda akan mengajukan banding?</ModalBody>
+					<ModalFooter>
+						<Button color="primary" onClick={this.toggleModal1}>
+							Ya
+						</Button>{" "}
+						<Button color="secondary" onClick={this.props.toggleModal}>
+							Tidak
+						</Button>
+					</ModalFooter>
+				</Modal>
+				<Modal isOpen={this.state.modal1} toggle={this.toggleModal1}>
+					<ModalHeader toggle={this.toggleModal1}>Upload Dokumen Banding</ModalHeader>
+					<Formik
+						initialValues={{
+							dokumen: [],
+						}}
+						validationSchema={evaluasiSchema}
+						onSubmit={this.onSubmit}
+					>
+						<Form className="form-horizontal">
+							<ModalBody>
+								<FormGroup>
+									<label>Dalam hal mengajukan permohonan banding maka wajib mengunggah surat permohonan banding & dokumen pendukungnya</label>
+									<div>
+										<Field name="dokumen">
+											{({ field, form, meta }) => (
+												<DropzoneWrapper
+													className=""
+													onDrop={(e) => {
+														this.onDrop(e);
+														form.setFieldValue(field.name, e);
+													}}
+												>
+													{({ getRootProps, getInputProps, isDragActive }) => {
+														return (
+															<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+																<input {...getInputProps()} />
+																<div className="dropzone-previews flex">
+																	{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}
+																</div>
+																<div className="d-flex align-items-center">
+																	<small className="ml-auto">
+																		<button
+																			type="button"
+																			className="btn btn-link"
+																			onClick={(e) => {
+																				this.clearFiles(e);
+																				form.setFieldValue(field.name, []);
+																			}}
+																		>
+																			Reset dokumen
+																		</button>
+																	</small>
+																</div>
+															</div>
+														);
+													}}
+												</DropzoneWrapper>
+											)}
+										</Field>
+										<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+										<p className="mrgn-top-5">
+											Ukuran setiap dokumen maksimal 15mb
+										</p>
+									</div>
+								</FormGroup>
+							</ModalBody>
+							<ModalFooter>
+								<Button color="primary" type="submit">
+									Kirim
+								</Button>
+							</ModalFooter>
+						</Form>
+					</Formik>
+				</Modal>
+			</>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(ModalPermohonan);

+ 46 - 46
components/PT/JawabanKeberatan/Riwayat.js

@@ -1,46 +1,46 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.dokumen.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.dokumen.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 55 - 55
components/PT/JawabanKeberatan/TableSanksiJawaban.js

@@ -1,55 +1,55 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.laporan.pt.nama}</h4>
-													{/* <small className="text-muted">0742/O/1990 - www.satyagama.ac.id - info@satyagama.ac.id</small> */}
-													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.jawaban?.keberatan ? (
-											<Link href={{ pathname: to, query: { id: data._id } }}>
-												<Button color="primary">{linkName}</Button>
-											</Link>
-										) : (
-											<div className="badge-info badge">Menunggu Jawaban</div>
-										)}
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											{/* <img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" /> */}
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.laporan.pt.nama}</h4>
+													{/* <small className="text-muted">0742/O/1990 - www.satyagama.ac.id - info@satyagama.ac.id</small> */}
+													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.jawaban?.keberatan ? (
+											<Link href={{ pathname: to, query: { id: data._id } }}>
+												<Button color="primary">{linkName}</Button>
+											</Link>
+										) : (
+											<div className="badge-info badge">Menunggu Jawaban</div>
+										)}
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 5 - 5
components/PT/JawabanPencabutanSanksi/DetailJawaban.js

@@ -1,5 +1,5 @@
-function DetailJawaban({data}) {
-	return <div>Enter</div>;
-}
-
-export default DetailJawaban;
+function DetailJawaban({data}) {
+	return <div>Enter</div>;
+}
+
+export default DetailJawaban;

+ 53 - 53
components/PT/JawabanPencabutanSanksi/TableSanksiJawaban.js

@@ -1,53 +1,53 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.laporan.pt.nama}</h4>
-													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.jawaban?.cabut_sanksi ? (
-											<Link href={{ pathname: to, query: { id: data._id } }}>
-												<Button color="primary">{linkName}</Button>
-											</Link>
-										) : (
-											<div className="badge-info badge">Menunggu Jawaban</div>
-										)}
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.laporan.pt.nama}</h4>
+													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.jawaban?.cabut_sanksi ? (
+											<Link href={{ pathname: to, query: { id: data._id } }}>
+												<Button color="primary">{linkName}</Button>
+											</Link>
+										) : (
+											<div className="badge-info badge">Menunggu Jawaban</div>
+										)}
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 217 - 217
components/PT/Keberatan/ModalPermohonan.js

@@ -1,217 +1,217 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import { Row, Col, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
-import { addKeberatan } from "@/actions/keberatan";
-import { connect } from "react-redux";
-import { toast } from "react-toastify";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const evaluasiSchema = Yup.object().shape({
-	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Required").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-
-export class ModalPermohonan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			modal1: false,
-			files: [],
-		};
-	}
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	toggleModal1 = () => {
-		this.setState({ error: null });
-		this.props.toggleModal(false);
-		this.setState({
-			modal1: !this.state.modal1,
-		});
-	};
-
-	onSubmit = async (data) => {
-		this.setState({
-			modal1: !this.state.modal1,
-		});
-		const { query, token } = this.props;
-		const { id } = query;
-		const formdata = new FormData();
-		if (data.dokumen.length > 0) {
-			data.dokumen.forEach((e) => {
-				formdata.append("dokumen", e);
-			});
-		}
-
-		const tostid = toast.loading("Please wait...");
-		const success = await addKeberatan(token, id, formdata);
-
-		if (!success) {
-			toast.update(tostid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
-		} else {
-			toast.update(tostid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
-			Router.push({
-				pathname: "/pt/jawaban-keberatan",
-			});
-		}
-	};
-
-	render() {
-		const { files } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-		return (
-			<>
-				<Modal isOpen={this.props.modal} toggle={this.props.toggleModal}>
-					<ModalBody>Apakah anda akan mengajukan permohonan keberatan atas pengenaan sanksi?</ModalBody>
-					<ModalFooter>
-						<Button color="primary" onClick={this.toggleModal1}>
-							Ya
-						</Button>{" "}
-						<Button color="secondary" onClick={this.props.toggleModal}>
-							Tidak
-						</Button>
-					</ModalFooter>
-				</Modal>
-				<Modal isOpen={this.state.modal1} toggle={this.toggleModal1}>
-					<ModalHeader toggle={this.toggleModal1}>Unggah Dokumen Permohonan Keberatan</ModalHeader>
-					<Formik
-						initialValues={{
-							dokumen: [],
-						}}
-						validationSchema={evaluasiSchema}
-						onSubmit={this.onSubmit}
-					>
-						<Form className="form-horizontal">
-							<ModalBody>
-								<FormGroup>
-									<label>Dalam hal mengajukan permohonan banding maka wajib mengunggah surat permohonan banding & dokumen pendukungnya</label>
-									<div>
-										<Field name="dokumen">
-											{({ field, form, meta }) => (
-												<DropzoneWrapper
-													className=""
-													onDrop={(e) => {
-														this.onDrop(e);
-														form.setFieldValue(field.name, e);
-													}}
-												>
-													{({ getRootProps, getInputProps, isDragActive }) => {
-														return (
-															<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-																<input {...getInputProps()} />
-																<div className="dropzone-previews flex">
-																	{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}
-																</div>
-																<div className="d-flex align-items-center">
-																	<small className="ml-auto">
-																		<button
-																			type="button"
-																			className="btn btn-link"
-																			onClick={(e) => {
-																				this.clearFiles(e);
-																				form.setFieldValue(field.name, []);
-																			}}
-																		>
-																			Clear files
-																		</button>
-																	</small>
-																</div>
-															</div>
-														);
-													}}
-												</DropzoneWrapper>
-											)}
-										</Field>
-										<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-										<p className="mrgn-top-5">
-											Ukuran setiap dokumen maksimal 15mb
-										</p>
-									</div>
-								</FormGroup>
-							</ModalBody>
-							<ModalFooter>
-								<Button color="primary" type="submit">
-									Kirim
-								</Button>
-							</ModalFooter>
-						</Form>
-					</Formik>
-				</Modal>
-			</>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(ModalPermohonan);
+import React, { Component } from "react";
+import Router from "next/router";
+import { Row, Col, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
+import { addKeberatan } from "@/actions/keberatan";
+import { connect } from "react-redux";
+import { toast } from "react-toastify";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const evaluasiSchema = Yup.object().shape({
+	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Required").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+
+export class ModalPermohonan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			modal1: false,
+			files: [],
+		};
+	}
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	toggleModal1 = () => {
+		this.setState({ error: null });
+		this.props.toggleModal(false);
+		this.setState({
+			modal1: !this.state.modal1,
+		});
+	};
+
+	onSubmit = async (data) => {
+		this.setState({
+			modal1: !this.state.modal1,
+		});
+		const { query, token } = this.props;
+		const { id } = query;
+		const formdata = new FormData();
+		if (data.dokumen.length > 0) {
+			data.dokumen.forEach((e) => {
+				formdata.append("dokumen", e);
+			});
+		}
+
+		const tostid = toast.loading("Please wait...");
+		const success = await addKeberatan(token, id, formdata);
+
+		if (!success) {
+			toast.update(tostid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
+		} else {
+			toast.update(tostid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
+			Router.push({
+				pathname: "/pt/jawaban-keberatan",
+			});
+		}
+	};
+
+	render() {
+		const { files } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+		return (
+			<>
+				<Modal isOpen={this.props.modal} toggle={this.props.toggleModal}>
+					<ModalBody>Apakah anda akan mengajukan permohonan keberatan atas pengenaan sanksi?</ModalBody>
+					<ModalFooter>
+						<Button color="primary" onClick={this.toggleModal1}>
+							Ya
+						</Button>{" "}
+						<Button color="secondary" onClick={this.props.toggleModal}>
+							Tidak
+						</Button>
+					</ModalFooter>
+				</Modal>
+				<Modal isOpen={this.state.modal1} toggle={this.toggleModal1}>
+					<ModalHeader toggle={this.toggleModal1}>Unggah Dokumen Permohonan Keberatan</ModalHeader>
+					<Formik
+						initialValues={{
+							dokumen: [],
+						}}
+						validationSchema={evaluasiSchema}
+						onSubmit={this.onSubmit}
+					>
+						<Form className="form-horizontal">
+							<ModalBody>
+								<FormGroup>
+									<label>Dalam hal mengajukan permohonan banding maka wajib mengunggah surat permohonan banding & dokumen pendukungnya</label>
+									<div>
+										<Field name="dokumen">
+											{({ field, form, meta }) => (
+												<DropzoneWrapper
+													className=""
+													onDrop={(e) => {
+														this.onDrop(e);
+														form.setFieldValue(field.name, e);
+													}}
+												>
+													{({ getRootProps, getInputProps, isDragActive }) => {
+														return (
+															<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+																<input {...getInputProps()} />
+																<div className="dropzone-previews flex">
+																	{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}
+																</div>
+																<div className="d-flex align-items-center">
+																	<small className="ml-auto">
+																		<button
+																			type="button"
+																			className="btn btn-link"
+																			onClick={(e) => {
+																				this.clearFiles(e);
+																				form.setFieldValue(field.name, []);
+																			}}
+																		>
+																			Reset dokumen
+																		</button>
+																	</small>
+																</div>
+															</div>
+														);
+													}}
+												</DropzoneWrapper>
+											)}
+										</Field>
+										<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+										<p className="mrgn-top-5">
+											Ukuran setiap dokumen maksimal 15mb
+										</p>
+									</div>
+								</FormGroup>
+							</ModalBody>
+							<ModalFooter>
+								<Button color="primary" type="submit">
+									Kirim
+								</Button>
+							</ModalFooter>
+						</Form>
+					</Formik>
+				</Modal>
+			</>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(ModalPermohonan);

+ 46 - 46
components/PT/Keberatan/Riwayat.js

@@ -1,46 +1,46 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.dokumen.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.dokumen.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 46 - 46
components/PT/Riwayat.js

@@ -1,46 +1,46 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data.length
-								? data.map((value) => (
-										<tr>
-											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
-											<td>
-												{value.dokumen.map((e) => (
-													<>
-														<em className="fa-lg far fa-file-code"></em>
-														<a className="text-muted" href={data.path} download={e.judul}>
-															{e.judul}
-														</a>
-													</>
-												))}
-											</td>
-										</tr>
-								  ))
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data.length
+								? data.map((value) => (
+										<tr>
+											<td>{moment(value.createAt).format("DD MMMM YYYY")}</td>
+											<td>
+												{value.dokumen.map((e) => (
+													<>
+														<em className="fa-lg far fa-file-code"></em>
+														<a className="text-muted" href={data.path} download={e.judul}>
+															{e.judul}
+														</a>
+													</>
+												))}
+											</td>
+										</tr>
+								  ))
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 49 - 49
components/PT/TableSanksi.js

@@ -1,49 +1,49 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.laporan.pt.nama}</h4>
-													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										<Link href={{ pathname: to, query: { id: data._id } }}>
-											<Button color="primary">{linkName}</Button>
-										</Link>
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.laporan.pt.nama}</h4>
+													<p>{data.keterangan.length > 70 ? data.keterangan.substring(0, 70) + "..." : data.keterangan}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										<Link href={{ pathname: to, query: { id: data._id } }}>
+											<Button color="primary">{linkName}</Button>
+										</Link>
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 53 - 53
components/PT/TableSanksiJawaban.js

@@ -1,53 +1,53 @@
-import moment from "moment";
-import { Button, Table } from "reactstrap";
-import Link from "next/link";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Table className="table w-100">
-					<thead>
-						<tr>
-							<th>Nomor Sanksi</th>
-							<th>Keterangan Sanksi</th>
-							<th>Created</th>
-							<th>Status</th>
-						</tr>
-					</thead>
-					<tbody>
-						{listData.map((data) => {
-							return (
-								<tr key={data._id}>
-									<td>{data.no_sanksi}</td>
-									<td className="text-nowrap">
-										<div className="media align-items-center">
-											<div className="media-body d-flex">
-												<div>
-													<h4 className="m-0">{data.laporan.pt.nama}</h4>
-													<p>{data.keterangan}</p>
-												</div>
-											</div>
-										</div>
-									</td>
-									<td>{moment(data.sanksi.createdAt).format("DD MMMM YYYY")}</td>
-									<td>
-										{data.sanksi.keberatan?.jawaban || data.sanksi.banding?.jawaban || data.sanksi.cabut_sanksi?.jawaban ? (
-											<Link href={{ pathname: to, query: { noSanksi: data.sanksi.no_sanksi } }}>
-												<Button color="primary">{linkName}</Button>
-											</Link>
-										) : (
-											<div className="badge-info badge">Menunggu Jawaban</div>
-										)}
-									</td>
-								</tr>
-							);
-						})}
-					</tbody>
-				</Table>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import moment from "moment";
+import { Button, Table } from "reactstrap";
+import Link from "next/link";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				<Table className="table w-100">
+					<thead>
+						<tr>
+							<th>Nomor Sanksi</th>
+							<th>Keterangan Sanksi</th>
+							<th>Created</th>
+							<th>Status</th>
+						</tr>
+					</thead>
+					<tbody>
+						{listData.map((data) => {
+							return (
+								<tr key={data._id}>
+									<td>{data.no_sanksi}</td>
+									<td className="text-nowrap">
+										<div className="media align-items-center">
+											<div className="media-body d-flex">
+												<div>
+													<h4 className="m-0">{data.laporan.pt.nama}</h4>
+													<p>{data.keterangan}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>{moment(data.sanksi.createdAt).format("DD MMMM YYYY")}</td>
+									<td>
+										{data.sanksi.keberatan?.jawaban || data.sanksi.banding?.jawaban || data.sanksi.cabut_sanksi?.jawaban ? (
+											<Link href={{ pathname: to, query: { noSanksi: data.sanksi.no_sanksi } }}>
+												<Button color="primary">{linkName}</Button>
+											</Link>
+										) : (
+											<div className="badge-info badge">Menunggu Jawaban</div>
+										)}
+									</td>
+								</tr>
+							);
+						})}
+					</tbody>
+				</Table>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 71 - 71
components/PT/Timeline.js

@@ -1,71 +1,71 @@
-import moment from "moment";
-import { API_URL } from "@/env";
-
-function Timeline({ data, dataPelaporan }) {
-	const jadwal = dataPelaporan;
-	const date = [...new Set(data.data.map((e) => moment(e.createdAt).format("DD MMMM YYYY")))];
-	return (
-		<ul className="timeline-alt">
-			{date.map((value) => (
-				<>
-					<li className="timeline-separator" data-datetime={value}></li>
-					{data.data
-						.filter((e) => e.for_pt && moment(e.createdAt).format("DD MMMM YYYY") === value)
-						.map((data, i) => (
-							<>
-								<li className={data.role === "PT" ? "timeline-inverted" : ""}>
-									<div className={`timeline-badge ${data.role === "PT" ? " danger" : "info"}`}>
-										<em className={`fas fa-${data.role === "PT" ? "graduation-cap" : "file"}`}></em>
-									</div>
-
-									<div className="timeline-card">
-										<div className={`popover ${data.role === "PT" ? "right" : "left"}`}>
-											<div className="arrow"></div>
-											<div className="popover-body">
-												<div className="d-flex align-items-center mb-3">
-													<img className="mr-3 rounded-circle thumb48" src={`/static/img${data.role === "PT" ? "/univ-avatar.png" : "/logo-single.png"}`} alt="Avatar" />
-													<p className="m-0">
-														<strong>{data.role_name}</strong>
-														<br />
-														{data.description}
-														<br />
-														<p className="text-muted">{moment(data.createdAt).format("hh:mm")}</p>
-													</p>
-												</div>
-												{data.data.files ? (
-													<>
-														<p className="text-muted my-2">Dokumen</p>
-														{data.files.map((e) => (
-															<div className="media bb p-2">
-																<div className="media-body">
-																	<p className="m-0">
-																		<a href={API_URL + e.path} target="_blank" download={e.name}>
-																			<strong>{e.name}</strong>
-																		</a>
-																	</p>
-																</div>
-															</div>
-														))}
-													</>
-												) : (
-													""
-												)}
-											</div>
-										</div>
-									</div>
-								</li>
-							</>
-						))}
-				</>
-			))}
-
-			<li className="timeline-end">
-				<a className="timeline-badge">
-					<em className="fa fa-plus"></em>
-				</a>
-			</li>
-		</ul>
-	);
-}
-
-export default Timeline;
+import moment from "moment";
+import { API_URL } from "@/env";
+
+function Timeline({ data, dataPelaporan }) {
+	const jadwal = dataPelaporan;
+	const date = [...new Set(data.data.map((e) => moment(e.createdAt).format("DD MMMM YYYY")))];
+	return (
+		<ul className="timeline-alt">
+			{date.map((value) => (
+				<>
+					<li className="timeline-separator" data-datetime={value}></li>
+					{data.data
+						.filter((e) => e.for_pt && moment(e.createdAt).format("DD MMMM YYYY") === value)
+						.map((data, i) => (
+							<>
+								<li className={data.role === "PT" ? "timeline-inverted" : ""}>
+									<div className={`timeline-badge ${data.role === "PT" ? " danger" : "info"}`}>
+										<em className={`fas fa-${data.role === "PT" ? "graduation-cap" : "file"}`}></em>
+									</div>
+
+									<div className="timeline-card">
+										<div className={`popover ${data.role === "PT" ? "right" : "left"}`}>
+											<div className="arrow"></div>
+											<div className="popover-body">
+												<div className="d-flex align-items-center mb-3">
+													<img className="mr-3 rounded-circle thumb48" src={`/static/img${data.role === "PT" ? "/univ-avatar.png" : "/logo-single.png"}`} alt="Avatar" />
+													<p className="m-0">
+														<strong>{data.role_name}</strong>
+														<br />
+														{data.description}
+														<br />
+														<p className="text-muted">{moment(data.createdAt).format("hh:mm")}</p>
+													</p>
+												</div>
+												{data.data.files ? (
+													<>
+														<p className="text-muted my-2">Dokumen</p>
+														{data.files.map((e) => (
+															<div className="media bb p-2">
+																<div className="media-body">
+																	<p className="m-0">
+																		<a href={API_URL + e.path} target="_blank" download={e.name}>
+																			<strong>{e.name}</strong>
+																		</a>
+																	</p>
+																</div>
+															</div>
+														))}
+													</>
+												) : (
+													""
+												)}
+											</div>
+										</div>
+									</div>
+								</li>
+							</>
+						))}
+				</>
+			))}
+
+			<li className="timeline-end">
+				<a className="timeline-badge">
+					<em className="fa fa-plus"></em>
+				</a>
+			</li>
+		</ul>
+	);
+}
+
+export default Timeline;

+ 4 - 6
components/Pelaporan/InputData.js

@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { Component, useMemo } from "react";
 import Router from "next/router";
 import { getPelanggaranPublic } from "@/actions/pelanggaran";
 import { createPelaporan } from "@/actions/pelaporan";
@@ -232,9 +232,9 @@ export class InputData extends Component {
 										>
 											{({ getRootProps, getInputProps, isDragActive }) => {
 												return (
-													<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+													<div {...getRootProps()} className={"dropzone card p-3" + (isDragActive ? "dropzone-drag-active" : "")}>
 														<input name="dokumen" {...getInputProps()} />
-														<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}</div>
+														<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}</div>
 														<div className="d-flex align-items-center">
 															<small className="ml-auto">
 																<button
@@ -245,7 +245,7 @@ export class InputData extends Component {
 																		form.setFieldValue(field.name, []);
 																	}}
 																>
-																	Clear files
+																	Reset dokumen
 																</button>
 															</small>
 														</div>
@@ -261,7 +261,6 @@ export class InputData extends Component {
 								</p>
 							</div>
 						</FormGroup>
-
 						<FormGroup row>
 							<div className="col-xl-10">
 								<button className="btn btn-sm btn-primary" type="submit" disabled={isSubmitting}>
@@ -275,6 +274,5 @@ export class InputData extends Component {
 		);
 	}
 }
-
 const mapStateToProps = (state) => ({ user: state.user, token: state.token });
 export default connect(mapStateToProps)(InputData);

+ 242 - 238
components/Pemeriksaan/InputEvaluasi.js

@@ -1,238 +1,242 @@
-import React, { Component } from "react";
-import { insertPemeriksaan } from "@/actions/pemeriksaan";
-import Router from "next/router";
-import Datetime from "react-datetime";
-import moment from "moment";
-import { Row, Col, FormGroup, Input } from "reactstrap";
-import { ToastContainer, toast } from "react-toastify";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-import { getOneLaporan } from "@/actions/pelaporan";
-
-const selectInstanceId = 1;
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const evaluasiSchema = Yup.object().shape({
-	tanggal: Yup.date().required("Required"),
-	judul: Yup.string().min(3).max(150).required("Required"),
-	dokumen: Yup.array().min(1).required("Required").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-
-let Dropzone = null;
-
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-export default class InputEvaluasi extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			dropdownOpen: false,
-			splitButtonOpen: false,
-			judulEvaluasi: "",
-			tanggal: moment().format("D MMMM YYYY"),
-			files: [],
-		};
-	}
-
-	setjudulEvaluasi = (e) => {
-		this.setState({ judulEvaluasi: e.target.value });
-	};
-
-	setTanggal = (moment) => {
-		this.setState({ tanggal: moment.format("D MMMM YYYY") });
-	};
-
-	toggleSplit = () => {
-		this.setState({
-			splitButtonOpen: !this.state.splitButtonOpen,
-		});
-	};
-
-	toggleDropDown = () => {
-		this.setState({
-			dropdownOpen: !this.state.dropdownOpen,
-		});
-	};
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	onSubmit = async (data, { resetForm }) => {
-		const { token, query } = this.props;
-		const { id } = query;
-		const formdata = new FormData();
-		formdata.append("judul", data.judul);
-		formdata.append("tanggal", data.tanggal);
-		data.dokumen.forEach((e) => {
-			formdata.append("dokumen", e);
-		});
-
-		await toast.promise(insertPemeriksaan(token, id, formdata), {
-			pending: "Loading",
-			success: "Success",
-			error: "Error",
-		});
-		this.setState({ files: [] });
-		resetForm();
-		const pelaporan = await getOneLaporan(token, query.id);
-		this.props.changePelaporan(pelaporan);
-	};
-
-	render() {
-		const { files } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-		return (
-			<>
-				<p className="lead bb">Evaluasi</p>
-				<Formik
-					initialValues={{
-						tanggal: new Date(),
-						judul: "",
-						dokumen: [],
-					}}
-					validationSchema={evaluasiSchema}
-					onSubmit={this.onSubmit}
-				>
-					<Form className="form-horizontal">
-						<FormGroup>
-							<label className="col-form-label">Tanggal Dokumen</label>
-							<Field name="tanggal">
-								{({ field, form }) => (
-									<Datetime
-										timeFormat={false}
-										inputProps={{ className: "form-control" }}
-										value={field.value}
-										onChange={(e) => {
-											form.setFieldValue(field.name, e);
-										}}
-									/>
-								)}
-							</Field>
-							<ErrorMessage name="tanggal" component="div" className="form-text text-danger" />
-						</FormGroup>
-						<FormGroup>
-							<label className="col-form-label">Judul Dokumen</label>
-							<Field name="judul">{({ field, form }) => <Input type="text" placeholder="judul" {...field} />}</Field>
-							<ErrorMessage name="judul" component="div" className="form-text text-danger" />
-						</FormGroup>
-						<FormGroup row>
-							<label className="col-md-2 col-form-label">Upload File Pendukung</label>
-							<div className="col-md-10">
-								<Field name="dokumen">
-									{({ field, form, meta }) => (
-										<DropzoneWrapper
-											className=""
-											onDrop={(e) => {
-												this.onDrop(e);
-												form.setFieldValue(field.name, e);
-											}}
-										>
-											{({ getRootProps, getInputProps, isDragActive }) => {
-												return (
-													<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-														<input name="dokumen" {...getInputProps()} />
-														<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}</div>
-														<div className="d-flex align-items-center">
-															<small className="ml-auto">
-																<button
-																	type="button"
-																	className="btn btn-link"
-																	onClick={(e) => {
-																		this.clearFiles(e);
-																		form.setFieldValue(field.name, []);
-																	}}
-																>
-																	Clear files
-																</button>
-															</small>
-														</div>
-													</div>
-												);
-											}}
-										</DropzoneWrapper>
-									)}
-								</Field>
-								<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-								<p className="mrgn-top-5">
-									Ukuran setiap dokumen maksimal 15mb
-								</p>
-							</div>
-						</FormGroup>
-						<FormGroup row>
-							<div className="col-xl-10">
-								<button className="btn btn-sm btn-primary" type="submit">
-									Simpan Evaluasi
-								</button>
-							</div>
-						</FormGroup>
-					</Form>
-				</Formik>
-			</>
-		);
-	}
-}
+import React, { Component } from "react";
+import { insertPemeriksaan } from "@/actions/pemeriksaan";
+import Router from "next/router";
+import Datetime from "react-datetime";
+import moment from "moment";
+import { Row, Col, FormGroup, Input } from "reactstrap";
+import { ToastContainer, toast } from "react-toastify";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+import { getOneLaporan } from "@/actions/pelaporan";
+
+const selectInstanceId = 1;
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const evaluasiSchema = Yup.object().shape({
+	tanggal: Yup.date().required("Wajib diisi"),
+	judul: Yup.string().min(3).max(150).required("Wajib diisi"),
+	dokumen: Yup.array().min(1).required("Wajib diisi").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+
+let Dropzone = null;
+
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+export default class InputEvaluasi extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			dropdownOpen: false,
+			splitButtonOpen: false,
+			judulEvaluasi: "",
+			tanggal: moment().format("D MMMM YYYY"),
+			files: [],
+		};
+	}
+
+	setjudulEvaluasi = (e) => {
+		this.setState({ judulEvaluasi: e.target.value });
+	};
+
+	setTanggal = (moment) => {
+		this.setState({ tanggal: moment.format("D MMMM YYYY") });
+	};
+
+	toggleSplit = () => {
+		this.setState({
+			splitButtonOpen: !this.state.splitButtonOpen,
+		});
+	};
+
+	toggleDropDown = () => {
+		this.setState({
+			dropdownOpen: !this.state.dropdownOpen,
+		});
+	};
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	onSubmit = async (data, { resetForm }) => {
+		const { token, query } = this.props;
+		const { id } = query;
+		const formdata = new FormData();
+		formdata.append("judul", data.judul);
+		formdata.append("tanggal", data.tanggal);
+		data.dokumen.forEach((e) => {
+			formdata.append("dokumen", e);
+		});
+
+		await toast.promise(insertPemeriksaan(token, id, formdata), {
+			pending: "Loading",
+			success: "Success",
+			error: "Error",
+		});
+		this.setState({ files: [] });
+		resetForm();
+		const pelaporan = await getOneLaporan(token, query.id);
+		this.props.changePelaporan(pelaporan);
+	};
+
+	render() {
+		const { files } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+		return (
+			<>
+				<p className="lead bb">Evaluasi</p>
+				<Formik
+					initialValues={{
+						tanggal: new Date(),
+						judul: "",
+						dokumen: [],
+					}}
+					validationSchema={evaluasiSchema}
+					onSubmit={this.onSubmit}
+				>
+					<Form className="form-horizontal">
+						<FormGroup row>
+							<label className="col-md-2 col-form-label">Tanggal Dokumen</label>
+							<div className="col-md-10">
+								<Field name="tanggal">
+									{({ field, form }) => (
+										<Datetime
+											timeFormat={false}
+											inputProps={{ className: "form-control" }}
+											value={field.value}
+											onChange={(e) => {
+												form.setFieldValue(field.name, e);
+											}}
+										/>
+									)}
+								</Field>
+								<ErrorMessage name="tanggal" component="div" className="form-text text-danger" />
+							</div>
+						</FormGroup>
+						<FormGroup row>
+							<label className="col-md-2 col-form-label">Judul Dokumen</label>
+							<div className="col-md-10">
+								<Field name="judul">{({ field, form }) => <Input type="text" placeholder="judul" {...field} />}</Field>
+								<ErrorMessage name="judul" component="div" className="form-text text-danger" />
+							</div>
+						</FormGroup>
+						<FormGroup row>
+							<label className="col-md-2 col-form-label">Upload File Pendukung</label>
+							<div className="col-md-10">
+								<Field name="dokumen">
+									{({ field, form, meta }) => (
+										<DropzoneWrapper
+											className=""
+											onDrop={(e) => {
+												this.onDrop(e);
+												form.setFieldValue(field.name, e);
+											}}
+										>
+											{({ getRootProps, getInputProps, isDragActive }) => {
+												return (
+													<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+														<input name="dokumen" {...getInputProps()} />
+														<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}</div>
+														<div className="d-flex align-items-center">
+															<small className="ml-auto">
+																<button
+																	type="button"
+																	className="btn btn-link"
+																	onClick={(e) => {
+																		this.clearFiles(e);
+																		form.setFieldValue(field.name, []);
+																	}}
+																>
+																	Reset dokumen
+																</button>
+															</small>
+														</div>
+													</div>
+												);
+											}}
+										</DropzoneWrapper>
+									)}
+								</Field>
+								<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+								<p className="mrgn-top-5">
+									Ukuran setiap dokumen maksimal 15mb
+								</p>
+							</div>
+						</FormGroup>
+						<FormGroup row>
+							<div className="col-xl-10">
+								<button className="btn btn-sm btn-primary" type="submit">
+									Simpan Evaluasi
+								</button>
+							</div>
+						</FormGroup>
+					</Form>
+				</Formik>
+			</>
+		);
+	}
+}

+ 3 - 4
components/Pemeriksaan/TableLaporan.js

@@ -6,10 +6,10 @@ import moment from "moment";
 function TableLaporan({ listData, to, linkName }) {
 	return (
 		<div className="card b">
-			<div className="card-body">
+			<div className="card-body card-over">
 				{listData && (
 					<Datatable options={{ responsive: false }}>
-						<table className="table w-100">
+						<table className="table w-100" data-order='[[2, "asc"], [3, "desc"]]'>
 							<thead>
 								<tr>
 									<th>No.Laporan</th>
@@ -39,8 +39,7 @@ function TableLaporan({ listData, to, linkName }) {
 												{/* {data.level == 3 ? <div className="badge badge-success">Tinggi</div> : data.level == 2 ? <div className="badge badge-info">Sedang</div> : <div className="badge badge-warning">Rendah</div>} */}
 												{data.evaluasi?.length ? <div className="badge badge-info">Sudah diperiksa</div> : <div className="badge badge-danger">Belum diperiksa</div>}
 											</td>
-
-											<td>{moment(data.createdAt).fromNow()}</td>
+											<td>{moment(data.createdAt).format("MM-DD-YYYY")}</td>
 											<td>
 												<div className="ml-auto">
 													<Link

+ 42 - 40
components/Pemeriksaan/TableRiwayat.js

@@ -1,40 +1,42 @@
-import Datatable from "@/components/Tables/Datatable";
-
-function TableRiwayat({ data }) {
-	return (
-		<Datatable options={{ responsive: true }}>
-			<table className="table table-striped my-4 w-100">
-				<thead>
-					<tr>
-						<th>Tanggal Dibuat</th>
-						<th>Tanggal Dokumen</th>
-						<th>Judul Dokumen</th>
-						<th>File Pendukung</th>
-					</tr>
-				</thead>
-				<tbody>
-					{data.evaluasi.map((e, index) => (
-						<tr key={index}>
-							<td>{moment(e.createdAt).format("D MMMM YYYY")}</td>
-							<td>{moment(e.tanggal).format("D MMMM YYYY")}</td>
-							<td>{e.judul}</td>
-							<td>
-								{e.dokumen.map((e, index) => (
-									<>
-										<em key="index" className="fa-lg far fa-file-code"></em>
-										<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-											{e.judul}
-										</a>
-										<br />
-									</>
-								))}
-							</td>
-						</tr>
-					))}
-				</tbody>
-			</table>
-		</Datatable>
-	);
-}
-
-export default TableRiwayat;
+import Datatable from "@/components/Tables/Datatable";
+
+function TableRiwayat({ data }) {
+	return (
+		// <Datatable options={{ responsive: true }}>
+		<div className="card-over">
+			<table className="table table-striped my-4 w-100">
+				<thead>
+					<tr>
+						<th>Tanggal Dibuat</th>
+						<th>Tanggal Dokumen</th>
+						<th>Judul Dokumen</th>
+						<th>File Pendukung</th>
+					</tr>
+				</thead>
+				<tbody>
+					{data.evaluasi.map((e, index) => (
+						<tr key={index}>
+							<td>{moment(e.createdAt).format("D MMMM YYYY")}</td>
+							<td>{moment(e.tanggal).format("D MMMM YYYY")}</td>
+							<td>{e.judul}</td>
+							<td>
+								{e.dokumen.map((e, index) => (
+									<>
+										<em key="index" className="fa-lg far fa-file-code"></em>
+										<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+											{e.judul}
+										</a>
+										<br />
+									</>
+								))}
+							</td>
+						</tr>
+					))}
+				</tbody>
+			</table>
+		</div>
+		// </Datatable>
+	);
+}
+
+export default TableRiwayat;

+ 51 - 51
components/PencabutanSanksi/Riwayat.js

@@ -1,51 +1,51 @@
-import Datatable from "@/components/Tables/Datatable";
-import moment from "moment";
-import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
-import { API_URL } from "@/env";
-
-function Riwayat({ data }) {
-	return (
-		<Card className="card-default">
-			<CardHeader>
-				<CardTitle>Riwayat</CardTitle>
-			</CardHeader>
-			<CardBody>
-				<Datatable options={{ responsive: true }}>
-					<table className="table table-striped my-4 w-100">
-						<thead>
-							<tr>
-								<th>Tanggal</th>
-								<th>Status</th>
-								<th>Keterangan</th>
-								<th>Dokumen</th>
-							</tr>
-						</thead>
-						<tbody>
-							{data ? (
-								<tr>
-									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
-									<td>{data.status}</td>
-									<td>{data.keterangan}</td>
-									<td>
-										{data.dokumen.map((e) => (
-											<>
-												<em className="fa-lg far fa-file-code"></em>
-												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
-													{e.judul}
-												</a>
-											</>
-										))}
-									</td>
-								</tr>
-							) : (
-								""
-							)}
-						</tbody>
-					</table>
-				</Datatable>
-			</CardBody>
-		</Card>
-	);
-}
-
-export default Riwayat;
+import Datatable from "@/components/Tables/Datatable";
+import moment from "moment";
+import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
+import { API_URL } from "@/env";
+
+function Riwayat({ data }) {
+	return (
+		<Card className="card-default">
+			<CardHeader>
+				<CardTitle>Riwayat</CardTitle>
+			</CardHeader>
+			<CardBody>
+				<Datatable options={{ responsive: true }}>
+					<table className="table table-striped my-4 w-100">
+						<thead>
+							<tr>
+								<th>Tanggal</th>
+								<th>Status</th>
+								<th>Keterangan</th>
+								<th>Dokumen</th>
+							</tr>
+						</thead>
+						<tbody>
+							{data ? (
+								<tr>
+									<td>{moment(data.createAt).format("DD MMMM YYYY")}</td>
+									<td>{data.status}</td>
+									<td>{data.keterangan}</td>
+									<td>
+										{data.dokumen.map((e) => (
+											<>
+												<em className="fa-lg far fa-file-code"></em>
+												<a className="text-muted" href={e.path} target="_blank" download={e.judul}>
+													{e.judul}
+												</a>
+											</>
+										))}
+									</td>
+								</tr>
+							) : (
+								""
+							)}
+						</tbody>
+					</table>
+				</Datatable>
+			</CardBody>
+		</Card>
+	);
+}
+
+export default Riwayat;

+ 65 - 65
components/PencabutanSanksi/TableSanksi.js

@@ -1,65 +1,65 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableSanksi({ listData, to, linkName }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				<Datatable options={{ responsive: true }}>
-					<table className="table w-100">
-						<thead>
-							<tr>
-								<th>Nomor Sanksi</th>
-								<th>Keterangan Sanksi</th>
-								<th>Created</th>
-								<th>Status</th>
-								<th></th>
-							</tr>
-						</thead>
-						<tbody>
-							{listData.length
-								? listData.map((data) => {
-										return (
-											<tr key={data._id}>
-												<td>{data.no_sanksi}</td>
-												<td>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<h4 className="m-0">{data.laporan.pt.nama}</h4>
-																<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-												<td>{moment(data.createdAt).fromNow()}</td>
-												<td>{data.jawaban?.cabut_sanksi ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
-												<td>
-													<div className="ml-auto">
-														<Link
-															href={{
-																pathname: to,
-																query: { id: data._id },
-															}}
-														>
-															<Button color="primary" size="sm">
-																{linkName}
-															</Button>
-														</Link>
-													</div>
-												</td>
-											</tr>
-										);
-								  })
-								: ""}
-						</tbody>
-					</table>
-				</Datatable>
-			</div>
-		</div>
-	);
-}
-
-export default TableSanksi;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableSanksi({ listData, to, linkName }) {
+	return (
+		<div className="card b">
+			<div className="card-body">
+				<Datatable options={{ responsive: true }}>
+					<table className="table w-100" data-order='[3, "asc"]'>
+						<thead>
+							<tr>
+								<th>Nomor Sanksi</th>
+								<th>Keterangan Sanksi</th>
+								<th>Created</th>
+								<th>Status</th>
+								<th></th>
+							</tr>
+						</thead>
+						<tbody>
+							{listData.length
+								? listData.map((data) => {
+									return (
+										<tr key={data._id}>
+											<td>{data.no_sanksi}</td>
+											<td>
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															<h4 className="m-0">{data.laporan.pt.nama}</h4>
+															<p>{data.keterangan.length > 25 ? data.keterangan.substring(0, 25) + "..." : data.keterangan}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+											<td>{moment(data.createdAt).fromNow()}</td>
+											<td>{data.jawaban?.cabut_sanksi ? <div className="badge badge-info">Sudah Dijawab</div> : <div className="badge badge-danger">Belum Dijawab</div>}</td>
+											<td>
+												<div className="ml-auto">
+													<Link
+														href={{
+															pathname: to,
+															query: { id: data._id },
+														}}
+													>
+														<Button color="primary" size="sm">
+															{linkName}
+														</Button>
+													</Link>
+												</div>
+											</td>
+										</tr>
+									);
+								})
+								: ""}
+						</tbody>
+					</table>
+				</Datatable>
+			</div>
+		</div>
+	);
+}
+
+export default TableSanksi;

+ 124 - 124
components/Penjadwalan/DetailLaporan.js

@@ -1,124 +1,124 @@
-import React, { Component } from "react";
-import Select from "react-select";
-import Scrollable from "@/components/Common/Scrollable";
-import { addStatus } from "@/actions/pelaporan";
-import { Card, CardBody, CardHeader, CardTitle } from "reactstrap";
-
-const status = [
-	{ value: "Ditindaklanjuti Dikti Ristek", label: "Ditindaklanjuti Dikti Ristek", className: "State-ACT" },
-	{ value: "Delegasi ke LLDIKTI", label: "Delegasi ke LLDIKTI", className: "State-ACT" },
-	{ value: "Ditutup", label: "Ditutup", className: "State-ACT" },
-];
-const selectInstanceId = 1;
-export class DetailLaporan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			selectedOption: null,
-		};
-	}
-
-	componentDidMount = () => {
-		const { data } = this.props;
-		if (data.status) {
-			const selectedOption = status.filter((e) => e.value === data.status)[0];
-			this.setState({ selectedOption });
-		} else {
-			this.setState({ selectedOption: { value: "Ditindaklanjuti", label: "Ditindaklanjuti Dikti Ristek", className: "State-ACT" } });
-			// const tes = await addStatus({ number, ptId }, { status: data.status || "ditindaklanjuti" });
-		}
-	};
-
-	handleChangeSelect = async (selectedOption) => {
-		const { ptId, number } = this.props.query;
-		this.props.handleChangeSelect(selectedOption);
-		this.setState({ selectedOption });
-		await addStatus({ number, ptId }, { status: selectedOption.value });
-	};
-
-	render() {
-		const { data } = this.props;
-		return (
-			<Card className="card b">
-				<CardHeader>
-					<CardTitle tag="h4">Detail Laporan</CardTitle>
-				</CardHeader>
-				<CardBody>
-					<table className="table">
-						<tbody>
-							<tr>
-								<td>
-									<strong>Status</strong>
-								</td>
-								<td>
-									<Select instanceId={selectInstanceId + 1} value={this.state.selectedOption} onChange={this.handleChangeSelect} options={status} required />
-								</td>
-							</tr>
-							<tr>
-								<td>
-									<strong>Nomor Laporan</strong>
-								</td>
-								<td>{data._number}</td>
-							</tr>
-							<tr>
-								<td>
-									<strong>Perguruan Tinggi</strong>
-								</td>
-								<td>Universitas Satyagama</td>
-							</tr>
-							<tr>
-								<td>
-									<strong>Jenis Pelanggaran</strong>
-								</td>
-								<td>
-									<Scrollable height="75px" className="list-group">
-										<ul>{data.pelanggaran ? data.pelanggaran.map((e) => <li>{e.pelanggaran}</li>) : ""}</ul>
-									</Scrollable>
-								</td>
-							</tr>
-							<tr>
-								<td>
-									<strong>Keterangan Laporan</strong>
-								</td>
-								<td>
-									<Scrollable height="100px" className="list-group">
-										<p>{data.description}</p>
-									</Scrollable>
-								</td>
-							</tr>
-							<tr>
-								<td>
-									<strong>File Pendukung</strong>
-								</td>
-								<td>
-									<Scrollable height="120px" className="list-group">
-										<table className="table table-bordered bg-transparent">
-											<tbody>
-												{data.files
-													? data.files.map((e) => (
-															<tr>
-																<td>
-																	<em className="fa-lg far fa-file-code"></em>
-																</td>
-																<td>
-																	<a className="text-muted" href={`data:${e.type};base64, ${Buffer.from(e.data).toString("base64")}`} download={e.name}>
-																		{e.name}
-																	</a>
-																</td>
-															</tr>
-													  ))
-													: ""}
-											</tbody>
-										</table>
-									</Scrollable>
-								</td>
-							</tr>
-						</tbody>
-					</table>
-				</CardBody>
-			</Card>
-		);
-	}
-}
-
-export default DetailLaporan;
+import React, { Component } from "react";
+import Select from "react-select";
+import Scrollable from "@/components/Common/Scrollable";
+import { addStatus } from "@/actions/pelaporan";
+import { Card, CardBody, CardHeader, CardTitle } from "reactstrap";
+
+const status = [
+	{ value: "Ditindaklanjuti Dikti Ristek", label: "Ditindaklanjuti Dikti Ristek", className: "State-ACT" },
+	{ value: "Delegasi ke LLDIKTI", label: "Delegasi ke LLDIKTI", className: "State-ACT" },
+	{ value: "Ditutup", label: "Ditutup", className: "State-ACT" },
+];
+const selectInstanceId = 1;
+export class DetailLaporan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			selectedOption: null,
+		};
+	}
+
+	componentDidMount = () => {
+		const { data } = this.props;
+		if (data.status) {
+			const selectedOption = status.filter((e) => e.value === data.status)[0];
+			this.setState({ selectedOption });
+		} else {
+			this.setState({ selectedOption: { value: "Ditindaklanjuti", label: "Ditindaklanjuti Dikti Ristek", className: "State-ACT" } });
+			// const tes = await addStatus({ number, ptId }, { status: data.status || "ditindaklanjuti" });
+		}
+	};
+
+	handleChangeSelect = async (selectedOption) => {
+		const { ptId, number } = this.props.query;
+		this.props.handleChangeSelect(selectedOption);
+		this.setState({ selectedOption });
+		await addStatus({ number, ptId }, { status: selectedOption.value });
+	};
+
+	render() {
+		const { data } = this.props;
+		return (
+			<Card className="card b">
+				<CardHeader>
+					<CardTitle tag="h4">Detail Laporan</CardTitle>
+				</CardHeader>
+				<CardBody>
+					<table className="table">
+						<tbody>
+							<tr>
+								<td>
+									<strong>Status</strong>
+								</td>
+								<td>
+									<Select instanceId={selectInstanceId + 1} value={this.state.selectedOption} onChange={this.handleChangeSelect} options={status} required />
+								</td>
+							</tr>
+							<tr>
+								<td>
+									<strong>Nomor Laporan</strong>
+								</td>
+								<td>{data._number}</td>
+							</tr>
+							<tr>
+								<td>
+									<strong>Perguruan Tinggi</strong>
+								</td>
+								<td>Universitas Satyagama</td>
+							</tr>
+							<tr>
+								<td>
+									<strong>Jenis Pelanggaran</strong>
+								</td>
+								<td>
+									<Scrollable height="75px" className="list-group">
+										<ul>{data.pelanggaran ? data.pelanggaran.map((e) => <li>{e.pelanggaran}</li>) : ""}</ul>
+									</Scrollable>
+								</td>
+							</tr>
+							<tr>
+								<td>
+									<strong>Keterangan Laporan</strong>
+								</td>
+								<td>
+									<Scrollable height="100px" className="list-group">
+										<p>{data.description}</p>
+									</Scrollable>
+								</td>
+							</tr>
+							<tr>
+								<td>
+									<strong>File Pendukung</strong>
+								</td>
+								<td>
+									<Scrollable height="120px" className="list-group">
+										<table className="table table-bordered bg-transparent">
+											<tbody>
+												{data.files
+													? data.files.map((e) => (
+															<tr>
+																<td>
+																	<em className="fa-lg far fa-file-code"></em>
+																</td>
+																<td>
+																	<a className="text-muted" href={`data:${e.type};base64, ${Buffer.from(e.data).toString("base64")}`} download={e.name}>
+																		{e.name}
+																	</a>
+																</td>
+															</tr>
+													  ))
+													: ""}
+											</tbody>
+										</table>
+									</Scrollable>
+								</td>
+							</tr>
+						</tbody>
+					</table>
+				</CardBody>
+			</Card>
+		);
+	}
+}
+
+export default DetailLaporan;

+ 4 - 5
components/Penjadwalan/TableLaporan.js

@@ -6,10 +6,10 @@ import moment from "moment";
 function TableLaporan({ listData, to, linkName }) {
 	return (
 		<div className="card b">
-			<div className="card-body">
+			<div className="card-body card-over">
 				{listData && (
 					<Datatable options={{ responsive: false }}>
-						<table className="table w-100">
+						<table className="table w-100" data-order='[[2, "desc"], [3, "desc"]]'>
 							<thead>
 								<tr>
 									<th>No.Laporan</th>
@@ -39,8 +39,7 @@ function TableLaporan({ listData, to, linkName }) {
 												{/* {data.level == 3 ? <div className="badge badge-success">Tinggi</div> : data.level == 2 ? <div className="badge badge-info">Sedang</div> : <div className="badge badge-warning">Rendah</div>} */}
 												{data.jadwal ? <div className="badge badge-info">Ada Jadwal</div> : <div className="badge badge-danger">Tidak ada jadwal</div>}
 											</td>
-
-											<td>{moment(data.createdAt).fromNow()}</td>
+											<td>{moment(data.createdAt).format("MM-DD-YYYY")}</td>
 											<td>
 												<div className="ml-auto">
 													<Link
@@ -50,7 +49,7 @@ function TableLaporan({ listData, to, linkName }) {
 														}}
 													>
 														<Button color="primary" size="sm">
-															{linkName}
+															Edit
 														</Button>
 													</Link>
 												</div>

+ 147 - 147
components/Sanksi/Ringkasan.js

@@ -1,147 +1,147 @@
-import { useEffect, useState } from "react";
-import Scrollable from "@/components/Common/Scrollable";
-import { Card, Row, Col, Table, FormGroup } from "reactstrap";
-
-function Ringkasan({ dataLaporan, dataPelanggaran, dataUpload }) {
-	return (
-		<>
-			<Row>
-				<Col>
-					<p className="lead bb">Detail Laporan</p>
-					<form className="form-horizontal">
-						<FormGroup row>
-							<Col md="4">Nomor Laporan:</Col>
-							<Col md="8">
-								<strong>{dataLaporan.no_laporan}</strong>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Nama Perguruan Tinggi:</Col>
-							<Col md="8">
-								<strong>Universitas Satyagama</strong>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Jenis Pelanggaran:</Col>
-							<Col md="8">
-								<Scrollable height="125px" className="list-group">
-									<ul>
-										{dataLaporan.pelanggaran.map((e) => (
-											<li>{e.pelanggaran}</li>
-										))}
-									</ul>
-								</Scrollable>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Keterangan Laporan:</Col>
-							<Col md="8">
-								<Scrollable height="100px" className="list-group">
-									<p>{dataLaporan.keterangan}</p>
-								</Scrollable>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Dibuat Pada:</Col>
-							<Col md="8">
-								<strong>{moment(dataLaporan.createAt).format("D MMMM YYYY")}</strong>
-							</Col>
-						</FormGroup>
-					</form>
-				</Col>
-			</Row>
-			<Row>
-				<Col>
-					<p className="lead bb">Penetapan Sanksi</p>
-					<Card className="card-default">
-						<Table bordered hover responsive>
-							<thead>
-								<tr>
-									<th>No</th>
-									<th>Jenis Pelanggaran</th>
-									<th>Sanksi</th>
-								</tr>
-							</thead>
-							<tbody>
-								{dataPelanggaran
-									? dataPelanggaran.map((e, i) => (
-											<tr key={e._id}>
-												<td>{++i}</td>
-												<td>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<p>{e.pelanggaran}</p>
-																<p>TMT : {e.tmt_bulan} Bulan</p>
-																<p>Level Pelanggaran : {e.label_sanksi}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-												<td>
-													<div className="media align-items-center">
-														<div className="media-body d-flex">
-															<div>
-																<p>{e.sanksi}</p>
-																<p>Keterangan : {e.keterangan_sanksi}</p>
-															</div>
-														</div>
-													</div>
-												</td>
-											</tr>
-									  ))
-									: ""}
-							</tbody>
-						</Table>
-					</Card>
-				</Col>
-			</Row>
-			<Row>
-				<Col>
-					<p className="lead bb">Nomor Surat Keputusan Sanksi</p>
-					<form className="form-horizontal">
-						<FormGroup row>
-							<Col md="4">Nomor Surat:</Col>
-							<Col md="8">
-								<strong>{dataUpload ? dataUpload.nomorSanksi : ""}</strong>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Keterangan:</Col>
-							<Col md="8">
-								<strong>{dataUpload ? dataUpload.keterangan : ""}</strong>
-							</Col>
-						</FormGroup>
-						<FormGroup row>
-							<Col md="4">Surat Sanksi:</Col>
-							<Col md="8">
-								<Scrollable height="120px" className="list-group">
-									<table className="table table-bordered bg-transparent">
-										<tbody>
-											{dataUpload
-												? dataUpload.files.map((e) => (
-														<tr>
-															<td>
-																<em className="fa-lg far fa-file-code"></em>
-															</td>
-															<td>
-																<a className="text-muted" href={e.preview} download={e.name}>
-																	{e.name}
-																</a>
-															</td>
-														</tr>
-												  ))
-												: ""}
-										</tbody>
-									</table>
-								</Scrollable>
-							</Col>
-						</FormGroup>
-					</form>
-				</Col>
-			</Row>
-		</>
-	);
-}
-
-export default Ringkasan;
+import { useEffect, useState } from "react";
+import Scrollable from "@/components/Common/Scrollable";
+import { Card, Row, Col, Table, FormGroup } from "reactstrap";
+
+function Ringkasan({ dataLaporan, dataPelanggaran, dataUpload }) {
+	return (
+		<>
+			<Row>
+				<Col>
+					<p className="lead bb">Detail Laporan</p>
+					<form className="form-horizontal">
+						<FormGroup row>
+							<Col md="4">Nomor Laporan:</Col>
+							<Col md="8">
+								<strong>{dataLaporan.no_laporan}</strong>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Nama Perguruan Tinggi:</Col>
+							<Col md="8">
+								<strong>Universitas Satyagama</strong>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Jenis Pelanggaran:</Col>
+							<Col md="8">
+								<Scrollable height="125px" className="list-group">
+									<ul>
+										{dataLaporan.pelanggaran.map((e) => (
+											<li>{e.pelanggaran}</li>
+										))}
+									</ul>
+								</Scrollable>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Keterangan Laporan:</Col>
+							<Col md="8">
+								<Scrollable height="100px" className="list-group">
+									<p>{dataLaporan.keterangan}</p>
+								</Scrollable>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Dibuat Pada:</Col>
+							<Col md="8">
+								<strong>{moment(dataLaporan.createAt).format("D MMMM YYYY")}</strong>
+							</Col>
+						</FormGroup>
+					</form>
+				</Col>
+			</Row>
+			<Row>
+				<Col>
+					<p className="lead bb">Penetapan Sanksi</p>
+					<Card className="card-default">
+						<Table bordered hover responsive>
+							<thead>
+								<tr>
+									<th>No</th>
+									<th>Jenis Pelanggaran</th>
+									<th>Sanksi</th>
+								</tr>
+							</thead>
+							<tbody>
+								{dataPelanggaran
+									? dataPelanggaran.map((e, i) => (
+											<tr key={e._id}>
+												<td>{++i}</td>
+												<td>
+													<div className="media align-items-center">
+														<div className="media-body d-flex">
+															<div>
+																<p>{e.pelanggaran}</p>
+																<p>TMT : {e.tmt_bulan} Bulan</p>
+																<p>Level Pelanggaran : {e.label_sanksi}</p>
+															</div>
+														</div>
+													</div>
+												</td>
+												<td>
+													<div className="media align-items-center">
+														<div className="media-body d-flex">
+															<div>
+																<p>{e.sanksi}</p>
+																<p>Keterangan : {e.keterangan_sanksi}</p>
+															</div>
+														</div>
+													</div>
+												</td>
+											</tr>
+									  ))
+									: ""}
+							</tbody>
+						</Table>
+					</Card>
+				</Col>
+			</Row>
+			<Row>
+				<Col>
+					<p className="lead bb">Nomor Surat Keputusan Sanksi</p>
+					<form className="form-horizontal">
+						<FormGroup row>
+							<Col md="4">Nomor Surat:</Col>
+							<Col md="8">
+								<strong>{dataUpload ? dataUpload.nomorSanksi : ""}</strong>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Keterangan:</Col>
+							<Col md="8">
+								<strong>{dataUpload ? dataUpload.keterangan : ""}</strong>
+							</Col>
+						</FormGroup>
+						<FormGroup row>
+							<Col md="4">Surat Sanksi:</Col>
+							<Col md="8">
+								<Scrollable height="120px" className="list-group">
+									<table className="table table-bordered bg-transparent">
+										<tbody>
+											{dataUpload
+												? dataUpload.files.map((e) => (
+														<tr>
+															<td>
+																<em className="fa-lg far fa-file-code"></em>
+															</td>
+															<td>
+																<a className="text-muted" href={e.preview} download={e.name}>
+																	{e.name}
+																</a>
+															</td>
+														</tr>
+												  ))
+												: ""}
+										</tbody>
+									</table>
+								</Scrollable>
+							</Col>
+						</FormGroup>
+					</form>
+				</Col>
+			</Row>
+		</>
+	);
+}
+
+export default Ringkasan;

+ 67 - 67
components/Sanksi/TableLaporan.js

@@ -1,67 +1,67 @@
-import Datatable from "@/components/Tables/Datatable";
-import { Button } from "reactstrap";
-import Link from "next/link";
-import moment from "moment";
-
-function TableLaporan({ listData }) {
-	return (
-		<div className="card b">
-			<div className="card-body">
-				{listData && (
-					<Datatable options={{ responsive: false }}>
-						<table className="table w-100">
-							<thead>
-								<tr>
-									<th>No.Laporan</th>
-									<th>Deskripsi Laporan</th>
-									<th>Status</th>
-									<th>Created</th>
-									<th></th>
-								</tr>
-							</thead>
-							<tbody>
-								{listData.map((data) => {
-									return (
-										<tr key={data._id}>
-											<td>{data.no_laporan}</td>
-											<td className="text-nowrap">
-												<div className="media align-items-center">
-													<div className="media-body d-flex">
-														<div>
-															<h4 className="m-0">{data.pt.nama}</h4>
-															<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
-														</div>
-													</div>
-												</div>
-											</td>
-											<td>
-												<td>{data.sanksi ? <div className="badge badge-info">Sudah ditetapkan</div> : <div className="badge badge-danger">Belum ditetapkan</div>}</td>
-											</td>
-											<td>{moment(data.createdAt).fromNow()}</td>
-											<td>
-												<div className="ml-auto">
-													<Link
-														href={{
-															pathname: data.sanksi ? "/app/sanksi/detail" : "/app/sanksi/proses",
-															query: { id: data.sanksi || data._id },
-														}}
-													>
-														<Button color="primary" size="sm">
-															{data.sanksi ? "Detail" : "Proses Sanksi"}
-														</Button>
-													</Link>
-												</div>
-											</td>
-										</tr>
-									);
-								})}
-							</tbody>
-						</table>
-					</Datatable>
-				)}
-			</div>
-		</div>
-	);
-}
-
-export default TableLaporan;
+import Datatable from "@/components/Tables/Datatable";
+import { Button } from "reactstrap";
+import Link from "next/link";
+import moment from "moment";
+
+function TableLaporan({ listData }) {
+	return (
+		<div className="card b">
+			<div className="card-body card-over">
+				{listData && (
+					<Datatable options={{ responsive: false }}>
+						<table className="table w-100" data-order='[[2, "asc"], [3, "desc"]]'>
+							<thead>
+								<tr>
+									<th>No.Laporan</th>
+									<th>Deskripsi Laporan</th>
+									<th>Status</th>
+									<th>Created</th>
+									<th></th>
+								</tr>
+							</thead>
+							<tbody>
+								{listData.map((data) => {
+									return (
+										<tr key={data._id}>
+											<td>{data.no_laporan}</td>
+											<td className="text-nowrap">
+												<div className="media align-items-center">
+													<div className="media-body d-flex">
+														<div>
+															<h4 className="m-0">{data.pt.nama}</h4>
+															<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
+														</div>
+													</div>
+												</div>
+											</td>
+											<td>
+												<td>{data.sanksi ? <div className="badge badge-info">Sudah ditetapkan</div> : <div className="badge badge-danger">Belum ditetapkan</div>}</td>
+											</td>
+											<td>{moment(data.createdAt).format("MM-DD-YYYY")}</td>
+											<td>
+												<div className="ml-auto">
+													<Link
+														href={{
+															pathname: data.sanksi ? "/app/sanksi/detail" : "/app/sanksi/proses",
+															query: { id: data.sanksi || data._id },
+														}}
+													>
+														<Button color="primary" size="sm">
+															{data.sanksi ? "Detail" : "Proses Sanksi"}
+														</Button>
+													</Link>
+												</div>
+											</td>
+										</tr>
+									);
+								})}
+							</tbody>
+						</table>
+					</Datatable>
+				)}
+			</div>
+		</div>
+	);
+}
+
+export default TableLaporan;

+ 91 - 91
components/Sanksi/TablePenetapanSanksi.js

@@ -1,91 +1,91 @@
-import React, { Component } from "react";
-import { Card, Table } from "reactstrap";
-import { getPelanggaran } from "@/actions/pelanggaran";
-import { connect } from "react-redux";
-
-export class TablePenetapanSanksi extends Component {
-	checkedData = [];
-
-	constructor(props) {
-		super(props);
-		this.state = {
-			pelanggaran: null,
-			checkedData: [],
-		};
-	}
-
-	componentDidMount = async () => {
-		const { token } = this.props;
-		const pelanggaran = await getPelanggaran(token);
-		this.setState({ pelanggaran });
-	};
-
-	onHandleChange = (evt) => {
-		const checked = evt.target.checked;
-		const item = evt.target.value;
-		if (checked) this.checkedData.push(evt.target.value);
-		else this.checkedData = this.checkedData.filter((e) => e != item);
-		this.props.setCheckedData(this.checkedData);
-	};
-
-	render() {
-		const { pelanggaran } = this.state;
-		return (
-			<Card className="card-default">
-				<Table bordered hover responsive>
-					<thead>
-						<tr>
-							<th>No</th>
-							<th>Jenis Pelanggaran</th>
-							<th>Sanksi</th>
-							<th></th>
-						</tr>
-					</thead>
-					<tbody>
-						{pelanggaran
-							? pelanggaran.data.map((jp, index) => (
-									<tr key={jp._id}>
-										<td>
-											<label>{index + 1}</label>
-										</td>
-										<td>
-											<div className="media align-items-center">
-												<div className="media-body d-flex">
-													<div>
-														<p>{jp.pelanggaran}</p>
-														<p>TMT : {jp.tmt_bulan} Bulan</p>
-														<p>Jenis Sanksi Administratif : {jp.label_sanksi}</p>
-													</div>
-												</div>
-											</div>
-										</td>
-										<td>
-											<div className="media align-items-center">
-												<div className="media-body d-flex">
-													<div>
-														<p>{jp.sanksi}</p>
-														<p>Keterangan : {jp.keterangan_sanksi}</p>
-													</div>
-												</div>
-											</div>
-										</td>
-										<td>
-											<div className="checkbox c-checkbox">
-												<label>
-													<input type="checkbox" value={jp._id} onChange={this.onHandleChange} />
-													<span className="fa fa-check"></span>
-												</label>
-											</div>
-										</td>
-									</tr>
-							  ))
-							: ""}
-					</tbody>
-				</Table>
-			</Card>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(TablePenetapanSanksi);
+import React, { Component } from "react";
+import { Card, Table } from "reactstrap";
+import { getPelanggaran } from "@/actions/pelanggaran";
+import { connect } from "react-redux";
+
+export class TablePenetapanSanksi extends Component {
+	checkedData = [];
+
+	constructor(props) {
+		super(props);
+		this.state = {
+			pelanggaran: null,
+			checkedData: [],
+		};
+	}
+
+	componentDidMount = async () => {
+		const { token } = this.props;
+		const pelanggaran = await getPelanggaran(token);
+		this.setState({ pelanggaran });
+	};
+
+	onHandleChange = (evt) => {
+		const checked = evt.target.checked;
+		const item = evt.target.value;
+		if (checked) this.checkedData.push(evt.target.value);
+		else this.checkedData = this.checkedData.filter((e) => e != item);
+		this.props.setCheckedData(this.checkedData);
+	};
+
+	render() {
+		const { pelanggaran } = this.state;
+		return (
+			<Card className="card-default">
+				<Table bordered hover responsive>
+					<thead>
+						<tr>
+							<th>No</th>
+							<th>Jenis Pelanggaran</th>
+							<th>Sanksi</th>
+							<th></th>
+						</tr>
+					</thead>
+					<tbody>
+						{pelanggaran
+							? pelanggaran.data.map((jp, index) => (
+								<tr key={jp._id}>
+									<td>
+										<label>{index + 1}</label>
+									</td>
+									<td>
+										<div className="media align-items-center">
+											<div className="media-body d-flex">
+												<div>
+													<p>{jp.pelanggaran}</p>
+													<p>TMT : {jp.tmt_bulan} Bulan</p>
+													<p>Jenis Sanksi Administratif : {jp.label_sanksi}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>
+										<div className="media align-items-center">
+											<div className="media-body d-flex">
+												<div>
+													<p>{jp.sanksi}</p>
+													<p>Keterangan : {jp.keterangan_sanksi}</p>
+												</div>
+											</div>
+										</div>
+									</td>
+									<td>
+										<div className="checkbox c-checkbox">
+											<label>
+												<input type="checkbox" value={jp._id} onChange={this.onHandleChange} />
+												<span className="fa fa-check"></span>
+											</label>
+										</div>
+									</td>
+								</tr>
+							))
+							: ""}
+					</tbody>
+				</Table>
+			</Card>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(TablePenetapanSanksi);

+ 125 - 125
components/Sanksi/UploadSurat.js

@@ -1,125 +1,125 @@
-import React, { Component } from "react";
-import { Row, Col, Input, FormGroup } from "reactstrap";
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-export class UploadSurat extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			files: [],
-			nomorSanksi: "",
-			keterangan: "",
-		};
-	}
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-		this.props.setUploadSuratSanksi(this.state);
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-		this.props.setUploadSuratSanksi(this.state);
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-		this.props.setUploadSuratSanksi(this.state);
-	};
-
-	setNomorSanksi = (e) => {
-		this.setState({ nomorSanksi: e.target.value });
-		this.props.setUploadSuratSanksi(this.state);
-	};
-
-	setKeterangan = (e) => {
-		this.setState({ keterangan: e.target.value });
-		this.props.setUploadSuratSanksi(this.state);
-	};
-
-	render() {
-		const { files } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-		return (
-			<form className="form-horizontal" method="get" action="/" onSubmit={this.onSubmit}>
-				<FormGroup row>
-					<label className="col-md-2 col-form-label">Nomor Surat:</label>
-					<div className="col-md-10">
-						<Input type="text" value={this.state.nomorSanksi} onChange={this.setNomorSanksi} />
-					</div>
-				</FormGroup>
-				<FormGroup row className="mt-3">
-					<label className="col-md-2 col-form-label">Keterangan</label>
-					<div className="col-md-10">
-						<Input type="textarea" value={this.state.keterangan} onChange={this.setKeterangan} required />
-						{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
-					</div>
-				</FormGroup>
-				<FormGroup row>
-					<label className="col-md-2 col-form-label">Dokumen Surat Sanksi:</label>
-					<div className="col-md-10">
-						<DropzoneWrapper className="" onDrop={this.onDrop}>
-							{({ getRootProps, getInputProps, isDragActive }) => {
-								return (
-									<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-										<input {...getInputProps()} />
-										<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}</div>
-										<div className="d-flex align-items-center">
-											<small className="ml-auto">
-												<button type="button" className="btn btn-link" onClick={this.clearFiles}>
-													Clear files
-												</button>
-											</small>
-										</div>
-									</div>
-								);
-							}}
-						</DropzoneWrapper>
-						{/* <span className="form-text">Multiple files upload</span> */}
-						<p className="mrgn-top-5">
-							Ukuran setiap dokumen maksimal 15mb
-						</p>
-					</div>
-				</FormGroup>
-			</form>
-		);
-	}
-}
-
-export default UploadSurat;
+import React, { Component } from "react";
+import { Row, Col, Input, FormGroup } from "reactstrap";
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+export class UploadSurat extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			files: [],
+			nomorSanksi: "",
+			keterangan: "",
+		};
+	}
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+		this.props.setUploadSuratSanksi(this.state);
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+		this.props.setUploadSuratSanksi(this.state);
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+		this.props.setUploadSuratSanksi(this.state);
+	};
+
+	setNomorSanksi = (e) => {
+		this.setState({ nomorSanksi: e.target.value });
+		this.props.setUploadSuratSanksi(this.state);
+	};
+
+	setKeterangan = (e) => {
+		this.setState({ keterangan: e.target.value });
+		this.props.setUploadSuratSanksi(this.state);
+	};
+
+	render() {
+		const { files } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+		return (
+			<form className="form-horizontal" method="get" action="/" onSubmit={this.onSubmit}>
+				<FormGroup row>
+					<label className="col-md-2 col-form-label">Nomor Surat:</label>
+					<div className="col-md-10">
+						<Input type="text" value={this.state.nomorSanksi} onChange={this.setNomorSanksi} />
+					</div>
+				</FormGroup>
+				<FormGroup row className="mt-3">
+					<label className="col-md-2 col-form-label">Keterangan</label>
+					<div className="col-md-10">
+						<Input type="textarea" value={this.state.keterangan} onChange={this.setKeterangan} required />
+						{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
+					</div>
+				</FormGroup>
+				<FormGroup row>
+					<label className="col-md-2 col-form-label">Dokumen Surat Sanksi:</label>
+					<div className="col-md-10">
+						<DropzoneWrapper className="" onDrop={this.onDrop}>
+							{({ getRootProps, getInputProps, isDragActive }) => {
+								return (
+									<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+										<input {...getInputProps()} />
+										<div className="dropzone-previews flex">{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}</div>
+										<div className="d-flex align-items-center">
+											<small className="ml-auto">
+												<button type="button" className="btn btn-link" onClick={this.clearFiles}>
+													Clear files
+												</button>
+											</small>
+										</div>
+									</div>
+								);
+							}}
+						</DropzoneWrapper>
+						{/* <span className="form-text">Multiple files upload</span> */}
+						<p className="mrgn-top-5">
+							Ukuran setiap dokumen maksimal 15mb
+						</p>
+					</div>
+				</FormGroup>
+			</form>
+		);
+	}
+}
+
+export default UploadSurat;

+ 63 - 63
components/Tables/Datatable.js

@@ -1,63 +1,63 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import $ from 'jquery';
-
-/**
- * Wrapper component for dataTable plugin
- * Only DOM child elements, componets are not supported (e.g. <Table>)
- */
-export default class Datatable extends Component {
-
-    static propTypes = {
-        /** datatables options object */
-        options: PropTypes.object,
-        /** only one children allowed */
-        children: PropTypes.element.isRequired,
-        /** callback that receives the datatable instance as param */
-        dtInstance: PropTypes.func
-    }
-
-    static defaultProps = {
-        options: {}
-    }
-
-    componentDidMount() {
-        // Datatables
-        require('datatables.net-bs')
-        require('datatables.net-bs4/js/dataTables.bootstrap4.js')
-        require('datatables.net-bs4/css/dataTables.bootstrap4.css')
-        require('datatables.net-buttons')
-        require('datatables.net-buttons-bs')
-        require('datatables.net-responsive')
-        require('datatables.net-responsive-bs')
-        require('datatables.net-responsive-bs/css/responsive.bootstrap.css')
-        require('datatables.net-buttons/js/buttons.colVis.js') // Column visibility
-        require('datatables.net-buttons/js/buttons.html5.js') // HTML 5 file export
-        require('datatables.net-buttons/js/buttons.flash.js') // Flash file export
-        require('datatables.net-buttons/js/buttons.print.js') // Print view button
-        require('datatables.net-keytable');
-        require('datatables.net-keytable-bs/css/keyTable.bootstrap.css')
-        require('jszip/dist/jszip.js');
-        require('pdfmake/build/pdfmake.js');
-        require('pdfmake/build/vfs_fonts.js');
-
-        const dtInstance = $(this.tableElement).dataTable(this.props.options);
-
-        if(this.props.dtInstance)
-            this.props.dtInstance(dtInstance)
-    }
-
-    componentWillUnmount() {
-        $(this.tableElement).dataTable({destroy: true});
-    }
-
-    setRef = node => this.tableElement = node;
-
-    render() {
-        return (
-            React.cloneElement(React.Children.only(this.props.children), {
-                ref: this.setRef
-            })
-        )
-    }
-}
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import $ from 'jquery';
+
+/**
+ * Wrapper component for dataTable plugin
+ * Only DOM child elements, componets are not supported (e.g. <Table>)
+ */
+export default class Datatable extends Component {
+
+    static propTypes = {
+        /** datatables options object */
+        options: PropTypes.object,
+        /** only one children allowed */
+        children: PropTypes.element.isRequired,
+        /** callback that receives the datatable instance as param */
+        dtInstance: PropTypes.func
+    }
+
+    static defaultProps = {
+        options: {}
+    }
+
+    componentDidMount() {
+        // Datatables
+        require('datatables.net-bs')
+        require('datatables.net-bs4/js/dataTables.bootstrap4.js')
+        require('datatables.net-bs4/css/dataTables.bootstrap4.css')
+        require('datatables.net-buttons')
+        require('datatables.net-buttons-bs')
+        require('datatables.net-responsive')
+        require('datatables.net-responsive-bs')
+        require('datatables.net-responsive-bs/css/responsive.bootstrap.css')
+        require('datatables.net-buttons/js/buttons.colVis.js') // Column visibility
+        require('datatables.net-buttons/js/buttons.html5.js') // HTML 5 file export
+        require('datatables.net-buttons/js/buttons.flash.js') // Flash file export
+        require('datatables.net-buttons/js/buttons.print.js') // Print view button
+        require('datatables.net-keytable');
+        require('datatables.net-keytable-bs/css/keyTable.bootstrap.css')
+        require('jszip/dist/jszip.js');
+        require('pdfmake/build/pdfmake.js');
+        require('pdfmake/build/vfs_fonts.js');
+
+        const dtInstance = $(this.tableElement).dataTable(this.props.options);
+
+        if(this.props.dtInstance)
+            this.props.dtInstance(dtInstance)
+    }
+
+    componentWillUnmount() {
+        $(this.tableElement).dataTable({destroy: true});
+    }
+
+    setRef = node => this.tableElement = node;
+
+    render() {
+        return (
+            React.cloneElement(React.Children.only(this.props.children), {
+                ref: this.setRef
+            })
+        )
+    }
+}

+ 24 - 24
pages/404.js

@@ -1,24 +1,24 @@
-import React from "react";
-import BasePage from "@/components/Layout/BasePage";
-import Link from "next/link";
-
-const NotFound = (props) => (
-	<div className="abs-center wd-xl">
-		<div className="text-center mb-4">
-			<div className="text-lg mb-3">404</div>
-			<p className="lead m-0">We couldn't find this page.</p>
-			<p>The page you are looking for does not exists.</p>
-		</div>
-
-		<div className="p-3 text-center">
-			<span>Sistem Informasi Pengendalian Kelembagaan Perguruan Tinggi pada Pendidikan Tinggi Akademik (Sidali Dikti)</span>
-			<br />
-			<span className="mr-2">&copy;</span>
-			<span>2022</span>
-		</div>
-	</div>
-);
-
-NotFound.Layout = BasePage;
-
-export default NotFound;
+import React from "react";
+import BasePage from "@/components/Layout/BasePage";
+import Link from "next/link";
+
+const NotFound = (props) => (
+	<div className="abs-center wd-xl">
+		<div className="text-center mb-4">
+			<div className="text-lg mb-3">404</div>
+			<p className="lead m-0">We couldn't find this page.</p>
+			<p>The page you are looking for does not exists.</p>
+		</div>
+
+		<div className="p-3 text-center">
+			<span>Sistem Informasi Pengendalian Kelembagaan Perguruan Tinggi pada Pendidikan Tinggi Akademik (Sidali Dikti)</span>
+			<br />
+			<span className="mr-2">&copy;</span>
+			<span>2022</span>
+		</div>
+	</div>
+);
+
+NotFound.Layout = BasePage;
+
+export default NotFound;

+ 93 - 93
pages/_app.js

@@ -1,93 +1,93 @@
-/*!
- *
- * Angle - Bootstrap Admin Template
- *
- * Version: 4.8
- * Author: @themicon_co
- * Website: http://themicon.co
- * License: https://wrapbootstrap.com/help/licenses
- *
- */
-
-// Polyfills
-// ======================
-import "../polyfills.js";
-
-// App
-// ======================
-import App from "next/app";
-import React from "react";
-// Redux support
-import { Provider } from "react-redux";
-import withReduxStore from "../store/with-redux-store";
-// Translation support
-import * as Translate from "@/components/Common/Translate";
-// Base Layout
-import Base from "@/components/Layout/Base";
-// import BaseHorizontal from '@/components/Layout/BaseHorizontal';
-
-// Global Vendor
-// ======================
-// Whirl
-import "whirl/dist/whirl.css";
-// Font Awesome
-import "@fortawesome/fontawesome-free/css/brands.css";
-import "@fortawesome/fontawesome-free/css/regular.css";
-import "@fortawesome/fontawesome-free/css/solid.css";
-import "@fortawesome/fontawesome-free/css/fontawesome.css";
-// Animate.CSS
-import "animate.css/animate.min.css";
-// Simple line icons
-import "simple-line-icons/css/simple-line-icons.css";
-// Weather Icons
-import "weather-icons/css/weather-icons.min.css";
-import "weather-icons/css/weather-icons-wind.min.css";
-
-import "@fullcalendar/core/main.css";
-import "@fullcalendar/daygrid/main.css";
-import "@fullcalendar/timegrid/main.css";
-import "@fullcalendar/list/main.css";
-import "@fullcalendar/bootstrap/main.css";
-import "react-datetime/css/react-datetime.css";
-
-// App Styes
-// ======================
-import "../styles/bootstrap.scss";
-import "../styles/app.scss";
-import "react-perfect-scrollbar/dist/css/styles.css";
-
-// https://nextjs.org/docs/#custom-app
-
-class MyApp extends App {
-	static async getInitialProps({ Component, ctx }) {
-		let pageProps = {};
-
-		if (Component.getInitialProps) {
-			pageProps = await Component.getInitialProps(ctx);
-		}
-
-		// Require the initial dictionary.
-		// Use require to avoid 'fs' module
-		Translate.setDict("en", require("@/public/static/locales/en/translations.json"));
-		// The store has been updated in previous call,
-		// pass it down as initial prop so client can use it.
-		return { pageProps, store: Translate.store };
-	}
-
-	render() {
-		const { Component, pageProps, reduxStore, store } = this.props;
-		const Layout = Component.Layout ? Component.Layout : Base;
-		const ComponentWithTranslation = Translate.withTranslation(Component);
-		return (
-			<Provider store={reduxStore}>
-				<Translate.Provider store={store}>
-					<Layout>
-						<ComponentWithTranslation {...pageProps} />
-					</Layout>
-				</Translate.Provider>
-			</Provider>
-		);
-	}
-}
-
-export default withReduxStore(MyApp);
+/*!
+ *
+ * Angle - Bootstrap Admin Template
+ *
+ * Version: 4.8
+ * Author: @themicon_co
+ * Website: http://themicon.co
+ * License: https://wrapbootstrap.com/help/licenses
+ *
+ */
+
+// Polyfills
+// ======================
+import "../polyfills.js";
+
+// App
+// ======================
+import App from "next/app";
+import React from "react";
+// Redux support
+import { Provider } from "react-redux";
+import withReduxStore from "../store/with-redux-store";
+// Translation support
+import * as Translate from "@/components/Common/Translate";
+// Base Layout
+import Base from "@/components/Layout/Base";
+// import BaseHorizontal from '@/components/Layout/BaseHorizontal';
+
+// Global Vendor
+// ======================
+// Whirl
+import "whirl/dist/whirl.css";
+// Font Awesome
+import "@fortawesome/fontawesome-free/css/brands.css";
+import "@fortawesome/fontawesome-free/css/regular.css";
+import "@fortawesome/fontawesome-free/css/solid.css";
+import "@fortawesome/fontawesome-free/css/fontawesome.css";
+// Animate.CSS
+import "animate.css/animate.min.css";
+// Simple line icons
+import "simple-line-icons/css/simple-line-icons.css";
+// Weather Icons
+import "weather-icons/css/weather-icons.min.css";
+import "weather-icons/css/weather-icons-wind.min.css";
+
+import "@fullcalendar/core/main.css";
+import "@fullcalendar/daygrid/main.css";
+import "@fullcalendar/timegrid/main.css";
+import "@fullcalendar/list/main.css";
+import "@fullcalendar/bootstrap/main.css";
+import "react-datetime/css/react-datetime.css";
+
+// App Styes
+// ======================
+import "../styles/bootstrap.scss";
+import "../styles/app.scss";
+import "react-perfect-scrollbar/dist/css/styles.css";
+
+// https://nextjs.org/docs/#custom-app
+
+class MyApp extends App {
+	static async getInitialProps({ Component, ctx }) {
+		let pageProps = {};
+
+		if (Component.getInitialProps) {
+			pageProps = await Component.getInitialProps(ctx);
+		}
+
+		// Require the initial dictionary.
+		// Use require to avoid 'fs' module
+		Translate.setDict("en", require("@/public/static/locales/en/translations.json"));
+		// The store has been updated in previous call,
+		// pass it down as initial prop so client can use it.
+		return { pageProps, store: Translate.store };
+	}
+
+	render() {
+		const { Component, pageProps, reduxStore, store } = this.props;
+		const Layout = Component.Layout ? Component.Layout : Base;
+		const ComponentWithTranslation = Translate.withTranslation(Component);
+		return (
+			<Provider store={reduxStore}>
+				<Translate.Provider store={store}>
+					<Layout>
+						<ComponentWithTranslation {...pageProps} />
+					</Layout>
+				</Translate.Provider>
+			</Provider>
+		);
+	}
+}
+
+export default withReduxStore(MyApp);

+ 25 - 25
pages/_error.js

@@ -1,25 +1,25 @@
-import React from 'react';
-import ContentWrapper from '@/components/Layout/ContentWrapper';
-
-// https://nextjs.org/docs/#custom-error-handling
-class Error extends React.Component {
-    static getInitialProps({ res, err }) {
-        const statusCode = res ? res.statusCode : err ? err.statusCode : null;
-        return {
-            namespacesRequired: ['translations'],
-            statusCode
-        };
-    }
-
-    render() {
-        return (
-            <ContentWrapper>
-                {this.props.statusCode
-                    ? `An error ${this.props.statusCode} occurred on server`
-                    : 'An error occurred on client'}
-            </ContentWrapper>
-        );
-    }
-}
-
-export default Error;
+import React from 'react';
+import ContentWrapper from '@/components/Layout/ContentWrapper';
+
+// https://nextjs.org/docs/#custom-error-handling
+class Error extends React.Component {
+    static getInitialProps({ res, err }) {
+        const statusCode = res ? res.statusCode : err ? err.statusCode : null;
+        return {
+            namespacesRequired: ['translations'],
+            statusCode
+        };
+    }
+
+    render() {
+        return (
+            <ContentWrapper>
+                {this.props.statusCode
+                    ? `An error ${this.props.statusCode} occurred on server`
+                    : 'An error occurred on client'}
+            </ContentWrapper>
+        );
+    }
+}
+
+export default Error;

+ 2 - 2
pages/api/profile.js

@@ -1,3 +1,3 @@
-export default function handler(req, res) {
-    res.status(200).json({ text: 'Hello' })
+export default function handler(req, res) {
+    res.status(200).json({ text: 'Hello' })
   }

+ 308 - 308
pages/app/banding/detail.js

@@ -1,308 +1,308 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import Link from "next/link";
-import Select from "react-select";
-import DetailSanksi from "@/components/Main/DetailSanksi";
-import Header from "@/components/Main/Header";
-import DetailPT from "@/components/Main/DetailPT";
-import PermohonanPT from "@/components/Main/PermohonanPT";
-import Riwayat from "@/components/Banding/Riwayat";
-import { getOneSanksi } from "@/actions/sanksi";
-import { addJawabanBanding } from "@/actions/banding";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Card, CardBody, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
-import { getPT } from "@/actions/PT";
-import Loader from "@/components/Common/Loader";
-import { toast } from "react-toastify";
-import { connect } from "react-redux";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-const selectInstanceId = 1;
-
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-const jawabanBandingSchema = Yup.object().shape({
-	status: Yup.string().required("Harap Diisi"),
-	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Required").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-class JawabanBanding extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			modal: false,
-			selectedOption: null,
-			files: [],
-			sanksi: {},
-			pt: null,
-		};
-	}
-
-	static getInitialProps = async ({ query }) => {
-		return { query };
-	};
-
-	componentDidMount = async () => {
-		const { query, token } = this.props;
-		const sanksi = await getOneSanksi(token, query.id, { banding: true });
-		const pt = sanksi.data.laporan.pt;
-		this.setState({ sanksi, pt });
-	};
-
-	toggleModal = () => {
-		this.setState({
-			modal: !this.state.modal,
-		});
-	};
-
-	handleChangeSelect = (selectedOption) => {
-		this.setState({ selectedOption });
-	};
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	handelSimpan = async () => {
-		if (this.state.modal === true) {
-			this.toggleModal();
-		}
-		const { data } = this.state;
-		const { query, token } = this.props;
-		const { id } = query;
-		const formdata = new FormData();
-		formdata.append("status", data.status);
-		data.dokumen.forEach((e) => {
-			formdata.append("dokumen", e);
-		});
-		const toastid = toast.loading("Please wait...");
-		const added = await addJawabanBanding(token, id, formdata);
-		if (!added) {
-			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
-		} else {
-			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
-			Router.push({
-				pathname: "/app/banding",
-			});
-		}
-	};
-
-	render() {
-		const { files, sanksi, pt } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-
-		return (
-			<ContentWrapper unwrap>
-				{/* <Header /> */}
-				<div className="p-3">
-					<div className="content-heading">
-						<div>Jawaban Permohonan Banding</div>
-						<div className="ml-auto">
-							<Link href="/app/banding">
-								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
-							</Link>
-						</div>
-					</div>
-					<Row>
-						{sanksi.data ? (
-							<Col xl={9}>
-								<Card className="card-default">
-									<CardBody>
-										<Row>
-											<Col lg={12}>
-												<DetailSanksi data={sanksi.data} />
-												<PermohonanPT data={sanksi.data.pengajuan.banding} title="Permohonan Banding" />
-												<p className="lead bb">Jawaban</p>
-												<Formik
-													initialValues={{
-														status: "",
-														dokumen: [],
-													}}
-													validationSchema={jawabanBandingSchema}
-													onSubmit={async (data) => {
-														this.setState({ data });
-														if (sanksi.data.jawaban?.banding) this.toggleModal();
-														else await this.handelSimpan();
-													}}
-												>
-													{() => (
-														<Form className="form-horizontal">
-															<FormGroup>
-																<label className="row-form-label">Status:</label>
-																<div className="row-md-10">
-																	<Field name="status">
-																		{({ field, form, meta }) => (
-																			<Select
-																				instanceId={selectInstanceId + 1}
-																				value={this.state.selectedOption}
-																				onChange={(e) => {
-																					this.handleChangeSelect(e);
-																					form.setFieldValue(field.name, e.value);
-																				}}
-																				options={[
-																					{ value: "Ditolak", label: "Ditolak", className: "State-ACT" },
-																					{ value: "Dikabulkan", label: "Dikabulkan", className: "State-ACT" },
-																				]}
-																			/>
-																		)}
-																	</Field>
-																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
-																</div>
-															</FormGroup>
-															<FormGroup>
-																<label className="row-form-label">Dokumen Jawaban:</label>
-																<div className="row-md-10">
-																	<Field name="dokumen">
-																		{({ field, form }) => (
-																			<DropzoneWrapper
-																				className=""
-																				onDrop={(e) => {
-																					this.onDrop(e);
-																					form.setFieldValue(field.name, e);
-																				}}
-																			>
-																				{({ getRootProps, getInputProps, isDragActive }) => {
-																					return (
-																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-																							<input {...getInputProps()} />
-																							<div className="dropzone-previews flex">
-																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}
-																							</div>
-																							<div className="d-flex align-items-center">
-																								<small className="ml-auto">
-																									<button
-																										type="button"
-																										className="btn btn-link"
-																										onClick={(e) => {
-																											this.clearFiles(e);
-																											form.setFieldValue(field.name, []);
-																										}}
-																									>
-																										Clear files
-																									</button>
-																								</small>
-																							</div>
-																						</div>
-																					);
-																				}}
-																			</DropzoneWrapper>
-																		)}
-																	</Field>
-																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-																	<p className="mrgn-top-5">
-																		Ukuran setiap dokumen maksimal 15mb
-																	</p>
-																</div>
-															</FormGroup>
-															<FormGroup row>
-																<div className="col-xl-10">
-																	<Button color="primary" type="submit">
-																		Simpan
-																	</Button>
-																</div>
-															</FormGroup>
-														</Form>
-													)}
-												</Formik>
-											</Col>
-										</Row>
-									</CardBody>
-								</Card>
-							</Col>
-						) : (
-							<Loader />
-						)}
-						<Col xl={3}>{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
-					</Row>
-					{sanksi.data && (
-						<Row>
-							<Col>
-								<Riwayat data={sanksi.data.jawaban.banding} />
-							</Col>
-						</Row>
-					)}
-				</div>
-				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
-					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
-					<ModalFooter>
-						<Button color="primary" onClick={this.handelSimpan}>
-							Ya
-						</Button>{" "}
-						<Button color="secondary" onClick={this.toggleModal}>
-							Tidak
-						</Button>
-					</ModalFooter>
-				</Modal>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(JawabanBanding);
+import React, { Component } from "react";
+import Router from "next/router";
+import Link from "next/link";
+import Select from "react-select";
+import DetailSanksi from "@/components/Main/DetailSanksi";
+import Header from "@/components/Main/Header";
+import DetailPT from "@/components/Main/DetailPT";
+import PermohonanPT from "@/components/Main/PermohonanPT";
+import Riwayat from "@/components/Banding/Riwayat";
+import { getOneSanksi } from "@/actions/sanksi";
+import { addJawabanBanding } from "@/actions/banding";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col, Card, CardBody, FormGroup, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
+import { getPT } from "@/actions/PT";
+import Loader from "@/components/Common/Loader";
+import { toast } from "react-toastify";
+import { connect } from "react-redux";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+const selectInstanceId = 1;
+
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+const jawabanBandingSchema = Yup.object().shape({
+	status: Yup.string().required("Harap Diisi"),
+	dokumen: Yup.array().min(1, "Minimal terdapat 1 dokumen").required("Required").test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+class JawabanBanding extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			modal: false,
+			selectedOption: null,
+			files: [],
+			sanksi: {},
+			pt: null,
+		};
+	}
+
+	static getInitialProps = async ({ query }) => {
+		return { query };
+	};
+
+	componentDidMount = async () => {
+		const { query, token } = this.props;
+		const sanksi = await getOneSanksi(token, query.id, { banding: true });
+		const pt = sanksi.data.laporan.pt;
+		this.setState({ sanksi, pt });
+	};
+
+	toggleModal = () => {
+		this.setState({
+			modal: !this.state.modal,
+		});
+	};
+
+	handleChangeSelect = (selectedOption) => {
+		this.setState({ selectedOption });
+	};
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	handelSimpan = async () => {
+		if (this.state.modal === true) {
+			this.toggleModal();
+		}
+		const { data } = this.state;
+		const { query, token } = this.props;
+		const { id } = query;
+		const formdata = new FormData();
+		formdata.append("status", data.status);
+		data.dokumen.forEach((e) => {
+			formdata.append("dokumen", e);
+		});
+		const toastid = toast.loading("Please wait...");
+		const added = await addJawabanBanding(token, id, formdata);
+		if (!added) {
+			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
+		} else {
+			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
+			Router.push({
+				pathname: "/app/banding",
+			});
+		}
+	};
+
+	render() {
+		const { files, sanksi, pt } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+
+		return (
+			<ContentWrapper unwrap>
+				{/* <Header /> */}
+				<div className="p-3">
+					<div className="content-heading">
+						<div>Jawaban Permohonan Banding</div>
+						<div className="ml-auto">
+							<Link href="/app/banding">
+								<button className="btn btn-sm btn-secondary text-sm">&lt; kembali</button>
+							</Link>
+						</div>
+					</div>
+					<Row>
+						{sanksi.data ? (
+							<Col xl={9}>
+								<Card className="card-default">
+									<CardBody>
+										<Row>
+											<Col lg={12}>
+												<DetailSanksi data={sanksi.data} />
+												<PermohonanPT data={sanksi.data.pengajuan.banding} title="Permohonan Banding" />
+												<p className="lead bb">Jawaban</p>
+												<Formik
+													initialValues={{
+														status: "",
+														dokumen: [],
+													}}
+													validationSchema={jawabanBandingSchema}
+													onSubmit={async (data) => {
+														this.setState({ data });
+														if (sanksi.data.jawaban?.banding) this.toggleModal();
+														else await this.handelSimpan();
+													}}
+												>
+													{() => (
+														<Form className="form-horizontal">
+															<FormGroup>
+																<label className="row-form-label">Status:</label>
+																<div className="row-md-10">
+																	<Field name="status">
+																		{({ field, form, meta }) => (
+																			<Select
+																				instanceId={selectInstanceId + 1}
+																				value={this.state.selectedOption}
+																				onChange={(e) => {
+																					this.handleChangeSelect(e);
+																					form.setFieldValue(field.name, e.value);
+																				}}
+																				options={[
+																					{ value: "Ditolak", label: "Ditolak", className: "State-ACT" },
+																					{ value: "Dikabulkan", label: "Dikabulkan", className: "State-ACT" },
+																				]}
+																			/>
+																		)}
+																	</Field>
+																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
+																</div>
+															</FormGroup>
+															<FormGroup>
+																<label className="row-form-label">Dokumen Jawaban:</label>
+																<div className="row-md-10">
+																	<Field name="dokumen">
+																		{({ field, form }) => (
+																			<DropzoneWrapper
+																				className=""
+																				onDrop={(e) => {
+																					this.onDrop(e);
+																					form.setFieldValue(field.name, e);
+																				}}
+																			>
+																				{({ getRootProps, getInputProps, isDragActive }) => {
+																					return (
+																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+																							<input {...getInputProps()} />
+																							<div className="dropzone-previews flex">
+																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}
+																							</div>
+																							<div className="d-flex align-items-center">
+																								<small className="ml-auto">
+																									<button
+																										type="button"
+																										className="btn btn-link"
+																										onClick={(e) => {
+																											this.clearFiles(e);
+																											form.setFieldValue(field.name, []);
+																										}}
+																									>
+																										Reset dokumen
+																									</button>
+																								</small>
+																							</div>
+																						</div>
+																					);
+																				}}
+																			</DropzoneWrapper>
+																		)}
+																	</Field>
+																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+																	<p className="mrgn-top-5">
+																		Ukuran setiap dokumen maksimal 15mb
+																	</p>
+																</div>
+															</FormGroup>
+															<FormGroup row>
+																<div className="col-xl-10">
+																	<Button color="primary" type="submit">
+																		Simpan
+																	</Button>
+																</div>
+															</FormGroup>
+														</Form>
+													)}
+												</Formik>
+											</Col>
+										</Row>
+									</CardBody>
+								</Card>
+							</Col>
+						) : (
+							<Loader />
+						)}
+						<Col xl={3}>{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
+					</Row>
+					{sanksi.data && (
+						<Row>
+							<Col>
+								<Riwayat data={sanksi.data.jawaban.banding} />
+							</Col>
+						</Row>
+					)}
+				</div>
+				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
+					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
+					<ModalFooter>
+						<Button color="primary" onClick={this.handelSimpan}>
+							Ya
+						</Button>{" "}
+						<Button color="secondary" onClick={this.toggleModal}>
+							Tidak
+						</Button>
+					</ModalFooter>
+				</Modal>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(JawabanBanding);

+ 41 - 41
pages/app/banding/index.js

@@ -1,41 +1,41 @@
-import React, { Component } from "react";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col } from "reactstrap";
-import CaseProgress from "@/components/Main/CaseProgress";
-import TableSanksi from "@/components/Banding/TableSanksi";
-import { getSanksi } from "@/actions/sanksi";
-import Loader from "@/components/Common/Loader";
-import { connect } from "react-redux";
-
-class Banding extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			sanksi: {},
-		};
-	}
-
-	componentDidMount = async () => {
-		const { token } = this.props;
-		const sanksi = await getSanksi(token, { banding: true });
-		this.setState({ sanksi });
-	};
-
-	render() {
-		const { sanksi } = this.state;
-		return (
-			<ContentWrapper>
-				<div className="content-heading">Banding</div>
-				<Row>
-					<Col lg="4">
-						<CaseProgress />
-					</Col>
-					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/banding/detail" linkName="Detail" /> : <Loader />}</Col>
-				</Row>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(Banding);
+import React, { Component } from "react";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col } from "reactstrap";
+import CaseProgress from "@/components/Main/CaseProgress";
+import TableSanksi from "@/components/Banding/TableSanksi";
+import { getSanksi } from "@/actions/sanksi";
+import Loader from "@/components/Common/Loader";
+import { connect } from "react-redux";
+
+class Banding extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			sanksi: {},
+		};
+	}
+
+	componentDidMount = async () => {
+		const { token } = this.props;
+		const sanksi = await getSanksi(token, { banding: true });
+		this.setState({ sanksi });
+	};
+
+	render() {
+		const { sanksi } = this.state;
+		return (
+			<ContentWrapper>
+				<div className="content-heading">Banding</div>
+				<Row>
+					<Col lg="4">
+						<CaseProgress />
+					</Col>
+					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/banding/detail" linkName="Detail" /> : <Loader />}</Col>
+				</Row>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(Banding);

+ 3 - 3
pages/app/index.js

@@ -60,10 +60,10 @@ class App extends Component {
 					<Jumbotron>
 						<Row className="home-1">
 							<Col lg={8} className="d-flex flex-column justify-content-center align-items-start">
-								<h1 className="display-5 home-2">Sistem Informasi Pengendalian Kelembagaan Perguruan Tinggi pada Pendidikan Tinggi Akademik</h1>
-								<p className="lead">Layanan Pelaporan Pelanggaran Perguruan Tinggi Penyelenggara Pendidikan Tinggi Akademik</p>
+								<h1 className="display-5 home-2 txt-size">Sistem Informasi Pengendalian Kelembagaan Perguruan Tinggi pada Pendidikan Tinggi Akademik</h1>
+								<p className="lead txt-size">Layanan Pelaporan Pelanggaran Perguruan Tinggi Penyelenggara Pendidikan Tinggi Akademik</p>
 								<hr className="my-4" />
-								<p>Disediakan kepada masyarakat untuk melaporkan pelanggaran perguruan tinggi yang menyelenggarakan pendidikan tinggi akademik</p>
+								<p className="txt-size">Disediakan kepada masyarakat untuk melaporkan pelanggaran perguruan tinggi yang menyelenggarakan pendidikan tinggi akademik</p>
 								<p className="lead">
 									{/* <Link href="/laporan/new">
 										<button className="btn btn-info btn-lg"><img className="icon-buatlaporan" src="/static/img/icon-buat-laporan.png" alt="icon"/>Buat Laporan</button>

+ 338 - 338
pages/app/keberatan/detail.js

@@ -1,338 +1,338 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import Link from "next/link";
-import Select from "react-select";
-import DetailSanksi from "@/components/Main/DetailSanksi";
-import Header from "@/components/Main/Header";
-import DetailPT from "@/components/Main/DetailPT";
-import PermohonanPT from "@/components/Main/PermohonanPT";
-import Riwayat from "@/components/Keberatan/Riwayat";
-import { getOneSanksi } from "@/actions/sanksi";
-import { addJawabanKeberatan } from "@/actions/keberatan";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Card, CardBody, FormGroup, Input, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
-import { getPT } from "@/actions/PT";
-import Loader from "@/components/Common/Loader";
-import { toast } from "react-toastify";
-import { connect } from "react-redux";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-const selectInstanceId = 1;
-
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const jawabanKeberatanSchema = Yup.object().shape({
-	status: Yup.string().required("Harap Diisi"),
-	keterangan: Yup.string().min(3).max(200).required("Harap Diisi"),
-	dokumen: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-class DetailKeberatan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			modal: false,
-			selectedOption: null,
-			files: [],
-			keterangan: "",
-			sanksi: {},
-			pt: null,
-			data: {},
-		};
-	}
-
-	static getInitialProps = async ({ query }) => {
-		return { query };
-	};
-
-	componentDidMount = async () => {
-		const { query, token } = this.props;
-		const sanksi = await getOneSanksi(token, query.id);
-		const pt = sanksi.data.laporan.pt;
-		this.setState({ sanksi, pt });
-	};
-
-	toggleModal = () => {
-		this.setState({
-			modal: !this.state.modal,
-		});
-	};
-
-	handleChangeSelect = (selectedOption) => {
-		this.setState({ selectedOption });
-	};
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	handelSimpan = async () => {
-		if (this.state.modal === true) {
-			this.toggleModal();
-		}
-		const { data } = this.state;
-		const { query, token } = this.props;
-		const { id } = query;
-		const formdata = new FormData();
-		formdata.append("keterangan", data.keterangan);
-		formdata.append("status", data.status);
-		data.dokumen.forEach((e) => {
-			formdata.append("dokumen", e);
-		});
-		const toastid = toast.loading("Please wait...");
-		const added = await addJawabanKeberatan(token, id, formdata);
-		if (!added) {
-			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
-		} else {
-			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
-			Router.push({
-				pathname: "/app/keberatan",
-			});
-		}
-	};
-
-	render() {
-		const { files, sanksi, pt } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-
-		return (
-			<ContentWrapper unwrap>
-				{/* <Header /> */}
-				<div className="p-3">
-					<div className="content-heading">
-						<div>Permohonan Keberatan</div>
-						<div className="ml-auto">
-							<Link href="/app/keberatan">
-								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
-							</Link>
-						</div>
-					</div>
-					<Row>
-						{sanksi.data ? (
-							<Col xl="9">
-								<Card className="card-default">
-									<CardBody>
-										<Row>
-											<Col lg={12}>
-												<DetailSanksi data={sanksi.data} />
-											</Col>
-										</Row>
-										<Row>
-											<Col lg={12}>
-												<PermohonanPT data={sanksi.data.pengajuan.keberatan} title="Permohonan Keberatan" />
-											</Col>
-										</Row>
-										<Row>
-											<Col lg={12}>
-												<p className="lead bb">Jawaban</p>
-												<Formik
-													initialValues={{
-														status: "",
-														keterangan: "",
-														dokumen: [],
-													}}
-													validationSchema={jawabanKeberatanSchema}
-													onSubmit={async (data) => {
-														this.setState({ data });
-														if (sanksi.data.jawaban?.keberatan) this.toggleModal();
-														else await this.handelSimpan();
-													}}
-												>
-													{() => (
-														<Form className="form-horizontal">
-															<FormGroup>
-																<label className="row-form-label">Status:</label>
-																<div className="row-md-10">
-																	<Field name="status">
-																		{({ field, form, meta }) => (
-																			<Select
-																				instanceId={selectInstanceId + 1}
-																				value={this.state.selectedOption}
-																				onChange={(e) => {
-																					this.handleChangeSelect(e);
-																					form.setFieldValue(field.name, e.value);
-																				}}
-																				options={[
-																					{ value: "Menolak", label: "Menolak", className: "State-ACT" },
-																					{ value: "Mengubah Keputusan", label: "Mengubah Keputusan", className: "State-ACT" },
-																					{ value: "Membatalkan Keputusan", label: "Membatalkan Keputusan", className: "State-ACT" },
-																				]}
-																			/>
-																		)}
-																	</Field>
-																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
-																</div>
-															</FormGroup>
-															<FormGroup>
-																<label className="row-form-label">Keterangan Jawaban:</label>
-																<div className="row-md-10">
-																	<Field name="keterangan">{({ field }) => <Input type="textarea" {...field} />}</Field>
-																	<ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
-																	{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
-																</div>
-															</FormGroup>
-															<FormGroup>
-																<label className="row-form-label">Dokumen Jawaban:</label>
-																<div className="row-md-10">
-																	<Field name="dokumen">
-																		{({ field, form }) => (
-																			<DropzoneWrapper
-																				className=""
-																				onDrop={(e) => {
-																					this.onDrop(e);
-																					form.setFieldValue(field.name, e);
-																				}}
-																			>
-																				{({ getRootProps, getInputProps, isDragActive }) => {
-																					return (
-																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-																							<input {...getInputProps()} />
-																							<div className="dropzone-previews flex">
-																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}
-																							</div>
-																							<div className="d-flex align-items-center">
-																								<small className="ml-auto">
-																									<button
-																										type="button"
-																										className="btn btn-link"
-																										onClick={(e) => {
-																											this.clearFiles(e);
-																											form.setFieldValue(field.name, []);
-																										}}
-																									>
-																										Clear files
-																									</button>
-																								</small>
-																							</div>
-																						</div>
-																					);
-																				}}
-																			</DropzoneWrapper>
-																		)}
-																	</Field>
-																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-																	<p className="mrgn-top-5">
-																		Ukuran setiap dokumen maksimal 15mb
-																	</p>
-																</div>
-															</FormGroup>
-															{/* <FormGroup>
-													<div className="row-xl-10"> */}
-															<FormGroup row>
-																<div className="col-xl-10">
-																	<Button color="primary" type="submit">
-																		Simpan
-																	</Button>
-																</div>
-															</FormGroup>
-															{/* <Button color="primary" onClick={sanksi.data.jawaban?.keberatan ? this.toggleModal : this.handelSimpan}>
-															Simpan
-														</Button> */}
-															{/* </div>
-												</FormGroup> */}
-														</Form>
-													)}
-												</Formik>
-											</Col>
-										</Row>
-									</CardBody>
-								</Card>
-							</Col>
-						) : (
-							<Loader />
-						)}
-						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
-					</Row>
-					{sanksi.data && (
-						<Row>
-							<Col>
-								<Riwayat data={sanksi.data.jawaban?.keberatan ? sanksi.data.jawaban.keberatan : null} />
-							</Col>
-						</Row>
-					)}
-				</div>
-				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
-					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
-					<ModalFooter>
-						<Button color="primary" onClick={this.handelSimpan}>
-							Ya
-						</Button>{" "}
-						<Button color="secondary" onClick={this.toggleModal}>
-							Tidak
-						</Button>
-					</ModalFooter>
-				</Modal>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(DetailKeberatan);
+import React, { Component } from "react";
+import Router from "next/router";
+import Link from "next/link";
+import Select from "react-select";
+import DetailSanksi from "@/components/Main/DetailSanksi";
+import Header from "@/components/Main/Header";
+import DetailPT from "@/components/Main/DetailPT";
+import PermohonanPT from "@/components/Main/PermohonanPT";
+import Riwayat from "@/components/Keberatan/Riwayat";
+import { getOneSanksi } from "@/actions/sanksi";
+import { addJawabanKeberatan } from "@/actions/keberatan";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col, Card, CardBody, FormGroup, Input, Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
+import { getPT } from "@/actions/PT";
+import Loader from "@/components/Common/Loader";
+import { toast } from "react-toastify";
+import { connect } from "react-redux";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+const selectInstanceId = 1;
+
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const jawabanKeberatanSchema = Yup.object().shape({
+	status: Yup.string().required("Harap Diisi"),
+	keterangan: Yup.string().min(3).max(200).required("Harap Diisi"),
+	dokumen: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+class DetailKeberatan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			modal: false,
+			selectedOption: null,
+			files: [],
+			keterangan: "",
+			sanksi: {},
+			pt: null,
+			data: {},
+		};
+	}
+
+	static getInitialProps = async ({ query }) => {
+		return { query };
+	};
+
+	componentDidMount = async () => {
+		const { query, token } = this.props;
+		const sanksi = await getOneSanksi(token, query.id);
+		const pt = sanksi.data.laporan.pt;
+		this.setState({ sanksi, pt });
+	};
+
+	toggleModal = () => {
+		this.setState({
+			modal: !this.state.modal,
+		});
+	};
+
+	handleChangeSelect = (selectedOption) => {
+		this.setState({ selectedOption });
+	};
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	handelSimpan = async () => {
+		if (this.state.modal === true) {
+			this.toggleModal();
+		}
+		const { data } = this.state;
+		const { query, token } = this.props;
+		const { id } = query;
+		const formdata = new FormData();
+		formdata.append("keterangan", data.keterangan);
+		formdata.append("status", data.status);
+		data.dokumen.forEach((e) => {
+			formdata.append("dokumen", e);
+		});
+		const toastid = toast.loading("Please wait...");
+		const added = await addJawabanKeberatan(token, id, formdata);
+		if (!added) {
+			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
+		} else {
+			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
+			Router.push({
+				pathname: "/app/keberatan",
+			});
+		}
+	};
+
+	render() {
+		const { files, sanksi, pt } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+
+		return (
+			<ContentWrapper unwrap>
+				{/* <Header /> */}
+				<div className="p-3">
+					<div className="content-heading">
+						<div>Permohonan Keberatan</div>
+						<div className="ml-auto">
+							<Link href="/app/keberatan">
+								<button className="btn btn-sm btn-secondary text-sm">&lt; kembali</button>
+							</Link>
+						</div>
+					</div>
+					<Row>
+						{sanksi.data ? (
+							<Col xl="9">
+								<Card className="card-default">
+									<CardBody>
+										<Row>
+											<Col lg={12}>
+												<DetailSanksi data={sanksi.data} />
+											</Col>
+										</Row>
+										<Row>
+											<Col lg={12}>
+												<PermohonanPT data={sanksi.data.pengajuan.keberatan} title="Permohonan Keberatan" />
+											</Col>
+										</Row>
+										<Row>
+											<Col lg={12}>
+												<p className="lead bb">Jawaban</p>
+												<Formik
+													initialValues={{
+														status: "",
+														keterangan: "",
+														dokumen: [],
+													}}
+													validationSchema={jawabanKeberatanSchema}
+													onSubmit={async (data) => {
+														this.setState({ data });
+														if (sanksi.data.jawaban?.keberatan) this.toggleModal();
+														else await this.handelSimpan();
+													}}
+												>
+													{() => (
+														<Form className="form-horizontal">
+															<FormGroup>
+																<label className="row-form-label">Status:</label>
+																<div className="row-md-10">
+																	<Field name="status">
+																		{({ field, form, meta }) => (
+																			<Select
+																				instanceId={selectInstanceId + 1}
+																				value={this.state.selectedOption}
+																				onChange={(e) => {
+																					this.handleChangeSelect(e);
+																					form.setFieldValue(field.name, e.value);
+																				}}
+																				options={[
+																					{ value: "Menolak", label: "Menolak", className: "State-ACT" },
+																					{ value: "Mengubah Keputusan", label: "Mengubah Keputusan", className: "State-ACT" },
+																					{ value: "Membatalkan Keputusan", label: "Membatalkan Keputusan", className: "State-ACT" },
+																				]}
+																			/>
+																		)}
+																	</Field>
+																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
+																</div>
+															</FormGroup>
+															<FormGroup>
+																<label className="row-form-label">Keterangan Jawaban:</label>
+																<div className="row-md-10">
+																	<Field name="keterangan">{({ field }) => <Input type="textarea" {...field} />}</Field>
+																	<ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
+																	{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
+																</div>
+															</FormGroup>
+															<FormGroup>
+																<label className="row-form-label">Dokumen Jawaban:</label>
+																<div className="row-md-10">
+																	<Field name="dokumen">
+																		{({ field, form }) => (
+																			<DropzoneWrapper
+																				className=""
+																				onDrop={(e) => {
+																					this.onDrop(e);
+																					form.setFieldValue(field.name, e);
+																				}}
+																			>
+																				{({ getRootProps, getInputProps, isDragActive }) => {
+																					return (
+																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+																							<input {...getInputProps()} />
+																							<div className="dropzone-previews flex">
+																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}
+																							</div>
+																							<div className="d-flex align-items-center">
+																								<small className="ml-auto">
+																									<button
+																										type="button"
+																										className="btn btn-link"
+																										onClick={(e) => {
+																											this.clearFiles(e);
+																											form.setFieldValue(field.name, []);
+																										}}
+																									>
+																										Reset dokumen
+																									</button>
+																								</small>
+																							</div>
+																						</div>
+																					);
+																				}}
+																			</DropzoneWrapper>
+																		)}
+																	</Field>
+																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+																	<p className="mrgn-top-5">
+																		Ukuran setiap dokumen maksimal 15mb
+																	</p>
+																</div>
+															</FormGroup>
+															{/* <FormGroup>
+													<div className="row-xl-10"> */}
+															<FormGroup row>
+																<div className="col-xl-10">
+																	<Button color="primary" type="submit">
+																		Simpan
+																	</Button>
+																</div>
+															</FormGroup>
+															{/* <Button color="primary" onClick={sanksi.data.jawaban?.keberatan ? this.toggleModal : this.handelSimpan}>
+															Simpan
+														</Button> */}
+															{/* </div>
+												</FormGroup> */}
+														</Form>
+													)}
+												</Formik>
+											</Col>
+										</Row>
+									</CardBody>
+								</Card>
+							</Col>
+						) : (
+							<Loader />
+						)}
+						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
+					</Row>
+					{sanksi.data && (
+						<Row>
+							<Col>
+								<Riwayat data={sanksi.data.jawaban?.keberatan ? sanksi.data.jawaban.keberatan : null} />
+							</Col>
+						</Row>
+					)}
+				</div>
+				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
+					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
+					<ModalFooter>
+						<Button color="primary" onClick={this.handelSimpan}>
+							Ya
+						</Button>{" "}
+						<Button color="secondary" onClick={this.toggleModal}>
+							Tidak
+						</Button>
+					</ModalFooter>
+				</Modal>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(DetailKeberatan);

+ 41 - 41
pages/app/keberatan/index.js

@@ -1,41 +1,41 @@
-import React, { Component } from "react";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col } from "reactstrap";
-import CaseProgress from "@/components/Main/CaseProgress";
-import TableSanksi from "@/components/Keberatan/TableSanksi";
-import { getSanksi } from "@/actions/sanksi";
-import { connect } from "react-redux";
-import Loader from "@/components/Common/Loader";
-
-class Keberatan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			sanksi: {},
-		};
-	}
-
-	componentDidMount = async () => {
-		const { token } = this.props;
-		const sanksi = await getSanksi(token, { keberatan: true });
-		this.setState({ sanksi });
-	};
-
-	render() {
-		const { sanksi } = this.state;
-		return (
-			<ContentWrapper>
-				<div className="content-heading">Keberatan</div>
-				<Row>
-					<Col lg="4">
-						<CaseProgress />
-					</Col>
-					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/keberatan/detail" linkName="Detail" /> : <Loader />}</Col>
-				</Row>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(Keberatan);
+import React, { Component } from "react";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col } from "reactstrap";
+import CaseProgress from "@/components/Main/CaseProgress";
+import TableSanksi from "@/components/Keberatan/TableSanksi";
+import { getSanksi } from "@/actions/sanksi";
+import { connect } from "react-redux";
+import Loader from "@/components/Common/Loader";
+
+class Keberatan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			sanksi: {},
+		};
+	}
+
+	componentDidMount = async () => {
+		const { token } = this.props;
+		const sanksi = await getSanksi(token, { keberatan: true });
+		this.setState({ sanksi });
+	};
+
+	render() {
+		const { sanksi } = this.state;
+		return (
+			<ContentWrapper>
+				<div className="content-heading">Keberatan</div>
+				<Row>
+					<Col lg="4">
+						<CaseProgress />
+					</Col>
+					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/keberatan/detail" linkName="Detail" /> : <Loader />}</Col>
+				</Row>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(Keberatan);

+ 3 - 1
pages/app/pelaporan/new.js

@@ -50,7 +50,9 @@ class PelaporanNew extends Component {
 								<CardBody>
 									<Row>
 										<Col lg={12}>
-											<p className="lead bb">Informasi Laporan<span className="font-20">(wajib diisi)</span></p>
+											<p className="lead bb">
+												Informasi Laporan<span className="font-20">(wajib diisi)</span>
+											</p>
 											<InputData query={this.props.query} />
 										</Col>
 									</Row>

+ 20 - 4
pages/app/pelaporan/search.js

@@ -16,6 +16,7 @@ class Search extends Component {
 		selectedOptionMulti: [],
 		data: [],
 		pembina: [],
+		loading: false,
 	};
 
 	componentDidMount = async () => {
@@ -61,14 +62,18 @@ class Search extends Component {
 	};
 
 	handleApplyClick = () => {
+		this.setState({ loading: true });
 		this.fetchData();
+		this.setState({ loading: false });
 		if (this.state.data && this.state.data.length) {
 			this.renderTableData();
 		}
 	};
 
 	handleSearchClick = () => {
+		this.setState({ loading: true });
 		this.fetchData();
+		this.setState({ loading: false });
 		if (this.state.data.length) {
 			this.renderTableData();
 		}
@@ -76,6 +81,7 @@ class Search extends Component {
 
 	renderTableData() {
 		return (
+			!this.state.loading &&
 			this.state.data &&
 			this.state.data.map((pt, index) => {
 				return (
@@ -127,14 +133,24 @@ class Search extends Component {
 					</div>
 				</div>
 				<Row>
-					<Col lg={this.props.user?.role.id === 2021 ? 12 : 9}>
+					<Col lg={this.props.user.role.id === 2021 ? 12 : 9}>
 						<div className="form-group mb-4">
-							<input className="form-control mb-2" type="text" id="searchInput" placeholder="Pencarian Nama Perguruan Tinggi" />
+							<input
+								className="form-control mb-2"
+								type="text"
+								id="searchInput"
+								onKeyPress={(e) => {
+									if (e.key === "Enter") {
+										this.handleApplyClick();
+									}
+								}}
+								placeholder="Pencarian Nama Perguruan Tinggi"
+							/>
 							<div className="d-flex">
 								<button className="btn btn-secondary" type="button" onClick={(e) => this.handleSearchClick()}>
 									Search
 								</button>
-								<div className="ml-auto">{this.props.user?.role.id === 2021 && `Pembina: ${this.props.user.lembaga.nama}`}</div>
+								<div className="ml-auto">{this.props.user.role.id === 2021 && `Pembina: ${this.props.user.lembaga.nama}`}</div>
 							</div>
 						</div>
 						<div className="card card-default">
@@ -153,7 +169,7 @@ class Search extends Component {
 							</div>
 						</div>
 					</Col>
-					{this.props.user?.role.id === 2021 ? (
+					{this.props.user.role.id === 2021 ? (
 						""
 					) : (
 						<Col lg="3">

+ 74 - 74
pages/app/pemantauan-perbaikan/detail.js

@@ -1,74 +1,74 @@
-import React, { Component } from "react";
-import DetailSanksi from "@/components/Main/DetailSanksi";
-import Link from "next/link";
-import Header from "@/components/Main/Header";
-import DetailPT from "@/components/Main/DetailPT";
-import Riwayat from "@/components/DocPerbaikan/Riwayat";
-import { getOneSanksi } from "@/actions/sanksi";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Card, CardBody } from "reactstrap";
-import { getPT } from "@/actions/PT";
-import Loader from "@/components/Common/Loader";
-import { connect } from "react-redux";
-
-class PemantauanDokumen extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			sanksi: {},
-			pt: null,
-		};
-	}
-
-	static getInitialProps = async ({ query }) => ({ query });
-
-	componentDidMount = async () => {
-		const { query, token } = this.props;
-		const sanksi = await getOneSanksi(token, query.id);
-		const pt = sanksi.data.laporan.pt;
-		this.setState({ sanksi, pt });
-	};
-
-	render() {
-		const { sanksi, pt } = this.state;
-		return (
-			<ContentWrapper unwrap>
-				{/* <Header /> */}
-				<div className="p-3">
-					<div className="content-heading">
-						<div>Detail Pemantauan Perbaikan</div>
-						<div className="ml-auto">
-							<Link href="/app/pemantauan-perbaikan">
-								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
-							</Link>
-						</div>
-					</div>
-					<Row>
-						{sanksi.data ? (
-							<Col xl="9">
-								<Card className="card-default">
-									<CardBody>
-										<Row>
-											<Col lg={12}>
-												<DetailSanksi data={sanksi.data} />
-											</Col>
-										</Row>
-									</CardBody>
-								</Card>
-							</Col>
-						) : (
-							<Loader />
-						)}
-						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
-					</Row>
-					<Row>
-						<Col>{sanksi.data && <Riwayat data={sanksi.data.perbaikan} />}</Col>
-					</Row>
-				</div>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(PemantauanDokumen);
+import React, { Component } from "react";
+import DetailSanksi from "@/components/Main/DetailSanksi";
+import Link from "next/link";
+import Header from "@/components/Main/Header";
+import DetailPT from "@/components/Main/DetailPT";
+import Riwayat from "@/components/DocPerbaikan/Riwayat";
+import { getOneSanksi } from "@/actions/sanksi";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col, Card, CardBody } from "reactstrap";
+import { getPT } from "@/actions/PT";
+import Loader from "@/components/Common/Loader";
+import { connect } from "react-redux";
+
+class PemantauanDokumen extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			sanksi: {},
+			pt: null,
+		};
+	}
+
+	static getInitialProps = async ({ query }) => ({ query });
+
+	componentDidMount = async () => {
+		const { query, token } = this.props;
+		const sanksi = await getOneSanksi(token, query.id);
+		const pt = sanksi.data.laporan.pt;
+		this.setState({ sanksi, pt });
+	};
+
+	render() {
+		const { sanksi, pt } = this.state;
+		return (
+			<ContentWrapper unwrap>
+				{/* <Header /> */}
+				<div className="p-3">
+					<div className="content-heading">
+						<div>Detail Pemantauan Perbaikan</div>
+						<div className="ml-auto">
+							<Link href="/app/pemantauan-perbaikan">
+								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
+							</Link>
+						</div>
+					</div>
+					<Row>
+						{sanksi.data ? (
+							<Col xl="9">
+								<Card className="card-default">
+									<CardBody>
+										<Row>
+											<Col lg={12}>
+												<DetailSanksi data={sanksi.data} />
+											</Col>
+										</Row>
+									</CardBody>
+								</Card>
+							</Col>
+						) : (
+							<Loader />
+						)}
+						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
+					</Row>
+					<Row>
+						<Col>{sanksi.data && <Riwayat data={sanksi.data.perbaikan} />}</Col>
+					</Row>
+				</div>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(PemantauanDokumen);

+ 41 - 41
pages/app/pemantauan-perbaikan/index.js

@@ -1,41 +1,41 @@
-import React, { Component } from "react";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col } from "reactstrap";
-import CaseProgress from "@/components/Main/CaseProgress";
-import TableSanksi from "@/components/Main/TableSanksi";
-import { getSanksi } from "@/actions/sanksi";
-import { connect } from "react-redux";
-import Loader from "@/components/Common/Loader";
-
-class PemantauanPerbaikan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			sanksi: {},
-		};
-	}
-
-	componentDidMount = async () => {
-		const { token } = this.props;
-		const sanksi = await getSanksi(token, { perbaikan: true });
-		this.setState({ sanksi });
-	};
-
-	render() {
-		const { sanksi } = this.state;
-		return (
-			<ContentWrapper>
-				<div className="content-heading">Pemantauan Perbaikan</div>
-				<Row>
-					<Col lg="4">
-						<CaseProgress />
-					</Col>
-					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/pemantauan-perbaikan/detail" linkName="Detail" /> : <Loader />}</Col>
-				</Row>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(PemantauanPerbaikan);
+import React, { Component } from "react";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col } from "reactstrap";
+import CaseProgress from "@/components/Main/CaseProgress";
+import TableSanksi from "@/components/Main/TableSanksi";
+import { getSanksi } from "@/actions/sanksi";
+import { connect } from "react-redux";
+import Loader from "@/components/Common/Loader";
+
+class PemantauanPerbaikan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			sanksi: {},
+		};
+	}
+
+	componentDidMount = async () => {
+		const { token } = this.props;
+		const sanksi = await getSanksi(token, { perbaikan: true });
+		this.setState({ sanksi });
+	};
+
+	render() {
+		const { sanksi } = this.state;
+		return (
+			<ContentWrapper>
+				<div className="content-heading">Pemantauan Perbaikan</div>
+				<Row>
+					<Col lg="4">
+						<CaseProgress />
+					</Col>
+					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/pemantauan-perbaikan/detail" linkName="Detail" /> : <Loader />}</Col>
+				</Row>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(PemantauanPerbaikan);

+ 186 - 176
pages/app/pemantauan/index.js

@@ -1,176 +1,186 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Button, Table } from "reactstrap";
-import { getPT, getPembina } from "@/actions/PT";
-import "rc-slider/assets/index.css";
-import Select from "react-select";
-import "react-datetime/css/react-datetime.css";
-import { connect } from "react-redux";
-import Loader from "@/components/Common/Loader";
-
-const selectInstanceId = 1;
-class Search extends Component {
-	state = {
-		pembina: [],
-		selectedOptionMulti: [],
-		data: [],
-	};
-
-	componentDidMount = async () => {
-		const { user, token } = this.props;
-		if (user.role.id === 2020) {
-			const dataPembina = await getPembina(token);
-			this.setState({ pembina: dataPembina.data });
-		}
-	};
-
-	optionsPembina = (pembina) => {
-		return pembina.map((e) => ({ value: e.id, label: e.nama, className: "State-ACT" }));
-	};
-
-	handleChangeSelectMulti = (selectedOptionMulti) => {
-		this.setState({ selectedOptionMulti });
-	};
-
-	renderInputGroup = (props) => {
-		return (
-			<div className="input-group date">
-				<input className="form-control" {...props} />
-				<span className="input-group-append input-group-addon">
-					<span className="input-group-text fas fa-calendar-alt"></span>
-				</span>
-			</div>
-		);
-	};
-
-	handleClick = (e, PT_ID) => {
-		e.preventDefault();
-		Router.push({
-			pathname: "/app/pemantauan/timeline",
-			query: { ptId: PT_ID },
-		});
-	};
-
-	fetchData = async () => {
-		const pembina = this.props.user.role.id === 2021 ? null : this.state.selectedOptionMulti.map((e) => e.value).join(",");
-		const searchValue = document.getElementById("searchInput").value;
-		const jsonData = await getPT(this.props.token, { search: searchValue, pembina });
-		this.setState({ data: jsonData.data ? jsonData.data : [] });
-	};
-
-	handleApplyClick = () => {
-		this.fetchData();
-		if (this.state.data && this.state.data.length) {
-			this.renderTableData();
-		}
-	};
-
-	handleSearchClick = () => {
-		this.fetchData();
-		if (this.state.data && this.state.data.length) {
-			this.renderTableData();
-		}
-	};
-
-	renderTableData() {
-		return (
-			this.state.data &&
-			this.state.data.map((pt, index) => {
-				return (
-					<tr key={index}>
-						<td>
-							<label>{index + 1}</label>
-						</td>
-						<td>
-							<div className="media align-items-center">
-								<a className="mr-3" href="">
-									<img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" />
-								</a>
-								<div className="media-body d-flex">
-									<div>
-										<h4 className="m-0">{pt.nama}</h4>
-										<small className="text-muted">
-											{pt.sk_pendirian} - {pt.website} - {pt.email}
-										</small>
-										<p>{pt.alamat.jalan}</p>
-										{this.props.user?.role.id === 2021 ? "" : <p>Pembina: {pt.pembina.nama}</p>}
-									</div>
-									<div className="ml-auto">
-										<Button color="info" size="sm" onClick={(e) => this.handleClick(e, pt.id)}>
-											View
-										</Button>
-									</div>
-								</div>
-							</div>
-						</td>
-					</tr>
-				);
-			})
-		);
-	}
-
-	render() {
-		const { selectedOptionMulti, pembina } = this.state;
-		return (
-			<ContentWrapper>
-				<div className="content-heading">
-					<div>
-						Search
-						<small>Search and filter results</small>
-					</div>
-				</div>
-				<Row>
-					<Col lg={this.props.user?.role.id === 2021 ? 12 : 9}>
-						<div className="form-group mb-4">
-							<input className="form-control mb-2" type="text" id="searchInput" placeholder="Pencarian Nama Perguruan Tinggi" />
-							<div className="d-flex">
-								<button className="btn btn-secondary" type="button" onClick={(e) => this.handleSearchClick()}>
-									Search
-								</button>
-								<div className="ml-auto">{this.props.user?.role.id === 2021 && `Pembina: ${this.props.user.lembaga.nama}`}</div>
-							</div>
-						</div>
-						{/* START card */}
-						<div className="card card-default">
-							<div className="card-header">Search Results</div>
-							{/* START table-responsive */}
-							<Table striped bordered hover>
-								<thead>
-									<tr>
-										<th>No. </th>
-										<th>Description</th>
-									</tr>
-								</thead>
-								<tbody>{this.renderTableData()}</tbody>
-							</Table>
-							{/* END table-responsive */}
-							<div className="card-footer">
-								<div className="d-flex"></div>
-							</div>
-						</div>
-						{/* END card */}
-					</Col>
-					{this.props.user?.role.id === 2021 ? (
-						""
-					) : (
-						<Col lg="3">
-							<h3 className="m-0 pb-3">Filters</h3>
-							<div className="form-group mb-4">
-								<label className="col-form-label mb-2">by Pembina</label>
-								<br />
-								<Select instanceId={selectInstanceId + 1} isMulti value={selectedOptionMulti} onChange={this.handleChangeSelectMulti} options={pembina ? this.optionsPembina(pembina) : []} required />
-							</div>
-							<Button color="secondary" size="lg" onClick={(e) => this.handleApplyClick()}>
-								Apply
-							</Button>
-						</Col>
-					)}
-				</Row>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(Search);
+import React, { Component } from "react";
+import Router from "next/router";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col, Button, Table } from "reactstrap";
+import { getPT, getPembina } from "@/actions/PT";
+import "rc-slider/assets/index.css";
+import Select from "react-select";
+import "react-datetime/css/react-datetime.css";
+import { connect } from "react-redux";
+import Loader from "@/components/Common/Loader";
+
+const selectInstanceId = 1;
+class Search extends Component {
+	state = {
+		pembina: [],
+		selectedOptionMulti: [],
+		data: [],
+	};
+
+	componentDidMount = async () => {
+		const { user, token } = this.props;
+		if (user.role.id === 2020) {
+			const dataPembina = await getPembina(token);
+			this.setState({ pembina: dataPembina.data });
+		}
+	};
+
+	optionsPembina = (pembina) => {
+		return pembina.map((e) => ({ value: e.id, label: e.nama, className: "State-ACT" }));
+	};
+
+	handleChangeSelectMulti = (selectedOptionMulti) => {
+		this.setState({ selectedOptionMulti });
+	};
+
+	renderInputGroup = (props) => {
+		return (
+			<div className="input-group date">
+				<input className="form-control" {...props} />
+				<span className="input-group-append input-group-addon">
+					<span className="input-group-text fas fa-calendar-alt"></span>
+				</span>
+			</div>
+		);
+	};
+
+	handleClick = (e, PT_ID) => {
+		e.preventDefault();
+		Router.push({
+			pathname: "/app/pemantauan/timeline",
+			query: { ptId: PT_ID },
+		});
+	};
+
+	fetchData = async () => {
+		const pembina = this.props.user.role.id === 2021 ? null : this.state.selectedOptionMulti.map((e) => e.value).join(",");
+		const searchValue = document.getElementById("searchInput").value;
+		const jsonData = await getPT(this.props.token, { search: searchValue, pembina });
+		this.setState({ data: jsonData.data ? jsonData.data : [] });
+	};
+
+	handleApplyClick = () => {
+		this.fetchData();
+		if (this.state.data && this.state.data.length) {
+			this.renderTableData();
+		}
+	};
+
+	handleSearchClick = () => {
+		this.fetchData();
+		if (this.state.data && this.state.data.length) {
+			this.renderTableData();
+		}
+	};
+
+	renderTableData() {
+		return (
+			this.state.data &&
+			this.state.data.map((pt, index) => {
+				return (
+					<tr key={index}>
+						<td>
+							<label>{index + 1}</label>
+						</td>
+						<td>
+							<div className="media align-items-center">
+								<a className="mr-3" href="">
+									<img className="img-fluid rounded thumb64" src="/static/img/dummy-search.png" alt="Dummy" />
+								</a>
+								<div className="media-body d-flex">
+									<div>
+										<h4 className="m-0">{pt.nama}</h4>
+										<small className="text-muted">
+											{pt.sk_pendirian} - {pt.website} - {pt.email}
+										</small>
+										<p>{pt.alamat.jalan}</p>
+										{this.props.user?.role.id === 2021 ? "" : <p>Pembina: {pt.pembina.nama}</p>}
+									</div>
+									<div className="ml-auto">
+										<Button color="info" size="sm" onClick={(e) => this.handleClick(e, pt.id)}>
+											View
+										</Button>
+									</div>
+								</div>
+							</div>
+						</td>
+					</tr>
+				);
+			})
+		);
+	}
+
+	render() {
+		const { selectedOptionMulti, pembina } = this.state;
+		return (
+			<ContentWrapper>
+				<div className="content-heading">
+					<div>
+						Search
+						<small>Search and filter results</small>
+					</div>
+				</div>
+				<Row>
+					<Col lg={this.props.user?.role.id === 2021 ? 12 : 9}>
+						<div className="form-group mb-4">
+							<input
+								className="form-control mb-2"
+								type="text"
+								id="searchInput"
+								placeholder="Pencarian Nama Perguruan Tinggi"
+								onKeyPress={(e) => {
+									if (e.key === "Enter") {
+										this.handleApplyClick();
+									}
+								}}
+							/>
+							<div className="d-flex">
+								<button className="btn btn-secondary" type="button" onClick={(e) => this.handleSearchClick()}>
+									Search
+								</button>
+								<div className="ml-auto">{this.props.user?.role.id === 2021 && `Pembina: ${this.props.user.lembaga.nama}`}</div>
+							</div>
+						</div>
+						{/* START card */}
+						<div className="card card-default">
+							<div className="card-header">Search Results</div>
+							{/* START table-responsive */}
+							<Table striped bordered hover>
+								<thead>
+									<tr>
+										<th>No. </th>
+										<th>Description</th>
+									</tr>
+								</thead>
+								<tbody>{this.renderTableData()}</tbody>
+							</Table>
+							{/* END table-responsive */}
+							<div className="card-footer">
+								<div className="d-flex"></div>
+							</div>
+						</div>
+						{/* END card */}
+					</Col>
+					{this.props.user?.role.id === 2021 ? (
+						""
+					) : (
+						<Col lg="3">
+							<h3 className="m-0 pb-3">Filters</h3>
+							<div className="form-group mb-4">
+								<label className="col-form-label mb-2">by Pembina</label>
+								<br />
+								<Select instanceId={selectInstanceId + 1} isMulti value={selectedOptionMulti} onChange={this.handleChangeSelectMulti} options={pembina ? this.optionsPembina(pembina) : []} required />
+							</div>
+							<Button color="secondary" size="lg" onClick={(e) => this.handleApplyClick()}>
+								Apply
+							</Button>
+						</Col>
+					)}
+				</Row>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(Search);

+ 152 - 143
pages/app/pemantauan/timeline.js

@@ -1,143 +1,152 @@
-import React, { Component } from "react";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { getLog } from "@/actions/log";
-import { Row, Col, Card, CardBody, Button } from "reactstrap";
-import Timeline from "@/components/Main/Timeline";
-import { getOnePT } from "@/actions/PT";
-import { connect } from "react-redux";
-import { getOneLaporan, getPelaporan } from "../../../actions/pelaporan";
-import DetailLaporan from "@/components/Main/DetailLaporan";
-import Link from "next/link";
-import Loader from "@/components/Common/Loader";
-import DetailPT from "@/components/Main/DetailPT";
-import moment from "moment";
-import Datatable from "@/components/Tables/Datatable";
-
-class Pemantauan extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			log: {},
-			detailLaporanPt: {},
-			pelaporan: {},
-			pt: {},
-		};
-	}
-
-	static async getInitialProps({ query }) {
-		return { query };
-	}
-
-	componentDidMount = async () => {
-		const { query, token } = this.props;
-		const ptId = query.ptId;
-		const pelaporan = await getPelaporan(token, { pt_id: ptId });
-		const pt = await getOnePT(token, ptId);
-		this.setState({ pelaporan, pt });
-	};
-
-	handleLihatPemantaun = async (e, id_laporan) => {
-		const { token } = this.props;
-		const log = await getLog(token, id_laporan);
-		const detailLaporanPt = await getOneLaporan(token, id_laporan);
-		this.setState({ detailLaporanPt, log });
-	};
-
-	render() {
-		const { detailLaporanPt, log, pt, pelaporan } = this.state;
-		return (
-			<ContentWrapper unwrap>
-				<div className="p-3">
-					<div className="content-heading">
-						<div>Pemantauan {pt?.data && pt.data.nama}</div>
-						<div className="ml-auto">
-							<Link href="/app/pemantauan">
-								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
-							</Link>
-						</div>
-					</div>
-					<Row>
-						<Col xl={9}>
-							<div className="card b">
-								<div className="card-body">
-									{pelaporan.data?.length ? (
-										<Datatable options={{ responsive: false }}>
-											<table className="table w-100">
-												<thead>
-													<tr>
-														<th>Tanggal</th>
-														<th>No.Laporan</th>
-														<th>Deskripsi Laporan</th>
-														<th>Pelapor</th>
-														<th>Aksi</th>
-													</tr>
-												</thead>
-												<tbody>
-													{pelaporan.data.map((data) => {
-														return (
-															<tr key={data._id}>
-																<td>{moment(data.createdAt).format("DD/MM/YYYY")}</td>
-																<td>{data.no_laporan}</td>
-
-																<td className="text-nowrap">
-																	<div className="media align-items-center">
-																		<div className="media-body d-flex">
-																			<div>
-																				<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
-																			</div>
-																		</div>
-																	</div>
-																</td>
-
-																<td>{data.user.isPrivate ? "" : data.user.nama}</td>
-
-																<td>
-																	<div>
-																		<Button onClick={(e) => this.handleLihatPemantaun(e, data._id)}>Detail</Button>
-																	</div>
-																</td>
-															</tr>
-														);
-													})}
-												</tbody>
-											</table>
-										</Datatable>
-									) : pelaporan.data ? (
-										"Pelaporan tidak ada"
-									) : (
-										<Loader />
-									)}
-								</div>
-							</div>
-						</Col>
-						<Col xl={3}>{pt?.data ? <DetailPT data={pt.data} /> : <Loader />}</Col>
-					</Row>
-					<Row>
-						<Col xl="12">
-							{detailLaporanPt.data ? (
-								<Card className="card-default">
-									<CardBody>
-										<Row>
-											<Col lg={12}>{<DetailLaporan data={detailLaporanPt.data} />}</Col>
-										</Row>
-									</CardBody>
-								</Card>
-							) : (
-								<div></div>
-							)}
-						</Col>
-					</Row>
-					<Row>
-						{log.data && (
-							<Col xl={"12"}>
-								<Timeline data={log.data} />
-							</Col>
-						)}
-					</Row>
-				</div>
-			</ContentWrapper>
-		);
-	}
-}
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(Pemantauan);
+import React, { Component } from "react";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { getLog } from "@/actions/log";
+import { Row, Col, Card, CardBody, Button } from "reactstrap";
+import Timeline from "@/components/Main/Timeline";
+import { getOnePT } from "@/actions/PT";
+import { connect } from "react-redux";
+import { getOneLaporan, getPelaporan } from "../../../actions/pelaporan";
+import DetailLaporan from "@/components/Main/DetailLaporan";
+import Link from "next/link";
+import Loader from "@/components/Common/Loader";
+import DetailPT from "@/components/Main/DetailPT";
+import moment from "moment";
+import Datatable from "@/components/Tables/Datatable";
+<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
+
+
+class Pemantauan extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			log: {},
+			detailLaporanPt: {},
+			pelaporan: {},
+			pt: {},
+		};
+	}
+
+
+	static async getInitialProps({ query }) {
+		return { query };
+	}
+
+	componentDidMount = async () => {
+		const { query, token } = this.props;
+		const ptId = query.ptId
+		const pelaporan = await getPelaporan(token, { pt_id: ptId });
+		const pt = await getOnePT(token, ptId);
+		this.setState({ pelaporan, pt });
+
+	};
+
+	handleLihatPemantaun = async (e, id_laporan) => {
+		const { token } = this.props;
+		const log = await getLog(token, id_laporan)
+		const detailLaporanPt = await getOneLaporan(token, id_laporan)
+		this.setState({ detailLaporanPt, log });
+	}
+
+	render() {
+		const { detailLaporanPt, log, pt, pelaporan } = this.state;
+		console.log(pelaporan)
+		return (
+			<ContentWrapper unwrap>
+				<div className="p-3">
+					<div className="content-heading">
+						<div>Pemantauan {pt?.data && pt.data.nama}</div>
+						<div className="ml-auto">
+							<Link href="/app/pemantauan">
+								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
+							</Link>
+						</div>
+					</div>
+					<Row>
+						<Col xl={9}>
+							<div className="card b">
+								<div className="card-body">
+									{pelaporan.data?.length ? (
+										<Datatable options={{ responsive: false }}>
+											<table className="table w-100">
+												<thead>
+													<tr>
+														<th>Tanggal</th>
+														<th>No.Laporan</th>
+														<th>Deskripsi Laporan</th>
+														<th>Pelapor</th>
+														<th>Aksi</th>
+													</tr>
+												</thead>
+												<tbody>
+													{pelaporan.data.map((data) => {
+														return (
+															<tr key={data._id}>
+																<td>{moment(data.createdAt).format("DD/MM/YYYY")}</td>
+																<td>{data.no_laporan}</td>
+
+																<td className="text-nowrap">
+																	<div className="media align-items-center">
+																		<div className="media-body d-flex">
+																			<div>
+																				<p>{data.keterangan.length > 35 ? data.keterangan.substring(0, 35) + "..." : data.keterangan}</p>
+																			</div>
+																		</div>
+																	</div>
+																</td>
+
+																<td>{data.user.isPrivate ? "" : data.user.nama}</td>
+
+																<td>
+																	<div>
+
+																		<Button color="primary" onClick={(e) => this.handleLihatPemantaun(e, data._id)}>
+																			Detail
+																		</Button>
+
+																	</div>
+																</td>
+															</tr>
+														);
+													})}
+												</tbody>
+											</table>
+										</Datatable>
+									) : pelaporan.data ? "Pelaporan tidak ada" : <Loader />}
+								</div >
+							</div >
+						</Col>
+						<Col xl={3}>{pt?.data ? <DetailPT data={pt.data} /> : <Loader />}</Col>
+					</Row>
+					<Row>
+						<Col xl="12">
+							{detailLaporanPt.data ? (
+								<Card className="card-default">
+									<CardBody>
+										<Row>
+											<Col lg={12}>
+												{<DetailLaporan data={detailLaporanPt.data} />}
+											</Col>
+										</Row>
+									</CardBody>
+								</Card>
+							) : (
+								<div></div>
+							)}
+						</Col>
+					</Row>
+					<Row>
+						{log.data && (
+							<Col xl={"12"}>
+								<Timeline data={log.data} />
+							</Col>
+						)}
+					</Row>
+				</div>
+
+
+			</ContentWrapper>
+		);
+	}
+}
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(Pemantauan);

+ 10 - 7
pages/app/pemeriksaan/index.js

@@ -1,12 +1,14 @@
 import React, { Component } from "react";
 import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Button } from "reactstrap";
+import { Row, Col } from "reactstrap";
 import { getPelaporan } from "@/actions/pelaporan";
 import CaseProgress from "@/components/Main/CaseProgress";
 import TableLaporan from "@/components/Pemeriksaan/TableLaporan";
 import { connect } from "react-redux";
 import Loader from "@/components/Common/Loader";
 import Link from "next/link";
+import Button from "reactstrap/lib/Button";
+
 class Pemeriksaan extends Component {
 	constructor(props) {
 		super(props);
@@ -29,17 +31,18 @@ class Pemeriksaan extends Component {
 					<div>Evaluasi</div>
 					<div className="ml-auto">
 						<Link href="/app/penjadwalan">
-							<Button className="btn-header" color="info">
-								<h4>&lt; Penjadwalan</h4>
-							</Button>
-						</Link>
-						<Link href="/app/sanksi">
 							<span className="margin-l-5">
 								<Button className="btn-header" color="info">
-									<h4>Sanksi &gt;</h4>
+									<h4>&lt; Penjadwalan</h4>
 								</Button>
 							</span>
 						</Link>
+
+						<Link href="/app/sanksi">
+							<Button className="btn-header" color="info">
+								<h4>Sanksi &gt;</h4>
+							</Button>
+						</Link>
 					</div>
 				</div>
 				<Row>

+ 325 - 325
pages/app/pencabutan-sanksi/detail.js

@@ -1,325 +1,325 @@
-import React, { Component } from "react";
-import Router from "next/router";
-import Link from "next/link";
-import Select from "react-select";
-import DetailSanksi from "@/components/Main/DetailSanksi";
-import Header from "@/components/Main/Header";
-import DetailPT from "@/components/Main/DetailPT";
-import PermohonanPT from "@/components/Main/PermohonanPT";
-import Riwayat from "@/components/PencabutanSanksi/Riwayat";
-import { getOneSanksi } from "@/actions/sanksi";
-import { addJawabanCabutSanksi } from "@/actions/cabutSanksi";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col, Card, CardBody, FormGroup, Button, Input, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
-import { getPT } from "@/actions/PT";
-import Loader from "@/components/Common/Loader";
-import { toast } from "react-toastify";
-import { connect } from "react-redux";
-import { Formik, Form, Field, ErrorMessage } from "formik";
-import * as Yup from "yup";
-
-const checkIfFilesAreTooBig = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (file.size > 15 * 1024 * 1024) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-
-const checkIfFilesAreCorrectType = (files) => {
-	let valid = true;
-	if (files) {
-		files.map((file) => {
-			if (!["image/jpeg", "image/png"].includes(file.type)) {
-				valid = false;
-			}
-		});
-	}
-	return valid;
-};
-const jawabanCabutSanksiSchema = Yup.object().shape({
-	status: Yup.string().required("Harap Diisi"),
-	keterangan: Yup.string().max(200).notRequired(),
-	dokumen: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
-});
-
-let Dropzone = null;
-class DropzoneWrapper extends Component {
-	state = {
-		isClient: false,
-	};
-	componentDidMount = () => {
-		Dropzone = require("react-dropzone").default;
-		this.setState({ isClient: true });
-	};
-	render() {
-		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
-	}
-}
-
-const selectInstanceId = 1;
-
-class JawabanPencabutanSanksi extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			selectedOption: null,
-			files: [],
-			keterangan: "",
-			sanksi: {},
-			pt: null,
-			data: {},
-		};
-	}
-
-	static getInitialProps = async ({ query }) => ({ query });
-
-	componentDidMount = async () => {
-		const { query, token } = this.props;
-		const sanksi = await getOneSanksi(token, query.id);
-		const pt = sanksi.data.laporan.pt;
-		this.setState({ sanksi, pt });
-	};
-
-	toggleModal = () => {
-		this.setState({
-			modal: !this.state.modal,
-		});
-	};
-
-	handleChangeSelect = (selectedOption) => {
-		this.setState({ selectedOption });
-	};
-
-	onDrop = (files) => {
-		this.setState({
-			files: files.map((file) =>
-				Object.assign(file, {
-					preview: URL.createObjectURL(file),
-				})
-			),
-			stat: "Added " + files.length + " file(s)",
-		});
-	};
-
-	uploadFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
-		});
-	};
-
-	clearFiles = (e) => {
-		e.preventDefault();
-		e.stopPropagation();
-		this.setState({
-			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
-		});
-		this.setState({
-			files: [],
-		});
-	};
-
-	setKeterangan = (e) => {
-		this.setState({ keterangan: e.target.value });
-	};
-
-	handleSimpan = async () => {
-		const { data } = this.state;
-		console.log(data);
-		const { token, query } = this.props;
-		const formdata = new FormData();
-		formdata.append("status", data.status);
-		formdata.append("keterangan", data.keterangan);
-		this.state.files.forEach((e) => {
-			formdata.append("dokumen", e);
-		});
-		const toastid = toast.loading("Please wait...");
-		const added = await addJawabanCabutSanksi(token, query.id, formdata);
-		if (!added) {
-			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
-		} else {
-			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
-			Router.push({
-				pathname: "/app/pencabutan-sanksi",
-			});
-		}
-	};
-
-	render() {
-		const { files, selectedOption, sanksi, pt } = this.state;
-
-		const thumbs = files.map((file, index) => (
-			<Col md={3} key={index}>
-				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
-			</Col>
-		));
-
-		return (
-			<ContentWrapper unwrap>
-				{/* <Header /> */}
-				<div className="p-3">
-					<div className="content-heading">
-						<div>Jawaban Permohonan Pencabutan Sanksi</div>
-						<div className="ml-auto">
-							<Link href="/app/pencabutan-sanksi">
-								<button className="btn btn-sm btn-secondary text-sm">&lt; back</button>
-							</Link>
-						</div>
-					</div>
-					<Row>
-						{sanksi.data ? (
-							<Col xl="9">
-								<Card className="card-default">
-									<CardBody>
-										<Row>
-											<Col lg={12}>
-												<DetailSanksi data={sanksi.data} />
-												<PermohonanPT data={sanksi.data.pengajuan.cabut_sanksi} />
-												<p className="lead bb">Jawaban</p>
-												<Formik
-													initialValues={{
-														status: "",
-														keterangan: "",
-														dokumen: [],
-													}}
-													validationSchema={jawabanCabutSanksiSchema}
-													onSubmit={async (data) => {
-														this.setState({ data });
-														if (sanksi.data.jawaban?.cabut_sanksi) this.toggleModal();
-														else await this.handleSimpan();
-													}}
-												>
-													{() => (
-														<Form className="form-horizontal">
-															<FormGroup>
-																<label className="row-form-label">Status:</label>
-																<div className="row-md-10">
-																	<Field name="status">
-																		{({ field, form }) => (
-																			<Select
-																				instanceId={selectInstanceId + 1}
-																				value={this.state.selectedOption}
-																				onChange={(e) => {
-																					this.handleChangeSelect(e);
-																					form.setFieldValue(field.name, e.value);
-																				}}
-																				options={[
-																					{ value: "Diterima", label: "Diterima", className: "State-ACT" },
-																					{ value: "Rekomendasi Perbaikan", label: "Rekomendasi Perbaikan", className: "State-ACT" },
-																				]}
-																			/>
-																		)}
-																	</Field>
-																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
-																	{/* <span className="form-text">Pilih Jenis Pelanggaran</span> */}
-																</div>
-															</FormGroup>
-															{selectedOption && selectedOption.value === "Rekomendasi Perbaikan" ? (
-																<FormGroup>
-																	<label className="row-form-label">Keterangan:</label>
-																	<div className="row-md-10">
-																		<Field name="keterangan">{({ field }) => <Input type="textarea" {...field} />}</Field>
-																		<ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
-																		{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
-																	</div>
-																</FormGroup>
-															) : (
-																""
-															)}
-															<FormGroup>
-																<label className="row-form-label">Upload Dokumen:</label>
-																<div className="row-md-10">
-																	<Field name="dokumen">
-																		{({ field, form }) => (
-																			<DropzoneWrapper
-																				className=""
-																				onDrop={(e) => {
-																					this.onDrop(e);
-																					form.setFieldValue(field.name, e);
-																				}}
-																			>
-																				{({ getRootProps, getInputProps, isDragActive }) => {
-																					return (
-																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
-																							<input {...getInputProps()} />
-																							<div className="dropzone-previews flex">
-																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Drop files here to upload</div>}
-																							</div>
-																							<div className="d-flex align-items-center">
-																								<small className="ml-auto">
-																									<button
-																										type="button"
-																										className="btn btn-link"
-																										onClick={(e) => {
-																											this.clearFiles(e);
-																											form.setFieldValue(field.name, []);
-																										}}
-																									>
-																										Clear files
-																									</button>
-																								</small>
-																							</div>
-																						</div>
-																					);
-																				}}
-																			</DropzoneWrapper>
-																		)}
-																	</Field>
-																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
-																	<p className="mrgn-top-5">
-																		Ukuran setiap dokumen maksimal 15mb
-																	</p>
-																</div>
-															</FormGroup>
-															<FormGroup>
-																<div className="row-xl-10">
-																	<Button color="primary" type="submit">
-																		Simpan
-																	</Button>
-																</div>
-															</FormGroup>
-														</Form>
-													)}
-												</Formik>
-											</Col>
-										</Row>
-									</CardBody>
-								</Card>
-							</Col>
-						) : (
-							<Loader />
-						)}
-						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
-					</Row>
-					{sanksi.data && (
-						<Row>
-							<Col>
-								<Riwayat data={sanksi.data.jawaban?.cabut_sanksi} />
-							</Col>
-						</Row>
-					)}
-				</div>
-				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
-					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
-					<ModalFooter>
-						<Button color="primary" onClick={this.handleSimpan}>
-							Ya
-						</Button>{" "}
-						<Button color="secondary" onClick={this.toggleModal}>
-							Tidak
-						</Button>
-					</ModalFooter>
-				</Modal>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(JawabanPencabutanSanksi);
+import React, { Component } from "react";
+import Router from "next/router";
+import Link from "next/link";
+import Select from "react-select";
+import DetailSanksi from "@/components/Main/DetailSanksi";
+import Header from "@/components/Main/Header";
+import DetailPT from "@/components/Main/DetailPT";
+import PermohonanPT from "@/components/Main/PermohonanPT";
+import Riwayat from "@/components/PencabutanSanksi/Riwayat";
+import { getOneSanksi } from "@/actions/sanksi";
+import { addJawabanCabutSanksi } from "@/actions/cabutSanksi";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col, Card, CardBody, FormGroup, Button, Input, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
+import { getPT } from "@/actions/PT";
+import Loader from "@/components/Common/Loader";
+import { toast } from "react-toastify";
+import { connect } from "react-redux";
+import { Formik, Form, Field, ErrorMessage } from "formik";
+import * as Yup from "yup";
+
+const checkIfFilesAreTooBig = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (file.size > 15 * 1024 * 1024) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+
+const checkIfFilesAreCorrectType = (files) => {
+	let valid = true;
+	if (files) {
+		files.map((file) => {
+			if (!["image/jpeg", "image/png"].includes(file.type)) {
+				valid = false;
+			}
+		});
+	}
+	return valid;
+};
+const jawabanCabutSanksiSchema = Yup.object().shape({
+	status: Yup.string().required("Harap Diisi"),
+	keterangan: Yup.string().max(200).notRequired(),
+	dokumen: Yup.array().notRequired().test("filesize", "Maksimal ukuran dokumen 15mb", checkIfFilesAreTooBig),
+});
+
+let Dropzone = null;
+class DropzoneWrapper extends Component {
+	state = {
+		isClient: false,
+	};
+	componentDidMount = () => {
+		Dropzone = require("react-dropzone").default;
+		this.setState({ isClient: true });
+	};
+	render() {
+		return Dropzone ? <Dropzone {...this.props}>{this.props.children}</Dropzone> : null;
+	}
+}
+
+const selectInstanceId = 1;
+
+class JawabanPencabutanSanksi extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			selectedOption: null,
+			files: [],
+			keterangan: "",
+			sanksi: {},
+			pt: null,
+			data: {},
+		};
+	}
+
+	static getInitialProps = async ({ query }) => ({ query });
+
+	componentDidMount = async () => {
+		const { query, token } = this.props;
+		const sanksi = await getOneSanksi(token, query.id);
+		const pt = sanksi.data.laporan.pt;
+		this.setState({ sanksi, pt });
+	};
+
+	toggleModal = () => {
+		this.setState({
+			modal: !this.state.modal,
+		});
+	};
+
+	handleChangeSelect = (selectedOption) => {
+		this.setState({ selectedOption });
+	};
+
+	onDrop = (files) => {
+		this.setState({
+			files: files.map((file) =>
+				Object.assign(file, {
+					preview: URL.createObjectURL(file),
+				})
+			),
+			stat: "Added " + files.length + " file(s)",
+		});
+	};
+
+	uploadFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? "Dropzone ready to upload " + this.state.files.length + " file(s)" : "No files added.",
+		});
+	};
+
+	clearFiles = (e) => {
+		e.preventDefault();
+		e.stopPropagation();
+		this.setState({
+			stat: this.state.files.length ? this.state.files.length + " file(s) cleared." : "No files to clear.",
+		});
+		this.setState({
+			files: [],
+		});
+	};
+
+	setKeterangan = (e) => {
+		this.setState({ keterangan: e.target.value });
+	};
+
+	handleSimpan = async () => {
+		const { data } = this.state;
+		console.log(data);
+		const { token, query } = this.props;
+		const formdata = new FormData();
+		formdata.append("status", data.status);
+		formdata.append("keterangan", data.keterangan);
+		this.state.files.forEach((e) => {
+			formdata.append("dokumen", e);
+		});
+		const toastid = toast.loading("Please wait...");
+		const added = await addJawabanCabutSanksi(token, query.id, formdata);
+		if (!added) {
+			toast.update(toastid, { render: "All is not good", type: "error", isLoading: false, autoClose: true, closeButton: true });
+		} else {
+			toast.update(toastid, { render: "All is good", type: "success", isLoading: false, autoClose: true, closeButton: true });
+			Router.push({
+				pathname: "/app/pencabutan-sanksi",
+			});
+		}
+	};
+
+	render() {
+		const { files, selectedOption, sanksi, pt } = this.state;
+
+		const thumbs = files.map((file, index) => (
+			<Col md={3} key={index}>
+				<img className="img-fluid mb-2" src={file.preview} alt="Item" />
+			</Col>
+		));
+
+		return (
+			<ContentWrapper unwrap>
+				{/* <Header /> */}
+				<div className="p-3">
+					<div className="content-heading">
+						<div>Jawaban Permohonan Pencabutan Sanksi</div>
+						<div className="ml-auto">
+							<Link href="/app/pencabutan-sanksi">
+								<button className="btn btn-sm btn-secondary text-sm">&lt; kembali</button>
+							</Link>
+						</div>
+					</div>
+					<Row>
+						{sanksi.data ? (
+							<Col xl="9">
+								<Card className="card-default">
+									<CardBody>
+										<Row>
+											<Col lg={12}>
+												<DetailSanksi data={sanksi.data} />
+												<PermohonanPT data={sanksi.data.pengajuan.cabut_sanksi} />
+												<p className="lead bb">Jawaban</p>
+												<Formik
+													initialValues={{
+														status: "",
+														keterangan: "",
+														dokumen: [],
+													}}
+													validationSchema={jawabanCabutSanksiSchema}
+													onSubmit={async (data) => {
+														this.setState({ data });
+														if (sanksi.data.jawaban?.cabut_sanksi) this.toggleModal();
+														else await this.handleSimpan();
+													}}
+												>
+													{() => (
+														<Form className="form-horizontal">
+															<FormGroup>
+																<label className="row-form-label">Status:</label>
+																<div className="row-md-10">
+																	<Field name="status">
+																		{({ field, form }) => (
+																			<Select
+																				instanceId={selectInstanceId + 1}
+																				value={this.state.selectedOption}
+																				onChange={(e) => {
+																					this.handleChangeSelect(e);
+																					form.setFieldValue(field.name, e.value);
+																				}}
+																				options={[
+																					{ value: "Diterima", label: "Diterima", className: "State-ACT" },
+																					{ value: "Rekomendasi Perbaikan", label: "Rekomendasi Perbaikan", className: "State-ACT" },
+																				]}
+																			/>
+																		)}
+																	</Field>
+																	<ErrorMessage name="status" component="div" className="form-text text-danger" />
+																	{/* <span className="form-text">Pilih Jenis Pelanggaran</span> */}
+																</div>
+															</FormGroup>
+															{selectedOption && selectedOption.value === "Rekomendasi Perbaikan" ? (
+																<FormGroup>
+																	<label className="row-form-label">Keterangan:</label>
+																	<div className="row-md-10">
+																		<Field name="keterangan">{({ field }) => <Input type="textarea" {...field} />}</Field>
+																		<ErrorMessage name="keterangan" component="div" className="form-text text-danger" />
+																		{/* <span className="form-text">Deskripsi pelaporan minimum karakter 50 maksimum 200 karakter</span> */}
+																	</div>
+																</FormGroup>
+															) : (
+																""
+															)}
+															<FormGroup>
+																<label className="row-form-label">Upload Dokumen:</label>
+																<div className="row-md-10">
+																	<Field name="dokumen">
+																		{({ field, form }) => (
+																			<DropzoneWrapper
+																				className=""
+																				onDrop={(e) => {
+																					this.onDrop(e);
+																					form.setFieldValue(field.name, e);
+																				}}
+																			>
+																				{({ getRootProps, getInputProps, isDragActive }) => {
+																					return (
+																						<div {...getRootProps()} className={"dropzone card p-3 " + (isDragActive ? "dropzone-drag-active" : "")}>
+																							<input {...getInputProps()} />
+																							<div className="dropzone-previews flex">
+																								{this.state.files.length > 0 ? <Row>{thumbs}</Row> : <div className="text-center dz-default dz-message">Klik untuk upload dokumen</div>}
+																							</div>
+																							<div className="d-flex align-items-center">
+																								<small className="ml-auto">
+																									<button
+																										type="button"
+																										className="btn btn-link"
+																										onClick={(e) => {
+																											this.clearFiles(e);
+																											form.setFieldValue(field.name, []);
+																										}}
+																									>
+																										Reset dokumen
+																									</button>
+																								</small>
+																							</div>
+																						</div>
+																					);
+																				}}
+																			</DropzoneWrapper>
+																		)}
+																	</Field>
+																	<ErrorMessage name="dokumen" component="div" className="form-text text-danger" />
+																	<p className="mrgn-top-5">
+																		Ukuran setiap dokumen maksimal 15mb
+																	</p>
+																</div>
+															</FormGroup>
+															<FormGroup>
+																<div className="row-xl-10">
+																	<Button color="primary" type="submit">
+																		Simpan
+																	</Button>
+																</div>
+															</FormGroup>
+														</Form>
+													)}
+												</Formik>
+											</Col>
+										</Row>
+									</CardBody>
+								</Card>
+							</Col>
+						) : (
+							<Loader />
+						)}
+						<Col xl="3">{pt ? <DetailPT data={pt} /> : <Loader />}</Col>
+					</Row>
+					{sanksi.data && (
+						<Row>
+							<Col>
+								<Riwayat data={sanksi.data.jawaban?.cabut_sanksi} />
+							</Col>
+						</Row>
+					)}
+				</div>
+				<Modal isOpen={this.state.modal} toggle={this.toggleModal}>
+					<ModalBody>Apakah anda yakin ingin mengubah jawaban sebelumnya?</ModalBody>
+					<ModalFooter>
+						<Button color="primary" onClick={this.handleSimpan}>
+							Ya
+						</Button>{" "}
+						<Button color="secondary" onClick={this.toggleModal}>
+							Tidak
+						</Button>
+					</ModalFooter>
+				</Modal>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(JawabanPencabutanSanksi);

+ 41 - 41
pages/app/pencabutan-sanksi/index.js

@@ -1,41 +1,41 @@
-import React, { Component } from "react";
-import ContentWrapper from "@/components/Layout/ContentWrapper";
-import { Row, Col } from "reactstrap";
-import CaseProgress from "@/components/Main/CaseProgress";
-import TableSanksi from "@/components/PencabutanSanksi/TableSanksi";
-import { getSanksi } from "@/actions/sanksi";
-import { connect } from "react-redux";
-import Loader from "@/components/Common/Loader";
-
-class PencabutanSanksi extends Component {
-	constructor(props) {
-		super(props);
-		this.state = {
-			sanksi: {},
-		};
-	}
-
-	componentDidMount = async () => {
-		const { token } = this.props;
-		const sanksi = await getSanksi(token, { cabutSanksi: true });
-		this.setState({ sanksi });
-	};
-
-	render() {
-		const { sanksi } = this.state;
-		return (
-			<ContentWrapper>
-				<div className="content-heading">Permohonan Pencabutan Sanksi</div>
-				<Row>
-					<Col lg="4">
-						<CaseProgress />
-					</Col>
-					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/pencabutan-sanksi/detail" linkName="Detail" /> : <Loader />}</Col>
-				</Row>
-			</ContentWrapper>
-		);
-	}
-}
-
-const mapStateToProps = (state) => ({ user: state.user, token: state.token });
-export default connect(mapStateToProps)(PencabutanSanksi);
+import React, { Component } from "react";
+import ContentWrapper from "@/components/Layout/ContentWrapper";
+import { Row, Col } from "reactstrap";
+import CaseProgress from "@/components/Main/CaseProgress";
+import TableSanksi from "@/components/PencabutanSanksi/TableSanksi";
+import { getSanksi } from "@/actions/sanksi";
+import { connect } from "react-redux";
+import Loader from "@/components/Common/Loader";
+
+class PencabutanSanksi extends Component {
+	constructor(props) {
+		super(props);
+		this.state = {
+			sanksi: {},
+		};
+	}
+
+	componentDidMount = async () => {
+		const { token } = this.props;
+		const sanksi = await getSanksi(token, { cabutSanksi: true });
+		this.setState({ sanksi });
+	};
+
+	render() {
+		const { sanksi } = this.state;
+		return (
+			<ContentWrapper>
+				<div className="content-heading">Permohonan Pencabutan Sanksi</div>
+				<Row>
+					<Col lg="4">
+						<CaseProgress />
+					</Col>
+					<Col lg="8">{sanksi.data ? <TableSanksi listData={sanksi.data} to="/app/pencabutan-sanksi/detail" linkName="Detail" /> : <Loader />}</Col>
+				</Row>
+			</ContentWrapper>
+		);
+	}
+}
+
+const mapStateToProps = (state) => ({ user: state.user, token: state.token });
+export default connect(mapStateToProps)(PencabutanSanksi);

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно