Muhammad Sulaiman Yusuf
2 years ago
19 changed files with 260 additions and 2163 deletions
@ -0,0 +1,20 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import axios from 'axios' |
||||
|
||||
const DashboardPMO = () => { |
||||
const token = localStorage.getItem("token") |
||||
const HEADER = { |
||||
headers: { |
||||
"Content-Type": "application/json", |
||||
"Authorization": `Bearer ${token}` |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div></div> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default DashboardPMO; |
@ -1,128 +0,0 @@
|
||||
.number-asset { |
||||
font-size: 50px; |
||||
font-weight: bold; |
||||
text-align: center; |
||||
} |
||||
|
||||
.text-bold { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.view-rectangle { |
||||
display: flex; |
||||
flex-direction: row; |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.view1 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
margin-bottom: -20px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #008B8B; |
||||
} |
||||
|
||||
.view2 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #8B008B; |
||||
} |
||||
|
||||
.view3 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #7CFC00; |
||||
} |
||||
|
||||
.view4 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #FF0000; |
||||
} |
||||
|
||||
.view5 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #4682B4; |
||||
} |
||||
|
||||
.number-style { |
||||
font-size: 30px; |
||||
color: black; |
||||
font-weight: bold; |
||||
margin-bottom:15px; |
||||
} |
||||
|
||||
.number-style1 { |
||||
font-size: 30px; |
||||
color: #FFFFFF; |
||||
font-weight: bold; |
||||
margin-bottom:15px; |
||||
} |
||||
|
||||
.daily-info-card { |
||||
min-width: 130px; |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
margin: 4px; |
||||
border-radius: 8px; |
||||
} |
||||
|
||||
|
||||
.dashboard-container { |
||||
overflow-x: auto; |
||||
} |
||||
|
||||
.maptable-window-button-container { |
||||
float: right; |
||||
right: 0px; |
||||
} |
||||
|
||||
.maptable-close, .maptable-maximize, .maptable-minimize { |
||||
cursor: pointer; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.maptable-header { |
||||
margin-bottom: -10px; |
||||
} |
||||
|
||||
.maptable-title { |
||||
font-size: 24px; |
||||
font-weight: 700; |
||||
text-align: left; |
||||
margin-left: 20px; |
||||
} |
||||
|
||||
.maptable-close:hover, .maptable-maximize:hover, .maptable-minimize:hover { |
||||
color: #20a8d8; |
||||
} |
@ -1,41 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'; |
||||
import ReactDOM from 'react-dom'; |
||||
import { Pie } from '@ant-design/plots'; |
||||
|
||||
const PieChart = () => { |
||||
const data = [ |
||||
{ |
||||
type: 'FTTH', |
||||
value: 27, |
||||
}, |
||||
{ |
||||
type: 'Konstruksi', |
||||
value: 25, |
||||
}, |
||||
]; |
||||
const config = { |
||||
appendPadding: 10, |
||||
data, |
||||
angleField: 'value', |
||||
colorField: 'type', |
||||
radius: 0.9, |
||||
label: { |
||||
type: 'inner', |
||||
offset: '-30%', |
||||
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`, |
||||
style: { |
||||
fontSize: 14, |
||||
textAlign: 'center', |
||||
}, |
||||
}, |
||||
interactions: [ |
||||
{ |
||||
type: 'element-active', |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
return <Pie {...config} />; |
||||
}; |
||||
|
||||
export default PieChart; |
@ -1,106 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Doughnut, Bar } from 'react-chartjs-2'; |
||||
import faker from '@faker-js/faker'; |
||||
|
||||
export const optionsDoughnut = { |
||||
title: { |
||||
display: true, |
||||
text: 'PROJECT BY TYPE' |
||||
}, |
||||
}; |
||||
|
||||
export const data = { |
||||
labels: ['Manned guarding ', 'C&T', 'CMS ', 'ESS', 'RSO'], |
||||
datasets: [ |
||||
{ |
||||
label: '# of Votes', |
||||
data: [12, 19, 3, 5, 2], |
||||
backgroundColor: [ |
||||
'rgba(255, 99, 132, 0.2)', |
||||
'rgba(54, 162, 235, 0.2)', |
||||
'rgba(255, 206, 86, 0.2)', |
||||
'rgba(75, 192, 192, 0.2)', |
||||
'rgba(153, 102, 255, 0.2)', |
||||
// 'rgba(255, 159, 64, 0.2)',
|
||||
], |
||||
borderColor: [ |
||||
'rgba(255, 99, 132, 1)', |
||||
'rgba(54, 162, 235, 1)', |
||||
'rgba(255, 206, 86, 1)', |
||||
'rgba(75, 192, 192, 1)', |
||||
'rgba(153, 102, 255, 1)', |
||||
// 'rgba(255, 159, 64, 1)',
|
||||
], |
||||
borderWidth: 1, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
export const DoughnutChart = () => { |
||||
return <Doughnut data={data} options={optionsDoughnut} />; |
||||
} |
||||
|
||||
|
||||
export const options = { |
||||
title: { |
||||
display: true, |
||||
text: 'PROJECT BY GOVERNANCE PHASE' |
||||
}, |
||||
legend: { |
||||
position: 'right', |
||||
}, |
||||
responsive: true, |
||||
scales: { |
||||
yAxes: [{ |
||||
display: true, |
||||
|
||||
// position: 'right',
|
||||
stacked: true, |
||||
}], |
||||
xAxes: [{ |
||||
display: true, |
||||
// position: 'right',
|
||||
stacked: true, |
||||
}], |
||||
}, |
||||
}; |
||||
|
||||
const labels = ['']; |
||||
|
||||
export const dataBar = { |
||||
labels, |
||||
datasets: [ |
||||
{ |
||||
indexAxis: 'y', |
||||
label: 'Initiation', |
||||
data: labels.map(() => faker.datatype.number({ min: 0, max: 10 })), |
||||
backgroundColor: 'rgb(255, 99, 132)', |
||||
}, |
||||
{ |
||||
indexAxis: 'y', |
||||
label: 'Execution', |
||||
data: labels.map(() => faker.datatype.number({ min: 0, max: 10 })), |
||||
backgroundColor: 'rgb(75, 192, 192)', |
||||
}, |
||||
{ |
||||
indexAxis: 'y', |
||||
label: 'Closing', |
||||
data: labels.map(() => faker.datatype.number({ min: 0, max: 10 })), |
||||
backgroundColor: 'rgb(53, 162, 235)', |
||||
}, |
||||
// {
|
||||
// indexAxis: 'y',
|
||||
// label: 'Excecution',
|
||||
// data: labels.map(() => faker.datatype.number({ min: 0, max: 10 })),
|
||||
// backgroundColor: 'rgba(255, 159, 64, 1)',
|
||||
// },
|
||||
], |
||||
}; |
||||
|
||||
export function BarChart() { |
||||
return <Bar indexAxis='y' options={options} data={dataBar} />; |
||||
} |
||||
|
||||
|
||||
|
||||
|
@ -1,130 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Table, Tag, Space } from 'antd'; |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: 'Project Name', |
||||
dataIndex: 'project', |
||||
key: 'project', |
||||
fixed: 'left', |
||||
width: 200, |
||||
// render: text => <a>{text}</a>,
|
||||
}, |
||||
{ |
||||
title: 'Project Owner', |
||||
dataIndex: 'owner', |
||||
key: 'owner', |
||||
}, |
||||
{ |
||||
title: 'Start Date', |
||||
dataIndex: 'startDate', |
||||
key: 'startDate', |
||||
}, |
||||
{ |
||||
title: 'End Date', |
||||
dataIndex: 'endDate', |
||||
key: 'endDate', |
||||
}, |
||||
{ |
||||
title: 'Cost', |
||||
dataIndex: 'cost', |
||||
key: 'cost', |
||||
render: text => `Rp. ${text.toLocaleString()}`, |
||||
align: 'right' |
||||
}, |
||||
{ |
||||
title: 'Cost Health', |
||||
dataIndex: 'costHealth', |
||||
key: 'costHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' /> |
||||
, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: 'Work Health', |
||||
dataIndex: 'workHealth', |
||||
key: 'workHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' />, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: 'Schedule Health', |
||||
dataIndex: 'scheduleHealth', |
||||
key: 'scheduleHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' />, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: '% Complete', |
||||
dataIndex: 'complete', |
||||
key: 'complete', |
||||
render: text => `${text}%`, |
||||
align: 'right' |
||||
}, |
||||
// {
|
||||
// title: 'Action',
|
||||
// key: 'action',
|
||||
// render: (text, record) => (
|
||||
// <Space size="middle">
|
||||
// <a>Invite {record.name}</a>
|
||||
// <a>Delete</a>
|
||||
// </Space>
|
||||
// ),
|
||||
// },
|
||||
]; |
||||
|
||||
const data = [ |
||||
{ |
||||
key: '1', |
||||
project: 'Bay Plaza', |
||||
owner: 'John Brown', |
||||
startDate: '1/22/2022', |
||||
endDate: '1/22/2023', |
||||
complete: 20, |
||||
cost: 40000000, |
||||
costHealth: 'good', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'warning' |
||||
}, |
||||
{ |
||||
key: '2', |
||||
project: 'Jambi Bridge', |
||||
owner: 'Jim Green', |
||||
startDate: '4/22/2022', |
||||
endDate: '11/22/2023', |
||||
complete: 65, |
||||
cost: 20000000, |
||||
costHealth: 'good', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'danger' |
||||
}, |
||||
{ |
||||
key: '3', |
||||
project: 'Banten International Airport', |
||||
owner: 'Joe Black', |
||||
startDate: '1/22/2022', |
||||
endDate: '1/22/2025', |
||||
complete: 50, |
||||
cost: 200000000, |
||||
costHealth: 'default', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'default' |
||||
}, |
||||
]; |
||||
|
||||
const TableDashboard = () => { |
||||
return ( |
||||
<Table size="small" columns={columns} dataSource={data} scroll={{ x: 1300, y: 300 }} /> |
||||
); |
||||
} |
||||
|
||||
export default TableDashboard; |
@ -1,44 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Doughnut } from 'react-chartjs-2'; |
||||
|
||||
const options = { |
||||
title: { |
||||
display: true, |
||||
text: 'PROJECT BY TYPE' |
||||
}, |
||||
}; |
||||
|
||||
export const ProjectTypeChart = ({ projectTypes, projectsByType }) => { |
||||
let total = [] |
||||
|
||||
for (var j = 0; j < projectTypes.length; j++) { |
||||
total.push(projectsByType[j].total) |
||||
} |
||||
|
||||
let dataSets = [{ |
||||
label: "#", |
||||
data: total, |
||||
backgroundColor: [ |
||||
'rgba(255, 99, 132, 0.2)', |
||||
'rgba(54, 162, 235, 0.2)', |
||||
'rgba(255, 206, 86, 0.2)', |
||||
'rgba(75, 192, 192, 0.2)', |
||||
], |
||||
borderColor: [ |
||||
'rgba(255, 99, 132, 1)', |
||||
'rgba(54, 162, 235, 1)', |
||||
'rgba(255, 206, 86, 1)', |
||||
'rgba(75, 192, 192, 1)', |
||||
'rgba(153, 102, 255, 1)', |
||||
], |
||||
borderWidth: 1, |
||||
}] |
||||
|
||||
const data = { |
||||
labels: projectTypes, |
||||
datasets: dataSets, |
||||
}; |
||||
|
||||
return <Doughnut data={data} options={options} />; |
||||
} |
||||
|
@ -1,181 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { Row, Col, Collapse } from 'antd'; |
||||
import TableDashboardV1 from './tableDashboardv1'; |
||||
import { ProjectTypeChart } from './chartDashboard'; |
||||
import { ProjectPhaseChart } from './projectPhaseChart'; |
||||
import ProjectMap from './projectMap'; |
||||
import axios from 'axios' |
||||
import { BASE_OSPRO } from '../../const/ApiConst'; |
||||
import { formatRibuanDecimal } from '../../const/CustomFunc.js'; |
||||
|
||||
const { Panel } = Collapse; |
||||
|
||||
function BoxDashboardNoIcon({ value, title, secondaryTitle, bgColor, valFSize = '1.0rem', fSize = '0.7rem' }) { |
||||
return ( |
||||
<> |
||||
<div style={{ backgroundColor: bgColor, height: 60 }} class='box-dashboard-pm'> |
||||
<Row> |
||||
<Col span={24}> |
||||
<div class='box-content'> |
||||
<div style={{ fontSize: valFSize }} class='text-box'>{value}</div> |
||||
<div style={{ fontSize: fSize }} class='text-box'>{title}</div> |
||||
<div style={{ fontSize: '0.6rem' }} class='text-box-secondary'>{secondaryTitle && secondaryTitle.toUpperCase()}</div> |
||||
</div> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const DashboardPM = () => { |
||||
const [PROJECTCOUNT, SET_PROJECTCOUNT] = useState(0) |
||||
const [PROJECTBUDGETTOTAL, SET_PROJECTBUDGETTOTAL] = useState(0) |
||||
const [PROJECTMANPOWER, SET_PROJETMANPOWER] = useState(0) |
||||
const [PROJECTACTUALCOSTTOTAL, SET_PROJECTACTUALCOSTTOTAL] = useState(0) |
||||
const [PROJECTCOSTTOTAL, SET_PROJECTCOSTTOTAL] = useState(0) |
||||
const [TOTALREVENUE, SET_TOTALREVENUE] = useState(0) |
||||
const [MANPOWERS, SET_MANPOWERS] = useState(0) |
||||
const [PROJECTPHASES, SET_PROJECTPHASES] = useState([]) |
||||
const [PROJECTSBYPHASE, SET_PROJECTSBYPHASE] = useState([]) |
||||
const [PROJECTSONDANGER, SET_PROJECTSONDANGER] = useState(0) |
||||
const [PROJECTLOCATIONS, SET_PROJECTLOCATIONS] = useState([]) |
||||
|
||||
const [PROJECTTYPES, SET_PROJECTTYPES] = useState([]) |
||||
const [PROJECTSBYTYPE, SET_PROJECTSBYTYPE] = useState([]) |
||||
|
||||
const token = localStorage.getItem("token") |
||||
const HEADER = { |
||||
headers: { |
||||
"Content-Type": "application/json", |
||||
"Authorization": `Bearer ${token}` |
||||
} |
||||
} |
||||
|
||||
const getProjectInfos = async () => { |
||||
const URL = `${BASE_OSPRO}/api/project/list` |
||||
const result = await axios.get(URL, HEADER).then(res => res).catch(err => err.response) |
||||
let markers = [] |
||||
|
||||
SET_PROJECTCOUNT(result.data.totalRecord) |
||||
result.data.totalActualCost != undefined ? SET_PROJECTCOSTTOTAL(result.data.totalActualCost) : SET_PROJECTCOSTTOTAL(0) |
||||
result.data.totalPlannedCost != undefined ? SET_PROJECTBUDGETTOTAL(result.data.totalPlannedCost) : SET_PROJECTBUDGETTOTAL(0) |
||||
result.data.totalRevenue != undefined ? SET_TOTALREVENUE(result.data.totalRevenue) : SET_TOTALREVENUE(0) |
||||
result.data.manpowers != undefined ? SET_MANPOWERS(result.data.manpowers) : SET_MANPOWERS(0) |
||||
result.data.projectPhases != undefined ? SET_PROJECTPHASES(result.data.projectPhases) : SET_PROJECTPHASES([]) |
||||
result.data.projectsByPhase != undefined ? SET_PROJECTSBYPHASE(result.data.projectsByPhase) : SET_PROJECTSBYPHASE([]) |
||||
result.data.projectTypes != undefined ? SET_PROJECTTYPES(result.data.projectTypes) : SET_PROJECTTYPES([]) |
||||
result.data.projectsByType != undefined ? SET_PROJECTSBYTYPE(result.data.projectsByType) : SET_PROJECTSBYTYPE([]) |
||||
result.data.projectsOnDanger != undefined ? SET_PROJECTSONDANGER(result.data.projectsOnDanger) : SET_PROJECTSONDANGER(0) |
||||
|
||||
for (let i = 0; i < result.data.totalRecord; i++) { |
||||
if (typeof (result.data.data[i].geolocation) == 'object' && result.data.data[i].geolocation.length > 0) { |
||||
markers.push({ |
||||
lat: result.data.data[i].geolocation[0].lat, |
||||
lon: result.data.data[i].geolocation[0].lon, |
||||
label: result.data.data[i].area_kerja + "\n" + result.data.data[i].nama |
||||
}) |
||||
} |
||||
} |
||||
SET_PROJECTLOCATIONS(markers) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
getProjectInfos(); |
||||
}, []) |
||||
|
||||
return ( |
||||
<> |
||||
<div> |
||||
<Row |
||||
gutter={{ |
||||
xs: 8, |
||||
sm: 16, |
||||
md: 24, |
||||
lg: 32, |
||||
}} |
||||
> |
||||
<Col span={2} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={PROJECTCOUNT} |
||||
bgColor="#0287c7" |
||||
title="Projects" |
||||
/> |
||||
</Col> |
||||
<Col span={3} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={PROJECTSONDANGER} |
||||
bgColor="#f51d1d" |
||||
title="Projects on Danger" |
||||
fSize="0.7rem" |
||||
/> |
||||
</Col> |
||||
<Col span={5} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={`${formatRibuanDecimal(PROJECTBUDGETTOTAL)}`} |
||||
bgColor="#cf6102" |
||||
title="Budget" |
||||
valFSize="1rem" |
||||
/> |
||||
</Col> |
||||
<Col span={5} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={`${formatRibuanDecimal(PROJECTCOSTTOTAL)}`} |
||||
bgColor="#f51d1d" |
||||
title="Project Expanditure" |
||||
valFSize="1rem" |
||||
/> |
||||
</Col> |
||||
<Col span={5} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={`${formatRibuanDecimal(TOTALREVENUE)}`} |
||||
bgColor="#077857" |
||||
title="Revenue" |
||||
valFSize="1rem" |
||||
/> |
||||
</Col> |
||||
<Col span={2} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={MANPOWERS} |
||||
bgColor="#0287c7" |
||||
title="Manpower" |
||||
/> |
||||
</Col> |
||||
<Col span={2} style={{ margin: '' }}> |
||||
<BoxDashboardNoIcon |
||||
value={<i style={{ color: '#fff' }} class="zmdi zmdi-truck zmdi-hc-2x"></i>} |
||||
bgColor="#0287c7" |
||||
title="OSLOG" |
||||
/> |
||||
</Col> |
||||
</Row> |
||||
<Row style={{ marginTop: '15px' }}> |
||||
<Col span={24} style={{ margin: '0px 0px 0px 0px' }}> |
||||
<Collapse> |
||||
<Panel header="More Information" key="1"> |
||||
<Row style={{ margin: '10px 10px 0px 10px' }}> |
||||
<Col span={8} > |
||||
<ProjectMap markers={PROJECTLOCATIONS} /> |
||||
</Col> |
||||
<Col span={8}> |
||||
<ProjectPhaseChart projectPhases={PROJECTPHASES} projectsByPhase={PROJECTSBYPHASE} /> |
||||
</Col> |
||||
<Col span={8}> |
||||
<ProjectTypeChart projectTypes={PROJECTTYPES} projectsByType={PROJECTSBYTYPE} /> |
||||
</Col> |
||||
</Row> |
||||
</Panel> |
||||
</Collapse> |
||||
</Col> |
||||
</Row> |
||||
<Row style={{ marginTop: '15px' }}> |
||||
<Col span={24}> |
||||
<TableDashboardV1 /> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default DashboardPM; |
@ -1,40 +0,0 @@
|
||||
import React from 'react'; |
||||
|
||||
import icon from 'leaflet/dist/images/marker-icon.png'; |
||||
import iconShadow from 'leaflet/dist/images/marker-shadow.png'; |
||||
import L from 'leaflet'; |
||||
import { MapContainer, Marker, TileLayer, Popup } from 'react-leaflet' |
||||
import 'leaflet/dist/leaflet.css'; |
||||
|
||||
const defaultMapPosition = [-6.1753924, 106.8271528] |
||||
|
||||
const DefaultIcon = L.icon({ |
||||
iconUrl: icon, |
||||
shadowUrl: iconShadow, |
||||
iconSize: [15, 25], |
||||
shadowSize: [0, 0], |
||||
}); |
||||
L.Marker.prototype.options.icon = DefaultIcon; |
||||
|
||||
const ProjectMap = ({ markers }) => { |
||||
return ( |
||||
<MapContainer |
||||
center={defaultMapPosition} |
||||
zoom={5} |
||||
> |
||||
<TileLayer |
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" |
||||
/> |
||||
{markers.map(({ lat, lon, label }) => ( |
||||
<Marker position={[lat, lon]}> |
||||
<Popup> |
||||
{label} |
||||
</Popup> |
||||
</Marker> |
||||
))} |
||||
</MapContainer> |
||||
); |
||||
} |
||||
|
||||
export default ProjectMap; |
@ -1,37 +0,0 @@
|
||||
import React from 'react'; |
||||
import { HorizontalBar } from 'react-chartjs-2'; |
||||
|
||||
const options = { |
||||
responsive: true, |
||||
title: { |
||||
display: true, |
||||
text: 'PROJECT BY PHASE' |
||||
} |
||||
}; |
||||
|
||||
export const ProjectPhaseChart = ({ projectPhases, projectsByPhase }) => { |
||||
let dataSets = [] |
||||
let total = [] |
||||
|
||||
for (var i = 0; i < projectsByPhase.length; i++) { |
||||
total = [] |
||||
for (var j = 0; j < projectPhases.length; j++) { |
||||
if (projectPhases[j] != projectsByPhase[i].name) { |
||||
total.push(0) |
||||
} else { |
||||
total.push(projectsByPhase[i].total) |
||||
} |
||||
} |
||||
dataSets.push({ |
||||
label: projectsByPhase[i].name, |
||||
data: total, |
||||
backgroundColor: projectsByPhase[i].color, |
||||
borderColor: '#000000', |
||||
}); |
||||
} |
||||
const data = { |
||||
datasets: dataSets, |
||||
labels: projectPhases, |
||||
}; |
||||
return <HorizontalBar options={options} data={data} />; |
||||
} |
@ -1,130 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Table, Tag, Space } from 'antd'; |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: 'Project Name', |
||||
dataIndex: 'project', |
||||
key: 'project', |
||||
fixed: 'left', |
||||
width: 200, |
||||
render: text => <a>{text}</a>, |
||||
}, |
||||
{ |
||||
title: 'Project Owner', |
||||
dataIndex: 'owner', |
||||
key: 'owner', |
||||
}, |
||||
{ |
||||
title: 'Start Date', |
||||
dataIndex: 'startDate', |
||||
key: 'startDate', |
||||
}, |
||||
{ |
||||
title: 'End Date', |
||||
dataIndex: 'endDate', |
||||
key: 'endDate', |
||||
}, |
||||
{ |
||||
title: 'Cost', |
||||
dataIndex: 'cost', |
||||
key: 'cost', |
||||
render: text => `Rp. ${text.toLocaleString()}`, |
||||
align: 'right' |
||||
}, |
||||
{ |
||||
title: 'Cost Health', |
||||
dataIndex: 'costHealth', |
||||
key: 'costHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' /> |
||||
, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: 'Work Health', |
||||
dataIndex: 'workHealth', |
||||
key: 'workHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' />, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: 'Schedule Health', |
||||
dataIndex: 'scheduleHealth', |
||||
key: 'scheduleHealth', |
||||
render: text => |
||||
text === 'good' ? <div className='health-status-good' /> : |
||||
text === 'danger' ? <div className='health-status-danger' /> : |
||||
text === 'warning' ? <div className='health-status-warning' /> : <div className='health-status-default' />, |
||||
align: 'center', |
||||
}, |
||||
{ |
||||
title: '% Complete', |
||||
dataIndex: 'complete', |
||||
key: 'complete', |
||||
render: text => `${text}%`, |
||||
align: 'right' |
||||
}, |
||||
// {
|
||||
// title: 'Action',
|
||||
// key: 'action',
|
||||
// render: (text, record) => (
|
||||
// <Space size="middle">
|
||||
// <a>Invite {record.name}</a>
|
||||
// <a>Delete</a>
|
||||
// </Space>
|
||||
// ),
|
||||
// },
|
||||
]; |
||||
|
||||
const data = [ |
||||
{ |
||||
key: '1', |
||||
project: 'Bay Plaza', |
||||
owner: 'John Brown', |
||||
startDate: '1/22/2022', |
||||
endDate: '1/22/2023', |
||||
complete: 20, |
||||
cost: 40000000, |
||||
costHealth: 'good', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'warning' |
||||
}, |
||||
{ |
||||
key: '2', |
||||
project: 'Jambi Bridge', |
||||
owner: 'Jim Green', |
||||
startDate: '4/22/2022', |
||||
endDate: '11/22/2023', |
||||
complete: 65, |
||||
cost: 20000000, |
||||
costHealth: 'good', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'danger' |
||||
}, |
||||
{ |
||||
key: '3', |
||||
project: 'Banten International Airport', |
||||
owner: 'Joe Black', |
||||
startDate: '1/22/2022', |
||||
endDate: '1/22/2025', |
||||
complete: 50, |
||||
cost: 200000000, |
||||
costHealth: 'default', |
||||
workHealth: 'default', |
||||
scheduleHealth: 'default' |
||||
}, |
||||
]; |
||||
|
||||
const TableDashboard = () => { |
||||
return ( |
||||
<Table size="small" columns={columns} dataSource={data} scroll={{ x: 1300, y: 300 }} /> |
||||
); |
||||
} |
||||
|
||||
export default TableDashboard; |
@ -1,376 +0,0 @@
|
||||
import '../../../node_modules/react-grid-layout/css/styles.css'; |
||||
import '../../../node_modules/react-resizable/css/styles.css'; |
||||
import './../DashboardPMO/Dashboard.css'; |
||||
import { BASE_OSPRO } from '../../const/ApiConst'; |
||||
import React, { useEffect, useState, useRef } from 'react'; |
||||
import axios from 'axios' |
||||
import { Table, Row, Col, Button, Input, InputNumber, Space } from 'antd'; |
||||
import { formatRibuanDecimal } from '../../const/CustomFunc.js'; |
||||
import { Badge } from 'reactstrap'; |
||||
import { Link } from "react-router-dom"; |
||||
import { SearchOutlined } from '@ant-design/icons'; |
||||
import Highlighter from 'react-highlight-words'; |
||||
import _ from "lodash"; |
||||
import Slider from "antd/lib/slider"; |
||||
import numeral from "numeral"; |
||||
|
||||
const filterData = data => formatter => data.map(item => ({ |
||||
text: formatter(item), |
||||
value: formatter(item) |
||||
})) |
||||
|
||||
const TableDashboardV1 = () => { |
||||
const token = localStorage.getItem("token") |
||||
const HEADER = { |
||||
headers: { |
||||
"Content-Type": "application/json", |
||||
"Authorization": `Bearer ${token}`, |
||||
} |
||||
} |
||||
|
||||
const [MANPOWERS, SET_MANPOWERS] = useState(0) |
||||
const [MAXPLANNEDCOST, SET_MAXPLANNEDCOST] = useState(0) |
||||
const [MAXACTUALCOST, SET_MAXACTUALCOST] = useState(0) |
||||
const [MAXCOSTVARIANCE, SET_MAXCOSTVARIANCE] = useState(0) |
||||
const [dataTable, setDataTable] = useState([]) |
||||
const [searchText, setSearchText] = useState('') |
||||
const [searchedColumn, setSearchedColumn] = useState('') |
||||
const searchInput = useRef(null) |
||||
|
||||
const handleSearch = (selectedKeys, confirm, dataIndex) => { |
||||
confirm() |
||||
setSearchText(selectedKeys[0]) |
||||
setSearchedColumn(dataIndex) |
||||
}; |
||||
|
||||
const handleSlide = (dataIndex, value, confirm) => { |
||||
confirm({ closeDropdown: false }); |
||||
}; |
||||
|
||||
const slider = (dataIndex, maxSlider) => ({ |
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => { |
||||
return ( |
||||
<div |
||||
className="custom-filter-dropdown ant-table-filter-dropdown" |
||||
style={{ minWidth: "20rem", padding: "0.5rem 1rem" }} |
||||
> |
||||
<Row> |
||||
<Col span={4}> |
||||
<div style={{ display: "flex", flexDirection: "column" }}> |
||||
<div> |
||||
<strong>Min: </strong> |
||||
</div> |
||||
<div>0</div> |
||||
</div> |
||||
</Col> |
||||
<Col span={16}> |
||||
<Slider |
||||
range={true} |
||||
min={0} |
||||
max={maxSlider} |
||||
defaultValue={[0, maxSlider]} |
||||
onAfterChange={(value) => { |
||||
setSelectedKeys([value]) |
||||
handleSlide(dataIndex, value, confirm)} |
||||
} |
||||
/> |
||||
</Col> |
||||
<Col span={4}> |
||||
<div style={{ display: "flex", flexDirection: "column" }}> |
||||
<div> |
||||
<strong>Max:</strong> |
||||
</div> |
||||
<div style={{ whitespace: "nowrap", overflow: "hidden" }} ellipsis={true}>{dataIndex == 'manpower' || dataIndex == 'progress' ? maxSlider : formatRibuanDecimal(maxSlider)}</div> |
||||
</div> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
)}, |
||||
onFilter: (value, record) => { |
||||
return (record[dataIndex] >= value[0] && record[dataIndex] <= value[1]) |
||||
}, |
||||
onFilterDropdownVisibleChange: (visible) => { |
||||
if (visible) { |
||||
setTimeout(() => searchInput.current?.select(), 100); |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
const handleReset = (clearFilters) => { |
||||
clearFilters(); |
||||
setSearchText(''); |
||||
}; |
||||
|
||||
const getColumnSearchProps = (dataIndex) => ({ |
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => { |
||||
return ( |
||||
<div |
||||
style={{ |
||||
padding: 8, |
||||
}} |
||||
> |
||||
<Input |
||||
ref={searchInput} |
||||
placeholder={`Search ${dataIndex}`} |
||||
value={selectedKeys[0]} |
||||
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])} |
||||
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)} |
||||
style={{ |
||||
marginBottom: 8, |
||||
display: 'block', |
||||
}} |
||||
/> |
||||
<Space> |
||||
<Button |
||||
type="primary" |
||||
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)} |
||||
icon={<SearchOutlined />} |
||||
size="small" |
||||
style={{ |
||||
width: 90, |
||||
}} |
||||
> |
||||
Search |
||||
</Button> |
||||
<Button |
||||
onClick={() => clearFilters && handleReset(clearFilters)} |
||||
size="small" |
||||
style={{ |
||||
width: 90, |
||||
}} |
||||
> |
||||
Reset |
||||
</Button> |
||||
<Button |
||||
type="link" |
||||
size="small" |
||||
onClick={() => { |
||||
confirm({ |
||||
closeDropdown: false, |
||||
}); |
||||
setSearchText(selectedKeys[0]); |
||||
setSearchedColumn(dataIndex); |
||||
}} |
||||
> |
||||
</Button> |
||||
</Space> |
||||
</div> |
||||
)}, |
||||
filterIcon: (filtered) => ( |
||||
<SearchOutlined |
||||
style={{ |
||||
color: filtered ? '#1890ff' : undefined, |
||||
}} |
||||
/> |
||||
), |
||||
onFilter: (value, record) => { |
||||
return record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
||||
}, |
||||
onFilterDropdownVisibleChange: (visible) => { |
||||
if (visible) { |
||||
setTimeout(() => searchInput.current?.select(), 100); |
||||
} |
||||
}, |
||||
render: (text, record) => |
||||
searchedColumn === dataIndex ? ( |
||||
<Highlighter |
||||
highlightStyle={{ |
||||
backgroundColor: '#ffc069', |
||||
padding: 0, |
||||
}} |
||||
searchWords={[searchText]} |
||||
autoEscape |
||||
textToHighlight={text ? text.toString() : ''} |
||||
/> |
||||
) : ( |
||||
<> |
||||
<Link to={`/dashboard-project/${record.id}/${record.lastGanttId}`}> |
||||
{record.kode_sortname} <br /> {text} |
||||
</Link> |
||||
</> |
||||
), |
||||
}); |
||||
|
||||
|
||||
const filterDate = (dataIndex) => ({ |
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => { |
||||
return ( |
||||
<div |
||||
style={{ |
||||
padding: 8, |
||||
}} |
||||
> |
||||
<InputNumber |
||||
ref={searchInput} |
||||
placeholder={`Year`} |
||||
value={selectedKeys[0]} |
||||
min={2020} |
||||
max={2050} |
||||
//onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onChange={(e) => console.log(e)} |
||||
style={{ |
||||
marginBottom: 8, |
||||
display: 'block', |
||||
}} |
||||
/> |
||||
<Space> |
||||
<Button |
||||
type="primary" |
||||
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)} |
||||
icon={<SearchOutlined />} |
||||
size="small" |
||||
style={{ |
||||
width: 90, |
||||
}} |
||||
> |
||||
Search |
||||
</Button> |
||||
<Button |
||||
onClick={() => clearFilters && handleReset(clearFilters)} |
||||
size="small" |
||||
style={{ |
||||
width: 90, |
||||
}} |
||||
> |
||||
Reset |
||||
</Button> |
||||
<Button |
||||
type="link" |
||||
size="small" |
||||
onClick={() => { |
||||
confirm({ |
||||
closeDropdown: false, |
||||
}); |
||||
setSearchText(selectedKeys[0]); |
||||
setSearchedColumn(dataIndex); |
||||
}} |
||||
> |
||||
</Button> |
||||
</Space> |
||||
</div> |
||||
)}, |
||||
onFilter: (value, record) => { |
||||
return record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) |
||||
}, |
||||
onFilterDropdownVisibleChange: (visible) => { |
||||
if (visible) { |
||||
setTimeout(() => searchInput.current?.select(), 100); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
const getProjects = async () => { |
||||
const URL = `${BASE_OSPRO}/api/project/list` |
||||
const result = await axios.get(URL, HEADER).then(res => res).catch(err => err.response) |
||||
|
||||
if (result.data.code == 200) { |
||||
setDataTable(result.data.data); |
||||
result.data.manpowers != undefined ? SET_MANPOWERS(result.data.manpowers) : SET_MANPOWERS(0) |
||||
SET_MAXPLANNEDCOST(Math.max(...result.data.data.map(o => o.plannedCost))) |
||||
SET_MAXACTUALCOST(Math.max(...result.data.data.map(o => o.actualCost))) |
||||
SET_MAXCOSTVARIANCE(Math.max(...result.data.data.map(o => o.costVariance))) |
||||
} |
||||
} |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: 'Project', |
||||
dataIndex: 'nama', |
||||
key: 'nama', |
||||
render: (text, record) => |
||||
<Link to={`/dashboard-project/${record.id}/${record.lastGanttId}`}> |
||||
{record.kode_sortname} <br /> {text} |
||||
</Link>, |
||||
...getColumnSearchProps('nama'), |
||||
}, |
||||
{ |
||||
title: 'Project Manager', |
||||
dataIndex: 'projectManager', |
||||
key: 'projectManager', |
||||
filters: _.uniqWith(filterData(dataTable)(i => i.projectManager), _.isEqual), |
||||
onFilter: (value, record) => record.projectManager.indexOf(value) === 0, |
||||
}, |
||||
{ |
||||
title: 'Planned Interval', |
||||
dataIndex: 'plannedInterval', |
||||
key: 'plannedInterval', |
||||
...filterDate('plannedInterval'), |
||||
}, |
||||
{ |
||||
title: 'Manpower', |
||||
dataIndex: 'manpower', |
||||
key: 'manpower', |
||||
render: (text) => { |
||||
return `${text}/${MANPOWERS}` |
||||
}, |
||||
...slider('manpower', MANPOWERS) |
||||
}, |
||||
{ |
||||
title: 'Budget Project', |
||||
dataIndex: 'plannedCost', |
||||
key: 'plannedCost', |
||||
render: (text) => <a>{formatRibuanDecimal(text)}</a>, |
||||
...slider('plannedCost', MAXPLANNEDCOST) |
||||
}, |
||||
{ |
||||
title: 'Actual Cost', |
||||
dataIndex: 'actualCost', |
||||
key: 'actualCost', |
||||
render: (text) => <a>{formatRibuanDecimal(text)}</a>, |
||||
...slider('actualCost', MAXACTUALCOST) |
||||
|
||||
}, |
||||
{ |
||||
title: 'Cost Variance', |
||||
dataIndex: 'costVariance', |
||||
key: 'costVariance', |
||||
render: (text) => <a>{formatRibuanDecimal(text)}</a>, |
||||
...slider('costVariance', MAXCOSTVARIANCE) |
||||
}, |
||||
{ |
||||
title: 'Cost Health', |
||||
dataIndex: 'costHealth', |
||||
key: 'costHealth', |
||||
render: (text) => { |
||||
if (text == "on-budget") { |
||||
return <Badge color="success">On Budget</Badge> |
||||
} else if (text == "warning") { |
||||
return <Badge color="warning">Warning</Badge> |
||||
} else { |
||||
return <Badge color="danger">Danger</Badge> |
||||
} |
||||
}, |
||||
filters: _.uniqWith(filterData(dataTable)(i => i.costHealth), _.isEqual), |
||||
onFilter: (value, record) => record.costHealth.indexOf(value) === 0, |
||||
}, |
||||
{ |
||||
title: 'Progress', |
||||
dataIndex: 'progress', |
||||
key: 'progress', |
||||
render: (text) => { |
||||
return <Badge color={parseInt(text) > 74 ? 'success' : text > 49 ? 'warning' : 'danger'}>{text}%</Badge> |
||||
}, |
||||
...slider('progress', 100) |
||||
}, |
||||
]; |
||||
|
||||
useEffect(() => { |
||||
getProjects(); |
||||
}, []) |
||||
|
||||
|
||||
return ( |
||||
<> |
||||
<Row gutter={[16, 16]}> |
||||
<Col span={24}> |
||||
<Table |
||||
columns={columns} |
||||
dataSource={dataTable} |
||||
/> |
||||
</Col> |
||||
</Row> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default TableDashboardV1; |
@ -1,49 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Bar } from 'react-chartjs-2'; |
||||
|
||||
const data = { |
||||
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], |
||||
datasets: [ |
||||
{ |
||||
label: '# of Votes', |
||||
data: [12, 19, 3, 5, 2, 3], |
||||
backgroundColor: [ |
||||
'rgba(255, 99, 132, 0.2)', |
||||
'rgba(54, 162, 235, 0.2)', |
||||
'rgba(255, 206, 86, 0.2)', |
||||
'rgba(75, 192, 192, 0.2)', |
||||
'rgba(153, 102, 255, 0.2)', |
||||
'rgba(255, 159, 64, 0.2)', |
||||
], |
||||
borderColor: [ |
||||
'rgba(255, 99, 132, 1)', |
||||
'rgba(54, 162, 235, 1)', |
||||
'rgba(255, 206, 86, 1)', |
||||
'rgba(75, 192, 192, 1)', |
||||
'rgba(153, 102, 255, 1)', |
||||
'rgba(255, 159, 64, 1)', |
||||
], |
||||
borderWidth: 1, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const options = { |
||||
scales: { |
||||
yAxes: [ |
||||
{ |
||||
ticks: { |
||||
beginAtZero: true, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
}; |
||||
|
||||
const BarChart = () => ( |
||||
<> |
||||
<Bar data={data} options={options} /> |
||||
</> |
||||
); |
||||
|
||||
export default BarChart; |
@ -1,128 +0,0 @@
|
||||
.number-asset { |
||||
font-size: 50px; |
||||
font-weight: bold; |
||||
text-align: center; |
||||
} |
||||
|
||||
.text-bold { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.view-rectangle { |
||||
display: flex; |
||||
flex-direction: row; |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.view1 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
margin-bottom: -20px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #008B8B; |
||||
} |
||||
|
||||
.view2 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #8B008B; |
||||
} |
||||
|
||||
.view3 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #7CFC00; |
||||
} |
||||
|
||||
.view4 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #FF0000; |
||||
} |
||||
|
||||
.view5 { |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
|
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
background-color: #4682B4; |
||||
} |
||||
|
||||
.number-style { |
||||
font-size: 30px; |
||||
color: black; |
||||
font-weight: bold; |
||||
margin-bottom:15px; |
||||
} |
||||
|
||||
.number-style1 { |
||||
font-size: 30px; |
||||
color: #FFFFFF; |
||||
font-weight: bold; |
||||
margin-bottom:15px; |
||||
} |
||||
|
||||
.daily-info-card { |
||||
min-width: 130px; |
||||
width: 220px; |
||||
height: 100px; |
||||
padding: 10px; |
||||
justify-content: center; |
||||
align-items: center; |
||||
text-align: center; |
||||
margin: 4px; |
||||
border-radius: 8px; |
||||
} |
||||
|
||||
|
||||
.dashboard-container { |
||||
overflow-x: auto; |
||||
} |
||||
|
||||
.maptable-window-button-container { |
||||
float: right; |
||||
right: 0px; |
||||
} |
||||
|
||||
.maptable-close, .maptable-maximize, .maptable-minimize { |
||||
cursor: pointer; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.maptable-header { |
||||
margin-bottom: -10px; |
||||
} |
||||
|
||||
.maptable-title { |
||||
font-size: 24px; |
||||
font-weight: 700; |
||||
text-align: left; |
||||
margin-left: 20px; |
||||
} |
||||
|
||||
.maptable-close:hover, .maptable-maximize:hover, .maptable-minimize:hover { |
||||
color: #20a8d8; |
||||
} |
@ -1,18 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Line } from 'react-chartjs-2'; |
||||
|
||||
const MultiAxisLine = ({ data, handleClickChart, optionsAdded }) => { |
||||
const options = { ...optionsAdded }; |
||||
|
||||
return ( |
||||
<> |
||||
<Line |
||||
data={data} |
||||
options={options} |
||||
{...handleClickChart && { getElementAtEvent: handleClickChart }} |
||||
/> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default MultiAxisLine; |
@ -1,23 +0,0 @@
|
||||
import React from 'react'; |
||||
import { Pie } from 'react-chartjs-2'; |
||||
|
||||
const options = { |
||||
responsive: true, |
||||
maintainAspectRatio: false, |
||||
title: { |
||||
display: true, |
||||
text: 'Status Proyek' |
||||
}, |
||||
legend: { |
||||
display: true, |
||||
position: 'bottom' |
||||
} |
||||
} |
||||
|
||||
const PieChart = ({data}) => ( |
||||
<> |
||||
<Pie data={data} options={options} /> |
||||
</> |
||||
); |
||||
|
||||
export default PieChart; |
@ -1,308 +0,0 @@
|
||||
import '../../../node_modules/react-grid-layout/css/styles.css'; |
||||
import '../../../node_modules/react-resizable/css/styles.css'; |
||||
import '../Map/CustomScroll.css'; |
||||
import './Dashboard.css'; |
||||
import { Chart } from 'chart.js'; |
||||
import { ChartDataLabels } from 'chartjs-plugin-datalabels'; |
||||
import ContentLoader from "react-content-loader" |
||||
import LineChart from './LineChart'; |
||||
import PieChart from './PieChart'; |
||||
import React, { useEffect, useState } from 'react'; |
||||
import SiopasMap from '../Map/Map_16.js'; |
||||
import axios from 'axios' |
||||
import moment from 'moment'; |
||||
import numeral from 'numeral'; |
||||
import { BASE_SIMPRO } from '../../const/ApiConst'; |
||||
import { DatePicker } from 'antd'; |
||||
import { NotificationContainer, NotificationManager } from 'react-notifications'; |
||||
import { PLANNING_REALISASI_SEARCH, PLANNING_SEARCH, PROYEK_SEARCH_DETAIL, PROYEK_EDIT } from '../../const/ApiConst.js'; |
||||
import { Pagination, Tooltip, Tree, List, Checkbox } from 'antd'; |
||||
import { getChildrenTree, formatRupiah, DATE_TIME_FORMAT } from '../../const/CustomFunc.js'; |
||||
import { projectTreeConst } from '../../const/LayerTreeConst.js'; |
||||
import { |
||||
Card, Modal, ModalHeader, ModalBody, ModalFooter, Button, |
||||
CardBody, CardHeader, Col, Dropdown, DropdownItem, DropdownMenu, DropdownToggle, Row |
||||
} from 'reactstrap'; |
||||
import { |
||||
DONE_COLOR, IZIN_COLOR, NOT_YET_COLOR, |
||||
PRESENT_COLOR, TOTAL_COLOR, |
||||
RED_COLOR, |
||||
ORANGE_COLOR, |
||||
GREEN_COLOR, |
||||
DARK_GREY_COLOR, |
||||
BLUE_COLOR, |
||||
PURPLE_COLOR |
||||
} from '../../const/AppConst.js'; |
||||
const id_org = window.localStorage.getItem('id_org'); |
||||
const roleName = window.localStorage.getItem('role_name'); |
||||
|
||||
Chart.plugins.register(ChartDataLabels); |
||||
const { RangePicker } = DatePicker; |
||||
|
||||
let menu = [ |
||||
{ |
||||
"id": 3, |
||||
"title": "PANIC BUTTON", |
||||
"key": "absent", |
||||
"color": IZIN_COLOR |
||||
}, |
||||
{ |
||||
"id": 4, |
||||
"title": "WASPANG ACTIVE", |
||||
"key": "karyawan telat", |
||||
"color": GREEN_COLOR |
||||
}, |
||||
{ |
||||
"id": 5, |
||||
"title": "WASPANG ABSENT", |
||||
"key": "karyawan tanpa keterangan", |
||||
"color": ORANGE_COLOR |
||||
} |
||||
] |
||||
|
||||
const defaultPersentaseProyek = { |
||||
labels: [], |
||||
datasets: [ |
||||
{ |
||||
label: 'Progress', |
||||
data: [], |
||||
fill: false, |
||||
backgroundColor: 'rgb(54, 162, 235)', |
||||
borderColor: 'rgba(54, 162, 235, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const defaultCostProyek = { |
||||
labels: [], |
||||
datasets: [ |
||||
{ |
||||
label: 'Perencanaan', |
||||
data: [], |
||||
fill: false, |
||||
backgroundColor: 'rgb(255, 99, 132)', |
||||
borderColor: 'rgba(255, 99, 132, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
{ |
||||
label: 'Realisasi', |
||||
data: [], |
||||
fill: false, |
||||
backgroundColor: 'rgb(54, 162, 235)', |
||||
borderColor: 'rgba(54, 162, 235, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const defaultStatusProyek = { |
||||
labels: ['Aman', 'Alert', 'Critical'], |
||||
datasets: [ |
||||
{ |
||||
label: '# of Votes', |
||||
data: [], |
||||
backgroundColor: [ |
||||
'rgba(54, 162, 235, 0.2)', |
||||
'rgba(255, 206, 86, 0.2)', |
||||
'rgba(255, 99, 132, 0.2)', |
||||
], |
||||
borderColor: [ |
||||
'rgba(54, 162, 235, 1)', |
||||
'rgba(255, 206, 86, 1)', |
||||
'rgba(255, 99, 132, 1)', |
||||
], |
||||
borderWidth: 1, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const DashboardSimpro = () => { |
||||
const token = localStorage.getItem("token") |
||||
const HEADER = { |
||||
headers: { |
||||
"Content-Type": "application/json", |
||||
"Authorization": `Bearer ${token}` |
||||
} |
||||
} |
||||
const [openPlanRealisasi, setopenPlanRealisasi] = useState(false); |
||||
const [openCostPlanRealisasi, setopenCostPlanRealisasi] = useState(false); |
||||
const [dataChart, setDataChart] = useState([]); |
||||
const [projectTree, setProjectTree] = useState([]); |
||||
const [allProyek, setAllProyek] = useState(true); |
||||
const [dataStatusProyek, setDataStatusProyek] = useState(null); |
||||
const [dataPersentaseProyek, setDataPersentaseProyek] = useState(null); |
||||
const [dataCostProyek, setdataCostProyek] = useState(null); |
||||
const [projectTreeVisible, setProjectTreeVisible] = useState(false); |
||||
const [checkedKeysProjectTree, setCheckedKeysProjectTree] = useState([]); |
||||
const [openModalTable, setOpenModalTable] = useState(false); |
||||
const [dataDashboard, setDataDashboard] = useState(null); |
||||
|
||||
const handleGetDataDashboard = async () => { |
||||
const URL = `${BASE_SIMPRO}/dashboard-proyek/search` |
||||
const payload = { |
||||
"columns": [ |
||||
{ "name": "created_at", "logic_operator": "range", "value": "2021-11-06 00:00:00", "value1": "2021-11-06 23:59:59", "operator": "AND" } |
||||
], |
||||
"paging": { "start": 0, "length": -1 } |
||||
} |
||||
const result = await axios.post(URL, payload, HEADER).then(res => res).catch(err => err.response) |
||||
|
||||
if (result.data.code !== 200) { |
||||
NotificationManager.error('Gaga Menambah Data!!', 'Failed'); |
||||
} |
||||
|
||||
setDataDashboard(result.data.data); |
||||
} |
||||
|
||||
const handleGetDataDashboardChart = async () => { |
||||
const URL = `${BASE_SIMPRO}/dashboard-status/search` |
||||
let str = '' |
||||
|
||||
checkedKeysProjectTree.map((res, idx) => { |
||||
if (idx == 0) str += `${res}` |
||||
if (idx != 0) str += `,${res}` |
||||
}) |
||||
|
||||
const payload = { |
||||
"columns": [ |
||||
{ "name": "id", "logic_operator": "in", "value": str ? str : "0", "operator": "AND" } |
||||
], |
||||
"orders": { "columns": ["nama"], "ascending": true } |
||||
} |
||||
|
||||
const result = await axios.post(URL, payload, HEADER).then(res => res).catch(err => err.response) |
||||
|
||||
if (result.data.code !== 200) { |
||||
NotificationManager.error('Gaga Menambah Data!!', 'Failed'); |
||||
} |
||||
|
||||
const { persentase_progress, progress_cost_planning, progress_cost_realisasi, status_proyek } = result.data.data |
||||
const labelPersentaseProyek = persentase_progress ? persentase_progress.map(res => res.label) : [] |
||||
const valuePersentaseProyek = persentase_progress ? persentase_progress.map(res => res.total) : [] |
||||
const persentaseProyek = { |
||||
labels: labelPersentaseProyek, |
||||
datasets: [ |
||||
{ |
||||
label: 'Progress', |
||||
data: valuePersentaseProyek, |
||||
fill: false, |
||||
backgroundColor: 'rgb(54, 162, 235)', |
||||
borderColor: 'rgba(54, 162, 235, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
], |
||||
}; |
||||
setDataPersentaseProyek(persentaseProyek) |
||||
const labelCostPlaning = progress_cost_planning ? progress_cost_planning.map(res => res.label) : [] |
||||
const valueCostPlaning = progress_cost_planning ? progress_cost_planning.map(res => res.total) : [] |
||||
const valueCostRealisasi = progress_cost_realisasi ? progress_cost_realisasi.map(res => res.total) : [] |
||||
|
||||
const costProyek = { |
||||
labels: labelCostPlaning, |
||||
datasets: [ |
||||
{ |
||||
label: 'Perencanaan', |
||||
data: valueCostPlaning, |
||||
fill: false, |
||||
backgroundColor: 'rgb(255, 99, 132)', |
||||
borderColor: 'rgba(255, 99, 132, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
{ |
||||
label: 'Realisasi', |
||||
data: valueCostRealisasi, |
||||
fill: false, |
||||
backgroundColor: 'rgb(54, 162, 235)', |
||||
borderColor: 'rgba(54, 162, 235, 0.2)', |
||||
yAxisID: 'y-axis-1', |
||||
}, |
||||
], |
||||
}; |
||||
setdataCostProyek(costProyek) |
||||
|
||||
const valueStatusProyek = status_proyek ? status_proyek.map(res => res.total) : [] |
||||
|
||||
const statusProyek = { |
||||
labels: ['Aman', 'Alert', 'Critical'], |
||||
datasets: [ |
||||
{ |
||||
label: '# of Votes', |
||||
data: valueStatusProyek, |
||||
backgroundColor: [ |
||||
'rgba(54, 162, 235, 0.2)', |
||||
'rgba(255, 206, 86, 0.2)', |
||||
'rgba(255, 99, 132, 0.2)', |
||||
], |
||||
borderColor: [ |
||||
'rgba(54, 162, 235, 1)', |
||||
'rgba(255, 206, 86, 1)', |
||||
'rgba(255, 99, 132, 1)', |
||||
], |
||||
borderWidth: 1, |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
setDataStatusProyek(statusProyek) |
||||
|
||||
|
||||
} |
||||
|
||||
const getProyek = async () => { |
||||
const URL = `${BASE_SIMPRO}/proyek/list?start=0&length=-1&orderby=nama&asc=true` |
||||
const result = await axios.get(URL, HEADER).then(res => res).catch(err => err.response) |
||||
if (result.data.code !== 200) { |
||||
NotificationManager.error('Gaga Menambah Data!!', 'Failed'); |
||||
} |
||||
setProjectTree(result.data.data); |
||||
const arr = result.data.data.map(res => res.id) |
||||
setCheckedKeysProjectTree(arr) |
||||
} |
||||
|
||||
const renderDailyInfo = () => { |
||||
return ( |
||||
<> |
||||
<div className="daily-info-card" style={{ backgroundColor: IZIN_COLOR, cursor: 'pointer' }} > |
||||
<p className="number-style1">{dataDashboard ? dataDashboard.panic_button : 0}</p> |
||||
<p style={{ color: "#FFFFFF", fontWeight: "bold" }}>PANIC BUTTON</p> |
||||
</div> |
||||
<div className="daily-info-card" style={{ backgroundColor: GREEN_COLOR, cursor: 'pointer' }} > |
||||
<p className="number-style1">{dataDashboard ? dataDashboard.waspang_status.presensi : 0}</p> |
||||
<p style={{ color: "#FFFFFF", fontWeight: "bold" }}>WASPANG ACTIVE</p> |
||||
</div> |
||||
<div className="daily-info-card" style={{ backgroundColor: ORANGE_COLOR, cursor: 'pointer' }} > |
||||
<p className="number-style1">{dataDashboard ? dataDashboard.waspang_status.absensi : 0}</p> |
||||
<p style={{ color: "#FFFFFF", fontWeight: "bold" }}>WASPANG ABSENT</p> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const handleClickChart = param => { |
||||
if (!param.length) return; |
||||
const { _datasetIndex, _index } = param[0]; |
||||
const data = dataPersentaseProyek.datasets[_datasetIndex].label |
||||
setOpenModalTable(true) |
||||
} |
||||
|
||||
const handleClickProyek = id => { |
||||
const arr = [...checkedKeysProjectTree] |
||||
const idx = arr.indexOf(id) |
||||
if (idx == -1) { |
||||
arr.push(id) |
||||
} else { |
||||
arr.splice(idx, 1) |
||||
} |
||||
setCheckedKeysProjectTree(arr) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<SiopasMap /> |
||||
</> |
||||
|
||||
); |
||||
} |
||||
|
||||
export default DashboardSimpro; |
Loading…
Reference in new issue