diff --git a/package.json b/package.json index d721a5b..3e8583b 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-color": "^2.17.3", "react-content-loader": "^6.0.3", "react-dom": "^16.14.0", + "react-excel-renderer": "^1.1.0", "react-grid-layout": "^1.2.5", "react-highlight-words": "^0.18.0", "react-i18next": "^12.1.5", diff --git a/public/template/template_activity.xlsx b/public/template/template_activity.xlsx new file mode 100644 index 0000000..fea79a7 Binary files /dev/null and b/public/template/template_activity.xlsx differ diff --git a/src/routes.js b/src/routes.js index bd565bb..715634f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -9,6 +9,7 @@ const ConfigAlert = React.lazy(() => import('./views/Master/ConfigAlert')); const ControlMonitoring = React.lazy(() => import('./views/Report/ControlMonitoring')); const ControlMonitoringGantt = React.lazy(() => import('./views/ControlMonitoringGantt')); const CreatedProyek = React.lazy(() => import('./views/SimproV2/CreatedProyek')); +const GanttImportActivity = React.lazy(() => import('./views/SimproV2/CreatedProyek/ImportActivity')); const Dashboard = React.lazy(() => import('./views/Dashboard')); const Divisi = React.lazy(() => import('./views/SimproV2/Divisi')); const DivisiKaryawan = React.lazy(() => import('./views/Master/MasterTipeKaryawan')); @@ -55,6 +56,7 @@ const routes = [ { path: '/dashboard-customer/:PROJECT_ID/:GANTT_ID', name: 'DashboardCustomer', component: DashboardCustomer }, { path: '/dashboard-project/:PROJECT_ID/:GANTT_ID', exact: true, name: 'Dashboard Project', component: DashboardProject }, { path: '/projects', exact: true, name: 'Projects', component: CreatedProyek }, + { path: '/projects/:id/import/activity', exact: true, name: 'Gantt Import Activity', component: GanttImportActivity }, { path: '/projects/:id/:project/gantt', exact: true, name: 'Gantt', component: Gantt }, { path: '/human-resource', exact: true, name: 'Human Resource', component: ResourceWorker }, { path: '/material-resource', exact: true, name: 'Material Resource', component: ResourceMaterial }, diff --git a/src/views/SimproV2/CreatedProyek/DialogGantt.js b/src/views/SimproV2/CreatedProyek/DialogGantt.js index 3574038..9c76af6 100644 --- a/src/views/SimproV2/CreatedProyek/DialogGantt.js +++ b/src/views/SimproV2/CreatedProyek/DialogGantt.js @@ -123,6 +123,13 @@ const DialogGantt = ({ openDialog, closeDialog, toggleDialog, idTask, proyekName {" "} + + {" "} + + {" "} + diff --git a/src/views/SimproV2/CreatedProyek/ImportActivity/index.js b/src/views/SimproV2/CreatedProyek/ImportActivity/index.js new file mode 100644 index 0000000..c1cd433 --- /dev/null +++ b/src/views/SimproV2/CreatedProyek/ImportActivity/index.js @@ -0,0 +1,397 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import axios from "../../../../const/interceptorApi" +import { Card, CardBody, CardHeader, Col, Row, Input } from 'reactstrap'; +import { NotificationContainer, NotificationManager } from 'react-notifications'; +import { Button, Table, Form, Upload, Tooltip, Alert, Spin } from 'antd'; +import { InboxOutlined, UploadOutlined } from '@ant-design/icons'; +import {OutTable, ExcelRenderer} from 'react-excel-renderer'; +import './table.css'; +import { BASE_OSPRO } from '../../../../const/ApiConst'; +import { Prompt } from 'react-router-dom'; +// +const config = { + headers: + { + Authorization: `Bearer ${token}`, + "Content-type": `application/json` + } +}; +//const token = window.localStorage.getItem('token'); +const token = localStorage.getItem("token"); +const HEADER = { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, +}; + +const normFile = (e) => { + if (Array.isArray(e)) { + return e; + } + + return e?.fileList; +}; + +const formItemLayout = { + labelCol: { + span: 6, + }, + wrapperCol: { + span: 14, + }, +}; + +const errorHandler = { + // error notif + // reload table + // reload upload +}; + +const columnToIndexs = { + columnName: 'index', + level: 0, + predecessor: 1, + no: 2, + name: 3, + weight: 4, + start_date: 5, + end_date: 6, + picKit: 7, + picOwner: 8, + duration: 9 +} + +const ExcelDateToJSDate = (date) => { + let converted_date = new Date(Math.round((date - 25569) * 864e5)); + converted_date = String(converted_date).slice(4, 15) + date = converted_date.split(" ") + let day = date[1]; + let month = date[0]; + month = "JanFebMarAprMayJunJulAugSepOctNovDec".indexOf(month) / 3 + 1 + if (month.toString().length <= 1) + month = '0' + month + let year = date[2]; + return String(day + '-' + month + '-' + year.slice(2, 4)) +} + +const ImportActivity = ({ params }) => { + const pageName = params.name; + + const [dataTable, setDatatable] = useState([]) + // + const [isMovePage, setIsMovePage] = useState(false); + const [isPreview, setIsPreview] = useState(false); + + const fileHandler = (file) => { + setIsMovePage(true); + if(file.name.slice(file.name.lastIndexOf('.')+1) === "xlsx"){ + // NotificationManager.info('Loading...',' '); + setTimeout(() => { + previewHandler(file); + setIsMovePage(false); + }, 5000); + } else { + NotificationManager.error('file harus dalam format .xlsx', 'Failed'); + } + // else{ + // errorHandler() + // } + return false + } + + const previewHandler = (fileObj) => { + let dataMapped = [] + + ExcelRenderer(fileObj, (err, resp) => { + if(err){ + console.log(err) + errorHandler() + } else { + // let columns = resp.cols + let rawRows = resp.rows + + let rows = rawRows.filter(function (el) { + return el.length > 0; + }); + + for(let i = 5; i < rows.length; i++){ + let extractedRow = {} + + for (var prop in columnToIndexs) { + if (columnToIndexs.hasOwnProperty.call(columnToIndexs, prop)) { + let columnData = rows[i][columnToIndexs[prop]] + + // remove leading whitespace + if(typeof(columnData) == 'string') { + columnData = columnData.trim().replace(/ /g, '').replace(/<[^\/>][^>]*><\/[^>]+>/g, "") + } + + // convert excel's DATEVALUE + if(["start_date", "end_date"].includes(prop)){ + columnData = ExcelDateToJSDate(columnData) + } + + if(prop == 'weight'){ + columnData = Math.round(columnData * 100) + } + + extractedRow[prop] = columnData + + } + } + + dataMapped.push(extractedRow) + } + + setDatatable(dataMapped) + setIsPreview(true) + } + }); + + } + + const saveHandler = async (e) => { + setIsMovePage(true); + + let url = window.location.href; + let urlSplitted = url.split('/') + const ganttId = urlSplitted[5] + + const URL = `${BASE_OSPRO}/api/activity/import` + + let payload = { + ganttId: ganttId, + activities: dataTable + } + + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((error) => error.response); + + console.log(result) + if (result.data.code == 200) { + window.location = urlSplitted[0] + '//' + urlSplitted[2] + `/#/projects/` + ganttId + '/' + result.data.projectId + '/gantt' + } + + setIsMovePage(true); + + } + + const columns = [ + { + title: 'No', + dataIndex: 'no', + key: 'no', + width: '5%', + }, + { + title: 'Level', + dataIndex: 'level', + key: 'level', + width: '10%', + align: 'center', + + }, + { + title: 'Predecessor', + dataIndex: 'predecessor', + key: 'predecessor', + align: 'center', + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text, record) => { + let prepend = "" + for(let i = 1; i < record.level; i++) { + prepend = prepend + "--" + } + + return {prepend} {text} + }, + ellipsis: true, + width: '25%', + }, + { + title: 'Weight', + dataIndex: 'weight', + key: 'weight', + render: (text, record) => { + return {text}% + }, + width: '10%', + align: 'center', + }, + { + title: 'Start Date', + dataIndex: 'start_date', + key: 'start_date', + }, + { + title: 'Finish Date', + dataIndex: 'end_date', + key: 'end_date', + }, + { + title: 'Duration', + dataIndex: 'duration', + key: 'duration', + align: 'right', + }, + { + title: 'PIC KIT', + dataIndex: 'picKit', + key: 'picKit', + }, + { + title: 'PIC Owner', + dataIndex: 'picOwner', + key: 'picOwner', + }, + ]; + + if (isMovePage) { + return ( +
+ +
+ + +
+ + + + fileHandler(file)} + showUploadList={false} + > + + + + +
+ + + + +
+
+ + +

Data Preview

+
+ + + index % 2 == 0 ? 'table-row-light' : 'table-row-dark'} + dataSource={dataTable} + columns={columns} + pagination={false} + /> + + + + 'Import Activity belum disave ingin meninggalkan halaman ini?'} + /> + + ) + } + + return ( +
+ +
+ +
+ + + + + fileHandler(file)} + showUploadList={false} + > + + + + + + + + + + + + + +

Data Preview

+
+ +
index % 2 == 0 ? 'table-row-light' : 'table-row-dark'} + dataSource={dataTable} + columns={columns} + pagination={false} + /> + + + 'Import Activity belum disave ingin meninggalkan halaman ini?'} + /> + + ) +} + +export default ImportActivity; diff --git a/src/views/SimproV2/CreatedProyek/ImportActivity/table.css b/src/views/SimproV2/CreatedProyek/ImportActivity/table.css new file mode 100644 index 0000000..b519795 --- /dev/null +++ b/src/views/SimproV2/CreatedProyek/ImportActivity/table.css @@ -0,0 +1,13 @@ +.ant-table-tbody{ + padding: 0 !important; +} +.ant-table-tbody > tr > td{ + padding: 0 !important; +} + +.table-row-light { + background-color: #ffffff; +} +.table-row-dark { + background-color: #f0f3f5; +}