Yusuf
2 years ago
20 changed files with 1503 additions and 833 deletions
@ -1,102 +1,104 @@ |
|||||||
import React from 'react'; |
import React from 'react'; |
||||||
|
|
||||||
const Absensi = React.lazy(() => import('./views/Master/MasterAbsensi')); |
const Absensi = React.lazy(() => import('./views/Master/MasterAbsensi')); |
||||||
const BaseLayers = React.lazy(() => import('./views/BaseLayers')); |
const BaseLayers = React.lazy(() => import('./views/BaseLayers')); |
||||||
const Broadcast = React.lazy(() => import('./views/Master/MasterBroadcast')); |
const Broadcast = React.lazy(() => import('./views/Master/MasterBroadcast')); |
||||||
const ChecklistK3 = React.lazy(() => import('./views/SimproV2/ChecklistK3')); |
const ChecklistK3 = React.lazy(() => import('./views/SimproV2/ChecklistK3')); |
||||||
const Closing = React.lazy(() => import('./views/SimproV2/Closing')); |
const Closing = React.lazy(() => import('./views/SimproV2/Closing')); |
||||||
const ConfigAlert = React.lazy(() => import('./views/Master/ConfigAlert')); |
const ConfigAlert = React.lazy(() => import('./views/Master/ConfigAlert')); |
||||||
const ControlMonitoring = React.lazy(() => import('./views/Report/ControlMonitoring')); |
const ControlMonitoring = React.lazy(() => import('./views/Report/ControlMonitoring')); |
||||||
const ControlMonitoringGantt = React.lazy(() => import('./views/ControlMonitoringGantt')); |
const ControlMonitoringGantt = React.lazy(() => import('./views/ControlMonitoringGantt')); |
||||||
const CreatedProyek = React.lazy(() => import('./views/SimproV2/CreatedProyek')); |
const CreatedProyek = React.lazy(() => import('./views/SimproV2/CreatedProyek')); |
||||||
const DashboardSimpro = React.lazy(() => import('./views/DashboardSimpro')); |
const DashboardSimpro = React.lazy(() => import('./views/DashboardSimpro')); |
||||||
const DashboardPMO = React.lazy(() => import('./views/DashboardPMO')); |
const DashboardPMO = React.lazy(() => import('./views/DashboardPMO')); |
||||||
const Divisi = React.lazy(() => import('./views/SimproV2/Divisi')); |
const Divisi = React.lazy(() => import('./views/SimproV2/Divisi')); |
||||||
const DivisiKaryawan = React.lazy(() => import('./views/Master/MasterTipeKaryawan')); |
const DivisiKaryawan = React.lazy(() => import('./views/Master/MasterTipeKaryawan')); |
||||||
const Gantt = React.lazy(() => import('./views/SimproV2/Gantt')); |
const Gantt = React.lazy(() => import('./views/SimproV2/Gantt')); |
||||||
const Izin = React.lazy(() => import('./views/Master/MasterCuti')); |
const Izin = React.lazy(() => import('./views/Master/MasterCuti')); |
||||||
const K3 = React.lazy(() => import('./views/Report/k3')); |
const K3 = React.lazy(() => import('./views/Report/k3')); |
||||||
const LaporanAlert = React.lazy(() => import('./views/Report/alert')); |
const LaporanAlert = React.lazy(() => import('./views/Report/alert')); |
||||||
const LaporanTugas = React.lazy(() => import('./views/Master/MasterTask')); |
const LaporanTugas = React.lazy(() => import('./views/Master/MasterTask')); |
||||||
const Layer = React.lazy(() => import('./views/Layers/Layer')); |
const Layer = React.lazy(() => import('./views/Layers/Layer')); |
||||||
const Layers = React.lazy(() => import('./views/Layers/Layers')); |
const Layers = React.lazy(() => import('./views/Layers/Layers')); |
||||||
const Lembur = React.lazy(() => import('./views/Master/MasterLembur')); |
const Lembur = React.lazy(() => import('./views/Master/MasterLembur')); |
||||||
const MapConfig = React.lazy(() => import('./views/MapConfig')); |
const MapConfig = React.lazy(() => import('./views/MapConfig')); |
||||||
const Menu = React.lazy(() => import('./views/Master/MasterMenu')); |
const Menu = React.lazy(() => import('./views/Master/MasterMenu')); |
||||||
const OfficeHours = React.lazy(() => import('./views/Master/MasterOfficeHours')); |
const OfficeHours = React.lazy(() => import('./views/Master/MasterOfficeHours')); |
||||||
const Organization = React.lazy(() => import('./views/Master/MasterOrganization')); |
const Organization = React.lazy(() => import('./views/Master/MasterOrganization')); |
||||||
const PanicButton = React.lazy(() => import('./views/SimproV2/PanicButton')); |
const PanicButton = React.lazy(() => import('./views/SimproV2/PanicButton')); |
||||||
const PlanningHarian = React.lazy(() => import('./views/SimproV2/PlanningHarian')); |
const PlanningHarian = React.lazy(() => import('./views/SimproV2/PlanningHarian')); |
||||||
const Presensi = React.lazy(() => import('./views/SimproV2/Presence')); |
const Presensi = React.lazy(() => import('./views/SimproV2/Presence')); |
||||||
const ProjectRole = React.lazy(() => import('./views/Master/RoleProject')); |
const ProjectRole = React.lazy(() => import('./views/Master/RoleProject')); |
||||||
const ProjectType = React.lazy(() => import('./views/SimproV2/ProjectType')); |
const ProjectType = React.lazy(() => import('./views/SimproV2/ProjectType')); |
||||||
const Proyek = React.lazy(() => import('./views/Master/Proyek')); |
const ProjectPhase = React.lazy(() => import('./views/Master/ProjectPhase')); |
||||||
const RateCost = React.lazy(() => import('./views/SimproV2/RateCost')); |
const Proyek = React.lazy(() => import('./views/Master/Proyek')); |
||||||
const ResourceMaterial = React.lazy(() => import('./views/SimproV2/ResourceMaterial')); |
const RateCost = React.lazy(() => import('./views/SimproV2/RateCost')); |
||||||
const ResourceTools = React.lazy(() => import('./views/SimproV2/ResourceTools')); |
const ResourceMaterial = React.lazy(() => import('./views/SimproV2/ResourceMaterial')); |
||||||
const ResourceWorker = React.lazy(() => import('./views/SimproV2/ResourceWorker')); |
const ResourceTools = React.lazy(() => import('./views/SimproV2/ResourceTools')); |
||||||
const Roles = React.lazy(() => import('./views/Master/MasterRoles')); |
const ResourceWorker = React.lazy(() => import('./views/SimproV2/ResourceWorker')); |
||||||
const Satuan = React.lazy(() => import('./views/SimproV2/Satuan')); |
const Roles = React.lazy(() => import('./views/Master/MasterRoles')); |
||||||
const ScheduleShift = React.lazy(() => import('./views/SimproV2/ScheduleShift')); |
const Satuan = React.lazy(() => import('./views/SimproV2/Satuan')); |
||||||
const Shift = React.lazy(() => import('./views/SimproV2/Shift')); |
const ScheduleShift = React.lazy(() => import('./views/SimproV2/ScheduleShift')); |
||||||
const TestGantt = React.lazy(() => import('./views/testgantt')); |
const Shift = React.lazy(() => import('./views/SimproV2/Shift')); |
||||||
const UserAdmin = React.lazy(() => import('./views/Master/UserAdmin')); |
const TestGantt = React.lazy(() => import('./views/testgantt')); |
||||||
const UserShift = React.lazy(() => import('./views/SimproV2/UserShift')); |
const UserAdmin = React.lazy(() => import('./views/Master/UserAdmin')); |
||||||
const DashboardProject = React.lazy(() => import('./views/DashboardProject')); |
const UserShift = React.lazy(() => import('./views/SimproV2/UserShift')); |
||||||
const DashboardPMOV1 = React.lazy(() => import('./views/DashboardPMOV1')); |
const DashboardProject = React.lazy(() => import('./views/DashboardProject')); |
||||||
|
const DashboardPMOV1 = React.lazy(() => import('./views/DashboardPMOV1')); |
||||||
const routes = [ |
|
||||||
{ path: '/', exact: true, name: 'Home' }, |
const routes = [ |
||||||
{ path: '/dashboardold', name: 'Dashboard', component: DashboardSimpro }, |
{ path: '/', exact: true, name: 'Home' }, |
||||||
{ path: '/dashboard', name: 'Dashboard', component: DashboardPMOV1 }, |
{ path: '/dashboardold', name: 'Dashboard', component: DashboardSimpro }, |
||||||
|
{ path: '/dashboard', name: 'Dashboard', component: DashboardPMOV1 }, |
||||||
{ path: '/projects', exact: true, name: 'Projects', component: CreatedProyek }, |
|
||||||
{ path: '/projects/:id/:project/gantt', exact: true, name: 'Gantt', component: Gantt }, |
{ path: '/projects', exact: true, name: 'Projects', component: CreatedProyek }, |
||||||
{ path: '/human-resource', exact: true, name: 'Human Resource', component: ResourceWorker }, |
{ path: '/projects/:id/:project/gantt', exact: true, name: 'Gantt', component: Gantt }, |
||||||
{ path: '/material-resource', exact: true, name: 'Material Resource', component: ResourceMaterial }, |
{ path: '/human-resource', exact: true, name: 'Human Resource', component: ResourceWorker }, |
||||||
{ path: '/tools-resource', exact: true, name: 'Tools Resource', component: ResourceTools }, |
{ path: '/material-resource', exact: true, name: 'Material Resource', component: ResourceMaterial }, |
||||||
|
{ path: '/tools-resource', exact: true, name: 'Tools Resource', component: ResourceTools }, |
||||||
{ path: '/control-monitoring', exact: true, name: 'Control Monitoring', component: ControlMonitoring }, |
|
||||||
{ path: '/control-monitoring-gantt', exact: true, name: 'Control Monitoring Gantt', component: ControlMonitoringGantt }, |
{ path: '/control-monitoring', exact: true, name: 'Control Monitoring', component: ControlMonitoring }, |
||||||
{ path: '/presensi-resource', exact: true, name: 'Presensi Resource', component: Presensi }, |
{ path: '/control-monitoring-gantt', exact: true, name: 'Control Monitoring Gantt', component: ControlMonitoringGantt }, |
||||||
{ path: '/absensi-resource', exact: true, name: 'Absensi Resource', component: Absensi }, |
{ path: '/presensi-resource', exact: true, name: 'Presensi Resource', component: Presensi }, |
||||||
{ path: '/laporan-k3', exact: true, name: 'Laporan K3', component: K3 }, |
{ path: '/absensi-resource', exact: true, name: 'Absensi Resource', component: Absensi }, |
||||||
{ path: '/broadcast', exact: true, name: 'Broadcast', component: Broadcast }, |
{ path: '/laporan-k3', exact: true, name: 'Laporan K3', component: K3 }, |
||||||
{ path: '/panic-button', exact: true, name: 'Tombol Darurat', component: PanicButton }, |
{ path: '/broadcast', exact: true, name: 'Broadcast', component: Broadcast }, |
||||||
|
{ path: '/panic-button', exact: true, name: 'Tombol Darurat', component: PanicButton }, |
||||||
{ path: '/closing', exact: true, name: 'Closing', component: Closing }, |
|
||||||
|
{ path: '/closing', exact: true, name: 'Closing', component: Closing }, |
||||||
{ path: '/menu', exact: true, name: 'Menu', component: Menu }, |
|
||||||
{ path: '/roles', exact: true, name: 'Roles', component: Roles }, |
{ path: '/menu', exact: true, name: 'Menu', component: Menu }, |
||||||
{ path: '/project-role', exact: true, name: 'Project Role', component: ProjectRole }, |
{ path: '/roles', exact: true, name: 'Roles', component: Roles }, |
||||||
{ path: '/project-type', exact: true, name: 'Project Type', component: ProjectType }, |
{ path: '/project-role', exact: true, name: 'Project Role', component: ProjectRole }, |
||||||
{ path: '/divisi', exact: true, name: 'Divisi', component: Divisi }, |
{ path: '/project-type', exact: true, name: 'Project Type', component: ProjectType }, |
||||||
{ path: '/satuan', exact: true, name: 'Satuan', component: Satuan }, |
{ path: '/project-phase', exact: true, name: 'Project Phase', component: ProjectPhase }, |
||||||
{ path: '/config-alert', exact: true, name: 'Config Alert', component: ConfigAlert }, |
{ path: '/divisi', exact: true, name: 'Divisi', component: Divisi }, |
||||||
|
{ path: '/satuan', exact: true, name: 'Satuan', component: Satuan }, |
||||||
{ path: '/checklist-k3', exact: true, name: 'Checklist K3', component: ChecklistK3 }, |
{ path: '/config-alert', exact: true, name: 'Config Alert', component: ConfigAlert }, |
||||||
{ path: '/absensi', exact: true, name: 'Absensi', component: Absensi }, |
|
||||||
{ path: '/divisi-karyawan', exact: true, name: 'Divisi Karyawan', component: DivisiKaryawan }, |
{ path: '/checklist-k3', exact: true, name: 'Checklist K3', component: ChecklistK3 }, |
||||||
{ path: '/izin', exact: true, name: 'Izin', component: Izin }, |
{ path: '/absensi', exact: true, name: 'Absensi', component: Absensi }, |
||||||
{ path: '/laporan-alert', exact: true, name: 'Laporan Alert', component: LaporanAlert }, |
{ path: '/divisi-karyawan', exact: true, name: 'Divisi Karyawan', component: DivisiKaryawan }, |
||||||
{ path: '/laporan-tugas-karyawan', exact: true, name: 'Laporan Tugas Karyawan', component: LaporanTugas }, |
{ path: '/izin', exact: true, name: 'Izin', component: Izin }, |
||||||
{ path: '/lembur', exact: true, name: 'Lembur', component: Lembur }, |
{ path: '/laporan-alert', exact: true, name: 'Laporan Alert', component: LaporanAlert }, |
||||||
{ path: '/map/baselayers', exact: true, name: 'Base Layers', component: BaseLayers }, |
{ path: '/laporan-tugas-karyawan', exact: true, name: 'Laporan Tugas Karyawan', component: LaporanTugas }, |
||||||
{ path: '/map/config', exact: true, name: 'Config', component: MapConfig }, |
{ path: '/lembur', exact: true, name: 'Lembur', component: Lembur }, |
||||||
{ path: '/map/layers', exact: true, name: 'Layers', component: Layers }, |
{ path: '/map/baselayers', exact: true, name: 'Base Layers', component: BaseLayers }, |
||||||
{ path: '/map/layers/:id', exact: true, name: 'Layer Details', component: Layer }, |
{ path: '/map/config', exact: true, name: 'Config', component: MapConfig }, |
||||||
{ path: '/office-hours', exact: true, name: 'Jam Kerja', component: OfficeHours }, |
{ path: '/map/layers', exact: true, name: 'Layers', component: Layers }, |
||||||
{ path: '/organization', exact: true, name: 'Organisasi', component: Organization }, |
{ path: '/map/layers/:id', exact: true, name: 'Layer Details', component: Layer }, |
||||||
{ path: '/planning-harian', exact: true, name: 'Planning Harian', component: PlanningHarian }, |
{ path: '/office-hours', exact: true, name: 'Jam Kerja', component: OfficeHours }, |
||||||
{ path: '/presensi', exact: true, name: 'Presensi', component: Presensi }, |
{ path: '/organization', exact: true, name: 'Organisasi', component: Organization }, |
||||||
{ path: '/proyek', exact: true, name: 'Created Project', component: Proyek }, |
{ path: '/planning-harian', exact: true, name: 'Planning Harian', component: PlanningHarian }, |
||||||
{ path: '/proyek-gantt', exact: true, name: 'Gantt Chart Proyek', component: TestGantt }, |
{ path: '/presensi', exact: true, name: 'Presensi', component: Presensi }, |
||||||
{ path: '/rate-cost', exact: true, name: 'Rate Cost', component: RateCost }, |
{ path: '/proyek', exact: true, name: 'Created Project', component: Proyek }, |
||||||
{ path: '/schedule-shift', exact: true, name: 'Schedule Shift', component: ScheduleShift }, |
{ path: '/proyek-gantt', exact: true, name: 'Gantt Chart Proyek', component: TestGantt }, |
||||||
{ path: '/user-admin', exact: true, name: 'User Admin', component: UserAdmin }, |
{ path: '/rate-cost', exact: true, name: 'Rate Cost', component: RateCost }, |
||||||
{ path: '/user-shift', exact: true, name: 'Shift', component: UserShift }, |
{ path: '/schedule-shift', exact: true, name: 'Schedule Shift', component: ScheduleShift }, |
||||||
{ path: '/working-hour', exact: true, name: 'Working Hour', component: Shift }, |
{ path: '/user-admin', exact: true, name: 'User Admin', component: UserAdmin }, |
||||||
{ path: '/dashboard-project/:ID/:GANTTID', exact: true, name: 'Dashboard Project', component: DashboardProject }, |
{ path: '/user-shift', exact: true, name: 'Shift', component: UserShift }, |
||||||
]; |
{ path: '/working-hour', exact: true, name: 'Working Hour', component: Shift }, |
||||||
|
{ path: '/dashboard-project/:ID/:GANTTID', exact: true, name: 'Dashboard Project', component: DashboardProject }, |
||||||
export default routes; |
]; |
||||||
|
|
||||||
|
export default routes; |
||||||
|
@ -1,114 +0,0 @@ |
|||||||
import '../../../node_modules/react-grid-layout/css/styles.css'; |
|
||||||
import '../../../node_modules/react-resizable/css/styles.css'; |
|
||||||
import './Dashboard.css'; |
|
||||||
import PieChart from './PieChart'; |
|
||||||
import { BASE_OSPRO } from '../../const/ApiConst'; |
|
||||||
import ContentLoader from "react-content-loader" |
|
||||||
import React, { useEffect, useState } from 'react'; |
|
||||||
import axios from 'axios' |
|
||||||
import moment from 'moment'; |
|
||||||
import numeral from 'numeral'; |
|
||||||
import { Table, Tree, Row, Col, Space, Card} from 'antd'; |
|
||||||
import { Pie } from '@ant-design/plots'; |
|
||||||
import { NotificationContainer, NotificationManager } from 'react-notifications'; |
|
||||||
import { formatRibuanDecimal, DATE_TIME_FORMAT } from '../../const/CustomFunc.js'; |
|
||||||
import { Badge } from 'reactstrap'; |
|
||||||
|
|
||||||
const token = localStorage.getItem("token") |
|
||||||
const HEADER = { |
|
||||||
headers: { |
|
||||||
"Content-Type": "application/json", |
|
||||||
"Authorization": `Bearer ${token}`, |
|
||||||
"Access-Control-Allow-Origin": "*" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const Dashboard = () => { |
|
||||||
const token = localStorage.getItem("token") |
|
||||||
const HEADER = { |
|
||||||
headers: { |
|
||||||
"Content-Type": "application/json", |
|
||||||
"Authorization": `Bearer ${token}` |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const [dataTable, setDataTable] = useState([]) |
|
||||||
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) { |
|
||||||
NotificationManager.error('Belum ada data proyek!', 'Failed'); |
|
||||||
} |
|
||||||
console.log("res ", result.data.data) |
|
||||||
setDataTable(result.data.data); |
|
||||||
} |
|
||||||
|
|
||||||
const columns = [ |
|
||||||
{ |
|
||||||
title: 'Project', |
|
||||||
dataIndex: 'nama', |
|
||||||
key: 'nama', |
|
||||||
render: (text) => <a>{text}</a>, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'Planned Interval', |
|
||||||
dataIndex: 'plannedInterval', |
|
||||||
key: 'plannedInterval', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'Planned Cost', |
|
||||||
dataIndex: 'plannedCost', |
|
||||||
key: 'plannedCost', |
|
||||||
render: (text) => <a>{ formatRibuanDecimal(text) }</a>, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'Actual Cost', |
|
||||||
dataIndex: 'actualCost', |
|
||||||
key: 'actualCost', |
|
||||||
render: (text) => <a>{ formatRibuanDecimal(text) }</a>, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'Cost Variance', |
|
||||||
dataIndex: 'costVariance', |
|
||||||
key: 'costVariance', |
|
||||||
render: (text) => <a>{ formatRibuanDecimal(text) }</a>, |
|
||||||
}, |
|
||||||
{ |
|
||||||
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> |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: 'Progress', |
|
||||||
dataIndex: 'progress', |
|
||||||
key: 'progress', |
|
||||||
}, |
|
||||||
]; |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
getProjects(); |
|
||||||
}, []) |
|
||||||
|
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<Row gutter={[16, 16]}> |
|
||||||
<Col span={24}> |
|
||||||
<Table columns={columns} dataSource={dataTable} /> |
|
||||||
</Col> |
|
||||||
</Row> |
|
||||||
</> |
|
||||||
|
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
export default Dashboard; |
|
@ -1,199 +1,206 @@ |
|||||||
import React, {useEffect, useState} from 'react'; |
import React, { useEffect, useState } from 'react'; |
||||||
import { Row, Col, Select } from 'antd'; |
import { Row, Col, Select, Collapse } from 'antd'; |
||||||
import TableDashboard from './tableDashboard'; |
import { Column } from '@ant-design/plots'; |
||||||
import TableDashboardV1 from './tableDashboardv1'; |
import TableDashboardV1 from './tableDashboardv1'; |
||||||
import { BarChart, DoughnutChart } from './chartDashboard'; |
import { ProjectTypeChart } from './chartDashboard'; |
||||||
|
import { ProjectPhaseChart } from './projectPhaseChart'; |
||||||
import axios from 'axios' |
import axios from 'axios' |
||||||
import { NotificationContainer, NotificationManager } from 'react-notifications'; |
|
||||||
import { BASE_OSPRO } from '../../const/ApiConst'; |
import { BASE_OSPRO } from '../../const/ApiConst'; |
||||||
|
import { formatRibuanDecimal } from '../../const/CustomFunc.js'; |
||||||
|
|
||||||
|
|
||||||
|
const { Panel } = Collapse; |
||||||
|
|
||||||
function BoxDashboard({ value, title, secondaryTitle, icon, bgColor }) { |
function BoxDashboard({ value, title, secondaryTitle, icon, bgColor }) { |
||||||
return ( |
return ( |
||||||
<div style={{ backgroundColor: bgColor }} className='box-dashboard-pm'> |
<> |
||||||
<Row> |
<div style={{ backgroundColor: bgColor }} class='box-dashboard-pm'> |
||||||
<Col span={7}> |
<Row> |
||||||
<div className='box-icon'> |
<Col span={4}> |
||||||
{icon} |
<div class='box-icon'> |
||||||
</div> |
{icon} |
||||||
</Col> |
</div> |
||||||
<Col span={17}> |
</Col> |
||||||
<div className='box-content'> |
<Col span={20}> |
||||||
<div style={{ fontSize: '1.4rem' }} className='text-box'>{value}</div> |
<div class='box-content'> |
||||||
<div style={{ fontSize: '0.7rem' }} className='text-box'>{title.toUpperCase()}</div> |
<div style={{ fontSize: '1.0rem' }} class='text-box'>{value}</div> |
||||||
<div style={{ fontSize: '0.6rem' }} className='text-box-secondary'>{secondaryTitle && secondaryTitle.toUpperCase()}</div> |
<div style={{ fontSize: '0.7rem' }} class='text-box'>{title.toUpperCase()}</div> |
||||||
</div> |
<div style={{ fontSize: '0.6rem' }} class='text-box-secondary'>{secondaryTitle && secondaryTitle.toUpperCase()}</div> |
||||||
</Col> |
</div> |
||||||
</Row> |
</Col> |
||||||
</div> |
</Row> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// pardon this, i've no time
|
||||||
|
function BoxDashboardNoIcon({ value, title, secondaryTitle, bgColor }) { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div style={{ backgroundColor: bgColor }} class='box-dashboard-pm'> |
||||||
|
<Row> |
||||||
|
<Col span={24}> |
||||||
|
<div class='box-content'> |
||||||
|
<div style={{ fontSize: '1.4rem' }} class='text-box'>{value}</div> |
||||||
|
<div style={{ fontSize: '0.7rem' }} class='text-box'>{title.toUpperCase()}</div> |
||||||
|
<div style={{ fontSize: '0.6rem' }} class='text-box-secondary'>{secondaryTitle && secondaryTitle.toUpperCase()}</div> |
||||||
|
</div> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
</> |
||||||
) |
) |
||||||
} |
} |
||||||
|
|
||||||
const { Option } = Select; |
|
||||||
|
|
||||||
const DashbaoardPM = () => { |
const DashboardPM = () => { |
||||||
const [PROJECTCOUNT, SET_PROJECTCOUNT] = useState([]) |
const [PROJECTCOUNT, SET_PROJECTCOUNT] = useState(0) |
||||||
const [PROJECTBUDGETTOTAL, SET_PROJECTBUDGETTOTAL] = useState([]) |
const [PROJECTBUDGETTOTAL, SET_PROJECTBUDGETTOTAL] = useState(0) |
||||||
const [PROJECTMANPOWER, SET_PROJETMANPOWER] = useState([]) |
const [PROJECTMANPOWER, SET_PROJETMANPOWER] = useState(0) |
||||||
const [PROJECTACTUALCOSTTOTAL, SET_PROJECTACTUALCOSTTOTAL] = useState([]) |
const [PROJECTACTUALCOSTTOTAL, SET_PROJECTACTUALCOSTTOTAL] = useState(0) |
||||||
|
const [PROJECTCOSTTOTAL, SET_PROJECTCOSTTOTAL] = useState(0) |
||||||
|
const [TOTALREVENUE, SET_TOTALREVENUE] = useState(0) |
||||||
|
|
||||||
const token = localStorage.getItem("token") |
const token = localStorage.getItem("token") |
||||||
const HEADER = { |
const HEADER = { |
||||||
headers: { |
headers: { |
||||||
"Content-Type": "application/json", |
"Content-Type": "application/json", |
||||||
"Authorization": `Bearer ${token}` |
"Authorization": `Bearer ${token}` |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
const getProjects = async () => { |
const getProjectInfos = async () => { |
||||||
const URL = `${BASE_OSPRO}/api/project/list` |
const URL = `${BASE_OSPRO}/api/project/list` |
||||||
const result = await axios.get(URL, HEADER).then(res => res).catch(err => err.response) |
const result = await axios.get(URL, HEADER).then(res => res).catch(err => err.response) |
||||||
if (result.data.code !== 200) { |
|
||||||
NotificationManager.error('Belum ada data proyek!', 'Failed'); |
|
||||||
} |
|
||||||
console.log("res ", result.data.data) |
|
||||||
// setDataTable(result.data.data);
|
|
||||||
SET_PROJECTCOUNT(result.data.totalRecord) |
SET_PROJECTCOUNT(result.data.totalRecord) |
||||||
SET_PROJECTACTUALCOSTTOTAL(result.data.totalActualCost) |
result.data.totalActualCost != undefined ? SET_PROJECTCOSTTOTAL(result.data.totalActualCost) : SET_PROJECTCOSTTOTAL(0) |
||||||
SET_PROJECTBUDGETTOTAL(result.data.totalPlannedCost) |
result.data.totalPlannedCost != undefined ? SET_PROJECTBUDGETTOTAL(result.data.totalPlannedCost) : SET_PROJECTBUDGETTOTAL(0) |
||||||
|
result.data.totalRevenue != undefined ? SET_TOTALREVENUE(result.data.totalRevenue) : SET_TOTALREVENUE(0) |
||||||
} |
} |
||||||
|
|
||||||
useEffect(() => { |
useEffect(() => { |
||||||
getProjects(); |
getProjectInfos(); |
||||||
}, []) |
}, []) |
||||||
|
|
||||||
return ( |
return ( |
||||||
<div> |
<> |
||||||
<Row> |
<div> |
||||||
<Col span={4}><h6 style={{ textAlign: 'center' }}>PORTOFOLIO DASHBOARD</h6></Col> |
<Row |
||||||
{/* <Col span={5}> |
gutter={{ |
||||||
<Select |
xs: 8, |
||||||
style={{ width: '100%', padding: '0 5px' }} |
sm: 16, |
||||||
showSearch |
md: 24, |
||||||
placeholder="Department" |
lg: 32, |
||||||
optionFilterProp="children" |
}} |
||||||
onChange={onChange} |
> |
||||||
onSearch={onSearch} |
<Col span={6} style={{ margin: '' }}> |
||||||
filterOption={(input, option) => |
|
||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 |
|
||||||
} |
|
||||||
> |
|
||||||
<Option value="jack">Jack</Option> |
|
||||||
<Option value="lucy">Lucy</Option> |
|
||||||
<Option value="tom">Tom</Option> |
|
||||||
</Select> |
|
||||||
</Col> |
|
||||||
<Col span={5}> |
|
||||||
<Select |
|
||||||
style={{ width: '100%', padding: '0 5px' }} |
|
||||||
showSearch |
|
||||||
placeholder="Project Type" |
|
||||||
optionFilterProp="children" |
|
||||||
onChange={onChange} |
|
||||||
onSearch={onSearch} |
|
||||||
filterOption={(input, option) => |
|
||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 |
|
||||||
} |
|
||||||
> |
|
||||||
{['Manned guarding ', 'C&T', 'CMS ', 'ESS', 'RSO'].map(res => ( |
|
||||||
<Option value={res}>{res}</Option> |
|
||||||
))} |
|
||||||
|
|
||||||
</Select> |
|
||||||
</Col> |
|
||||||
<Col span={5}> |
|
||||||
<Select |
|
||||||
style={{ width: '100%', padding: '0 5px' }} |
|
||||||
showSearch |
|
||||||
placeholder="Project Owner" |
|
||||||
optionFilterProp="children" |
|
||||||
onChange={onChange} |
|
||||||
onSearch={onSearch} |
|
||||||
filterOption={(input, option) => |
|
||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 |
|
||||||
} |
|
||||||
> |
|
||||||
<Option value="jack">Jack</Option> |
|
||||||
<Option value="lucy">Lucy</Option> |
|
||||||
<Option value="tom">Tom</Option> |
|
||||||
</Select> |
|
||||||
</Col> |
|
||||||
<Col span={5}> |
|
||||||
<Select |
|
||||||
style={{ width: '100%', padding: '0 5px' }} |
|
||||||
showSearch |
|
||||||
placeholder="Governance Phase" |
|
||||||
optionFilterProp="children" |
|
||||||
onChange={onChange} |
|
||||||
onSearch={onSearch} |
|
||||||
filterOption={(input, option) => |
|
||||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 |
|
||||||
} |
|
||||||
> |
|
||||||
{['Initiation ', 'Excecution', 'Closing '].map(res => ( |
|
||||||
<Option value={res}>{res}</Option> |
|
||||||
))} |
|
||||||
</Select> |
|
||||||
</Col> */} |
|
||||||
</Row> |
|
||||||
|
|
||||||
<div style={{ paddingTop: 0 }}> |
|
||||||
<Row> |
|
||||||
<Col span={4}> |
|
||||||
<BoxDashboard |
|
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-assignment zmdi-hc-3x"></i>} |
|
||||||
value={PROJECTCOUNT} |
|
||||||
bgColor="teal" |
|
||||||
title="Project Count" /> |
|
||||||
{/* <BoxDashboard |
|
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-money-box zmdi-hc-3x"></i>} |
|
||||||
value="19 M" |
|
||||||
bgColor="#059669" |
|
||||||
title="Project Cost" |
|
||||||
secondaryTitle="(dollars)" /> */} |
|
||||||
<BoxDashboard |
<BoxDashboard |
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-money zmdi-hc-3x"></i>} |
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-money zmdi-hc-4x"></i>} |
||||||
value={`${PROJECTBUDGETTOTAL} M`} |
value={`${formatRibuanDecimal(PROJECTBUDGETTOTAL)}`} |
||||||
bgColor="#047857" |
bgColor="#077857" |
||||||
title="Budget Project Total"
|
title="Budget" |
||||||
// secondaryTitle="(dollars)"
|
/> |
||||||
/> |
</Col> |
||||||
|
<Col span={6} style={{ margin: '' }}> |
||||||
<BoxDashboard |
<BoxDashboard |
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-accounts-alt zmdi-hc-3x"></i>} |
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-money zmdi-hc-4x"></i>} |
||||||
value="2000" |
value={`${formatRibuanDecimal(PROJECTCOSTTOTAL)}`} |
||||||
bgColor="#0284c7" |
bgColor="#077857" |
||||||
title="Project Manpower" |
title="Spent on Projects" |
||||||
/> |
/> |
||||||
|
</Col> |
||||||
|
<Col span={6} style={{ margin: '' }}> |
||||||
<BoxDashboard |
<BoxDashboard |
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-account zmdi-hc-3x"></i>} |
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-money zmdi-hc-4x"></i>} |
||||||
value="540" |
value={`${formatRibuanDecimal(TOTALREVENUE)}`} |
||||||
bgColor="#0369a1" |
bgColor="#077857" |
||||||
title="Manpower Variance" |
title="Revenue" |
||||||
/> |
/> |
||||||
|
</Col> |
||||||
|
<Col span={6} style={{ margin: '' }}> |
||||||
<BoxDashboard |
<BoxDashboard |
||||||
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-alert-circle zmdi-hc-3x"></i>} |
icon={<i style={{ color: '#fff' }} class="zmdi zmdi-accounts-alt zmdi-hc-4x"></i>} |
||||||
value="80" |
value="-" |
||||||
bgColor="#b30000" |
bgColor="#0287c7" |
||||||
title="Active Car" /> |
title="Manpowers" |
||||||
|
/> |
||||||
</Col> |
</Col> |
||||||
<Col span={20}> |
</Row> |
||||||
<div style={{ padding: '20px 0 20px 5px' }}> |
<Row style={{ marginTop: '15px' }}> |
||||||
<Row> |
<Col span={24} style={{ margin: '0px 0px 0px 0px' }}> |
||||||
<Col span={12}> |
<Collapse> |
||||||
<BarChart /> |
<Panel key="1"> |
||||||
</Col> |
<Row style={{ margin: '15px 10px 0px 0px' }}> |
||||||
<Col span={12}> |
<Col span={8} > |
||||||
<DoughnutChart /> |
<Row |
||||||
</Col> |
gutter={{ |
||||||
</Row> |
xs: 8, |
||||||
</div> |
sm: 16, |
||||||
<div style={{ paddingLeft: 5, }}> |
md: 24, |
||||||
<TableDashboardV1 /> |
lg: 32, |
||||||
</div> |
}} |
||||||
|
> |
||||||
|
<Col span={12}> |
||||||
|
<BoxDashboardNoIcon |
||||||
|
value="-" |
||||||
|
bgColor="#077857" |
||||||
|
title="Active / Standby" |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
<Col span={12}> |
||||||
|
<BoxDashboardNoIcon |
||||||
|
value="-" |
||||||
|
bgColor="#077857" |
||||||
|
title="Need Maintenance" |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
<Row |
||||||
|
gutter={{ |
||||||
|
xs: 8, |
||||||
|
sm: 16, |
||||||
|
md: 24, |
||||||
|
lg: 32, |
||||||
|
}} |
||||||
|
> |
||||||
|
<Col span={12}> |
||||||
|
<BoxDashboardNoIcon |
||||||
|
bgColor="#077857" |
||||||
|
title="" |
||||||
|
value={<i style={{ color: '#fff' }} class="zmdi zmdi-truck zmdi-hc-2x"></i>} |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
<Col span={12}> |
||||||
|
<BoxDashboardNoIcon |
||||||
|
value="-" |
||||||
|
bgColor="#077857" |
||||||
|
title="On Maintenance" |
||||||
|
/> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</Col> |
||||||
|
<Col span={8}> |
||||||
|
<ProjectPhaseChart /> |
||||||
|
</Col> |
||||||
|
<Col span={8}> |
||||||
|
<ProjectTypeChart /> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</Panel> |
||||||
|
</Collapse> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
<Row style={{ marginTop: '15px' }}> |
||||||
|
<Col span={24}> |
||||||
|
<TableDashboardV1 /> |
||||||
</Col> |
</Col> |
||||||
</Row> |
</Row> |
||||||
</div> |
</div> |
||||||
</div> |
</> |
||||||
); |
); |
||||||
} |
} |
||||||
|
|
||||||
export default DashbaoardPM; |
export default DashboardPM; |
||||||
|
|
||||||
|
@ -0,0 +1,36 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Bar } from 'react-chartjs-2'; |
||||||
|
import { faker } from '@faker-js/faker'; |
||||||
|
|
||||||
|
const options = { |
||||||
|
indexAxis: 'y', |
||||||
|
responsive: true, |
||||||
|
}; |
||||||
|
|
||||||
|
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; |
||||||
|
|
||||||
|
export const data = { |
||||||
|
labels, |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
label: 'Dataset 1', |
||||||
|
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 })), |
||||||
|
borderColor: 'rgb(255, 99, 132)', |
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Dataset 2', |
||||||
|
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 })), |
||||||
|
borderColor: 'rgb(53, 162, 235)', |
||||||
|
backgroundColor: 'rgba(53, 162, 235, 0.5)', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export function ProjectPhaseChart() { |
||||||
|
return <Bar options={options} data={data} />; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,121 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' |
||||||
|
import {
|
||||||
|
Modal, ModalHeader, ModalBody, ModalFooter, |
||||||
|
Button, Form, FormGroup, Label, Input, Col, Row |
||||||
|
} from 'reactstrap'; |
||||||
|
import { DatePicker, Tooltip, Select } from 'antd'; |
||||||
|
import { formatRupiah, formatNumber } from '../../../const/CustomFunc' |
||||||
|
import moment from 'moment'; |
||||||
|
import 'antd/dist/antd.css'; |
||||||
|
const { Option } = Select |
||||||
|
|
||||||
|
const DialogForm = ({openDialog, closeDialog, toggleDialog, typeDialog, dataEdit}) => { |
||||||
|
const [id, setId] = useState(0)
|
||||||
|
const [projectType, setProjectType] = useState('') |
||||||
|
const [uom, setUom] = useState('') |
||||||
|
const [description, setDescription] = useState('') |
||||||
|
const [unitPrice, setUnitPrice] = useState() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=> { |
||||||
|
if(typeDialog==="Edit"){ |
||||||
|
console.log("cel data Edit", dataEdit) |
||||||
|
setId(dataEdit.id) |
||||||
|
setDescription(dataEdit.description) |
||||||
|
setUnitPrice(dataEdit.unit_price) |
||||||
|
setUom(dataEdit.uom) |
||||||
|
setProjectType(dataEdit.name) |
||||||
|
|
||||||
|
}else{ |
||||||
|
setId(0) |
||||||
|
} |
||||||
|
},[dataEdit,openDialog]) |
||||||
|
|
||||||
|
const handleSave = () => { |
||||||
|
let data = ''; |
||||||
|
if(typeDialog==="Save"){ |
||||||
|
data = { |
||||||
|
name: projectType, |
||||||
|
description |
||||||
|
} |
||||||
|
|
||||||
|
closeDialog('save', data); |
||||||
|
}else{ |
||||||
|
data = { |
||||||
|
id, |
||||||
|
name: projectType,
|
||||||
|
description |
||||||
|
} |
||||||
|
closeDialog('edit', data); |
||||||
|
} |
||||||
|
setId(0) |
||||||
|
setDescription('') |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const handleCancel = () => { |
||||||
|
closeDialog('cancel', 'none') |
||||||
|
setId(0) |
||||||
|
|
||||||
|
setDescription('') |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const renderForm = () => { |
||||||
|
return( |
||||||
|
<Form> |
||||||
|
<Row> |
||||||
|
<Col md={6}> |
||||||
|
<FormGroup> |
||||||
|
<Label className="capitalize">Project Type</Label> |
||||||
|
<Input type="text" value={projectType} onChange={(e)=> setProjectType(e.target.value)} placeholder={`Input material name...`}/> |
||||||
|
</FormGroup> |
||||||
|
</Col> |
||||||
|
<Col md={6}> |
||||||
|
<FormGroup> |
||||||
|
<Label className="capitalize">Description</Label> |
||||||
|
<Input row="4" type="textarea" value={description} onChange={(e)=> setDescription(e.target.value)} placeholder={`Description ...`} /> |
||||||
|
</FormGroup> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</Form> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<Modal size="lg" isOpen={openDialog} toggle={toggleDialog}> |
||||||
|
<ModalHeader className="capitalize" toggle={closeDialog}>{typeDialog=="Save" ? `Add` : "Edit"} Resource</ModalHeader> |
||||||
|
<ModalBody> |
||||||
|
{renderForm()} |
||||||
|
</ModalBody> |
||||||
|
<ModalFooter> |
||||||
|
<Button color="primary" onClick={() => handleSave()}>{typeDialog}</Button>{' '} |
||||||
|
<Button className="capitalize" color="secondary" onClick={() => handleCancel()}>Batal</Button> |
||||||
|
</ModalFooter> |
||||||
|
</Modal> |
||||||
|
|
||||||
|
{/* <DialogMap |
||||||
|
openDialog={openDialogMap} |
||||||
|
closeDialog={handleCloseDialogMap} |
||||||
|
toggleDialog={() => toggleMapDialog} |
||||||
|
dataEdit={dataEdit} |
||||||
|
workArea_={workArea} |
||||||
|
lat_={lat} |
||||||
|
lon_={lon} |
||||||
|
radius_={radius} |
||||||
|
/> */} |
||||||
|
</> |
||||||
|
) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default DialogForm; |
@ -0,0 +1,89 @@ |
|||||||
|
import React, { useEffect, useState } from 'react' |
||||||
|
import {
|
||||||
|
Modal, ModalHeader, ModalBody, ModalFooter, |
||||||
|
Button, Form, FormGroup, Label, Input, Col, Row |
||||||
|
} from 'reactstrap'; |
||||||
|
|
||||||
|
const DialogForm = ({openDialog, closeDialog, toggleDialog, typeDialog, dataEdit, idActivity, projectTypeId}) => { |
||||||
|
|
||||||
|
const [id, setId] = useState(0)
|
||||||
|
const [activity, setActivity] = useState('') |
||||||
|
|
||||||
|
useEffect(()=> { |
||||||
|
if(typeDialog==="edit"){ |
||||||
|
setId(dataEdit.id) |
||||||
|
setActivity(dataEdit.name_activity) |
||||||
|
|
||||||
|
}else{ |
||||||
|
setId(0) |
||||||
|
setActivity('') |
||||||
|
} |
||||||
|
},[dataEdit,openDialog]) |
||||||
|
|
||||||
|
const handleSave = () => { |
||||||
|
let data = ''; |
||||||
|
|
||||||
|
if(typeDialog==="add"){ |
||||||
|
data = { |
||||||
|
name_activity: activity, |
||||||
|
proyek_type_id:projectTypeId |
||||||
|
} |
||||||
|
|
||||||
|
if(idActivity && idActivity > 0){ |
||||||
|
data['parent_id'] = idActivity |
||||||
|
} |
||||||
|
console.log("cek cek cek") |
||||||
|
closeDialog('save', data); |
||||||
|
}else{ |
||||||
|
data = { |
||||||
|
id, |
||||||
|
name_activity: activity, |
||||||
|
proyek_type_id:projectTypeId |
||||||
|
} |
||||||
|
|
||||||
|
if(idActivity && idActivity > 0){ |
||||||
|
data['parent_id'] = idActivity |
||||||
|
} |
||||||
|
console.log("cek cek cek 2") |
||||||
|
closeDialog('edit', data); |
||||||
|
} |
||||||
|
setId(0) |
||||||
|
setActivity('') |
||||||
|
} |
||||||
|
|
||||||
|
const handleCancel = () => { |
||||||
|
closeDialog('cancel', 'none') |
||||||
|
setId(0) |
||||||
|
setActivity('') |
||||||
|
} |
||||||
|
|
||||||
|
const renderForm = () => { |
||||||
|
return( |
||||||
|
<Form> |
||||||
|
<FormGroup> |
||||||
|
<Label className="capitalize">Activity</Label> |
||||||
|
<Input row="4" type="textarea" value={activity} onChange={(e)=> setActivity(e.target.value)} placeholder={`Persiapan ...`} /> |
||||||
|
</FormGroup> |
||||||
|
</Form> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<Modal isOpen={openDialog} toggle={toggleDialog}> |
||||||
|
<ModalHeader className="capitalize" toggle={closeDialog}>{typeDialog=="add" ? `Add` : "Edit"} Activity</ModalHeader> |
||||||
|
<ModalBody> |
||||||
|
{renderForm()} |
||||||
|
</ModalBody> |
||||||
|
<ModalFooter> |
||||||
|
<Button className='capitalize' color="primary" onClick={() => handleSave()}>{typeDialog}</Button>{' '} |
||||||
|
<Button className="capitalize" color="secondary" onClick={() => handleCancel()}>Batal</Button> |
||||||
|
</ModalFooter> |
||||||
|
</Modal> |
||||||
|
</> |
||||||
|
) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default DialogForm; |
@ -0,0 +1,217 @@ |
|||||||
|
import React, { useEffect, useMemo, useState } from 'react' |
||||||
|
import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; |
||||||
|
import { Button } from 'reactstrap'; |
||||||
|
import { Table, Tooltip } from 'antd'; |
||||||
|
import { NotificationContainer, NotificationManager } from 'react-notifications'; |
||||||
|
import 'antd/dist/antd.css'; |
||||||
|
import SweetAlert from 'react-bootstrap-sweetalert'; |
||||||
|
import { TEMPLATE_GANTT_ADD, TEMPLATE_GANTT_DELETE, TEMPLATE_GANTT_EDIT, TEMPLATE_GANTT_TREE } from '../../../const/ApiConst'; |
||||||
|
import axios from "../../../const/interceptorApi" |
||||||
|
import DialogForm from './DialogFormInitial'; |
||||||
|
|
||||||
|
const DialogInitialGantt = ({ openDialog, closeDialog, toggleDialog, idTypeProject }) => { |
||||||
|
const token = window.localStorage.getItem('token'); |
||||||
|
const config = { |
||||||
|
headers: |
||||||
|
{ |
||||||
|
Authorization: `Bearer ${token}`, |
||||||
|
"Content-type": `application/json` |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const [dataTable, setDataTable] = useState([]) |
||||||
|
const [alertDelete, setAlertDelete] = useState(false) |
||||||
|
const [idDelete, setIdDelete] = useState(0) |
||||||
|
const [idActivity, setIdActivity] = useState(0) |
||||||
|
const [openDialogForm, setOpenDialogForm] = useState(false) |
||||||
|
const [typeDialog, setTypeDialog] = useState("add") |
||||||
|
const [dataEdit, setDataEdit] = useState([]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (idTypeProject && idTypeProject > 0) { |
||||||
|
getDataInitial(); |
||||||
|
} |
||||||
|
}, [idTypeProject]) |
||||||
|
|
||||||
|
const getDataInitial = async () => { |
||||||
|
const url = TEMPLATE_GANTT_TREE(idTypeProject) |
||||||
|
const result = await axios |
||||||
|
.get(url, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
if (result && result.data && result.data.code == 200) { |
||||||
|
setDataTable(result.data.data); |
||||||
|
} else { |
||||||
|
NotificationManager.error('Gagal mengambil data, Silahkan coba lagi!!', 'Failed'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleDelete = async (id) => { |
||||||
|
await setIdDelete(id) |
||||||
|
await setAlertDelete(true) |
||||||
|
} |
||||||
|
|
||||||
|
const onConfirmDelete = async () => { |
||||||
|
let url = TEMPLATE_GANTT_DELETE(idDelete); |
||||||
|
|
||||||
|
const result = await axios.delete(url, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataInitial() |
||||||
|
setIdDelete(0) |
||||||
|
setAlertDelete(false) |
||||||
|
NotificationManager.success(`Activity berhasil dihapus!`, 'Success!!'); |
||||||
|
} else { |
||||||
|
setIdDelete(0) |
||||||
|
setAlertDelete(false) |
||||||
|
NotificationManager.error(`Activity gagal dihapus!}`, 'Failed!!'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const cancelDelete = () => { |
||||||
|
setIdDelete(0) |
||||||
|
setAlertDelete(false) |
||||||
|
} |
||||||
|
|
||||||
|
const handleAdd = async () => { |
||||||
|
setIdActivity(0) |
||||||
|
await setTypeDialog("add") |
||||||
|
setOpenDialogForm(true) |
||||||
|
} |
||||||
|
|
||||||
|
const handleEdit = async (data) => { |
||||||
|
await setDataEdit(data) |
||||||
|
if (data.parent_id) { |
||||||
|
await setIdActivity(data.parent_id); |
||||||
|
} |
||||||
|
await setTypeDialog("edit") |
||||||
|
setOpenDialogForm(true) |
||||||
|
} |
||||||
|
|
||||||
|
const handleAddWithParent = async (id) => { |
||||||
|
setIdActivity(id) |
||||||
|
await setTypeDialog("add") |
||||||
|
setOpenDialogForm(true) |
||||||
|
} |
||||||
|
|
||||||
|
const closeDialogForm = (type, data) => { |
||||||
|
if (type == "save") { |
||||||
|
saveActivity(data) |
||||||
|
} else if (type == "edit") { |
||||||
|
updateActivity(data) |
||||||
|
} |
||||||
|
setIdActivity(0) |
||||||
|
setOpenDialogForm(false) |
||||||
|
} |
||||||
|
|
||||||
|
const toggleDialogForm = () => { |
||||||
|
if (openDialogForm) { |
||||||
|
setIdActivity(0) |
||||||
|
} |
||||||
|
setOpenDialogForm(!openDialogForm); |
||||||
|
} |
||||||
|
|
||||||
|
const saveActivity = async (data) => { |
||||||
|
const result = await axios.post(TEMPLATE_GANTT_ADD, data, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataInitial() |
||||||
|
NotificationManager.success(`Data activity berhasil ditambah`, 'Success!!'); |
||||||
|
} else { |
||||||
|
NotificationManager.error(`${result.data.message}`, 'Failed!!'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const updateActivity = async (data) => { |
||||||
|
const url = TEMPLATE_GANTT_EDIT(data.id) |
||||||
|
const result = await axios.put(url, data, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataInitial() |
||||||
|
NotificationManager.success(`Data activity berhasil diedit`, 'Success!!'); |
||||||
|
} else { |
||||||
|
NotificationManager.error(`${result.data.message}`, 'Failed!!'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const renderTable = useMemo(() => { |
||||||
|
const columns = [ |
||||||
|
{ |
||||||
|
title: 'Action', |
||||||
|
dataIndex: '', |
||||||
|
key: 'id', |
||||||
|
className: "nowrap", |
||||||
|
render: (text, record) => |
||||||
|
<> |
||||||
|
<Tooltip title="Delete Activity"> |
||||||
|
<Button size="small" size={"sm"} color='danger' onClick={() => handleDelete(text.id)}><i className="fa fa-trash"></i></Button> |
||||||
|
</Tooltip>{" "}<Tooltip title="Add Activity"> |
||||||
|
<Button size="small" size={"sm"} color='primary' onClick={() => handleAddWithParent(text.id)}><i className="fa fa-plus"></i></Button> |
||||||
|
</Tooltip>{" "}<Tooltip title="Edit Activity"> |
||||||
|
<Button size="small" size={"sm"} color='warning' onClick={() => handleEdit(text)}><i className="fa fa-edit"></i></Button> |
||||||
|
</Tooltip> |
||||||
|
</> |
||||||
|
, |
||||||
|
}, |
||||||
|
{ title: 'Nama Activity', dataIndex: 'name_activity', key: 'name_activity' }, |
||||||
|
]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Table |
||||||
|
size="small" |
||||||
|
columns={columns} |
||||||
|
dataSource={dataTable} |
||||||
|
pagination={{ position: ["bottomLeft"] }} |
||||||
|
/> |
||||||
|
) |
||||||
|
}, [dataTable]) |
||||||
|
|
||||||
|
|
||||||
|
return (<> |
||||||
|
<NotificationContainer /> |
||||||
|
<SweetAlert |
||||||
|
show={alertDelete} |
||||||
|
warning |
||||||
|
showCancel |
||||||
|
confirmBtnText="Delete" |
||||||
|
confirmBtnBsStyle="danger" |
||||||
|
title={`Are you sure?`} |
||||||
|
onConfirm={onConfirmDelete} |
||||||
|
onCancel={cancelDelete} |
||||||
|
focusCancelBtn |
||||||
|
> |
||||||
|
Delete this data |
||||||
|
</SweetAlert> |
||||||
|
<DialogForm |
||||||
|
openDialog={openDialogForm} |
||||||
|
closeDialog={closeDialogForm} |
||||||
|
toggleDialog={toggleDialogForm} |
||||||
|
idActivity={idActivity} |
||||||
|
typeDialog={typeDialog} |
||||||
|
dataEdit={dataEdit} |
||||||
|
projectTypeId={idTypeProject} |
||||||
|
/> |
||||||
|
<Modal size="lg" isOpen={openDialog} toggle={toggleDialog}> |
||||||
|
{/* <ModalHeader className="capitalize" toggle={closeDialog}>Initial Gantt</ModalHeader> */} |
||||||
|
<ModalHeader className="capitalize withBtn" toggle={closeDialog} style={{ width: "100%" }}> |
||||||
|
<div>Template Gantt</div> <Button onClick={handleAdd} size='sm' color="primary"><i className='fa fa-plus'></i></Button> |
||||||
|
</ModalHeader> |
||||||
|
<ModalBody> |
||||||
|
{renderTable} |
||||||
|
</ModalBody> |
||||||
|
<ModalFooter> |
||||||
|
<Button className="capitalize" color="secondary" onClick={closeDialog}>Close</Button> |
||||||
|
</ModalFooter> |
||||||
|
</Modal> |
||||||
|
</> |
||||||
|
) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default DialogInitialGantt; |
@ -0,0 +1,325 @@ |
|||||||
|
import * as XLSX from 'xlsx'; |
||||||
|
import DialogForm from './DialogForm'; |
||||||
|
import DialogInitialGantt from './DialogInitialGantt'; |
||||||
|
import React, { useState, useEffect, useMemo } from 'react'; |
||||||
|
import SweetAlert from 'react-bootstrap-sweetalert'; |
||||||
|
import axios from "../../../const/interceptorApi" |
||||||
|
import moment from 'moment' |
||||||
|
import { Card, CardBody, CardHeader, Col, Row, Input } from 'reactstrap'; |
||||||
|
import { NotificationContainer, NotificationManager } from 'react-notifications'; |
||||||
|
import { PROJECT_PHASE_ADD, PROJECT_PHASE_EDIT, PROJECT_PHASE_DELETE, PROJECT_PHASE_SEARCH } from '../../../const/ApiConst'; |
||||||
|
import { Pagination, Button, Tooltip, Table } from 'antd'; |
||||||
|
|
||||||
|
const token = window.localStorage.getItem('token'); |
||||||
|
const config = { |
||||||
|
headers: |
||||||
|
{ |
||||||
|
Authorization: `Bearer ${token}`, |
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", |
||||||
|
"Access-Control-Allow-Origin": `*` |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const ProjectPhase = ({ params }) => { |
||||||
|
const token = localStorage.getItem("token") |
||||||
|
const HEADER = { |
||||||
|
headers: { |
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", |
||||||
|
"Access-Control-Allow-Origin": "*", |
||||||
|
"Authorization": `Bearer ${token}` |
||||||
|
} |
||||||
|
} |
||||||
|
const pageName = params.name; |
||||||
|
|
||||||
|
const [alertDelete, setAlertDelete] = useState(false) |
||||||
|
const [allDataMenu, setAllDataMenu] = useState([]) |
||||||
|
const [clickOpenModal, setClickOpenModal] = useState(false) |
||||||
|
const [currentPage, setCurrentPage] = useState(1) |
||||||
|
const [dataEdit, setDataEdit] = useState([]) |
||||||
|
const [dataTable, setDatatable] = useState([]) |
||||||
|
const [idDelete, setIdDelete] = useState(0) |
||||||
|
const [idPhaseProject, setIdPhaseProject] = useState(0) |
||||||
|
const [openDialog, setOpenDialog] = useState(false) |
||||||
|
const [openDialogIG, setOpenDialogIG] = useState(false) |
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(10) |
||||||
|
const [search, setSearch] = useState('') |
||||||
|
const [totalPage, setTotalPage] = useState(0) |
||||||
|
const [typeDialog, setTypeDialog] = useState('Save') |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getDataProjectPhase() |
||||||
|
}, [currentPage, rowsPerPage, search]) |
||||||
|
|
||||||
|
const getDataProjectPhase = async () => { |
||||||
|
let start = 0; |
||||||
|
if (currentPage !== 1 && currentPage > 1) { |
||||||
|
start = (currentPage * rowsPerPage) - rowsPerPage |
||||||
|
} |
||||||
|
|
||||||
|
const payload = { |
||||||
|
"columns": [ |
||||||
|
{ |
||||||
|
"name": "name", |
||||||
|
"logic_operator": "like", |
||||||
|
"value": search, |
||||||
|
"operator": "AND" |
||||||
|
} |
||||||
|
], |
||||||
|
"orders": { |
||||||
|
"ascending": true, |
||||||
|
"columns": [ |
||||||
|
'id' |
||||||
|
] |
||||||
|
}, |
||||||
|
"paging": { |
||||||
|
"length": rowsPerPage, |
||||||
|
"start": start |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const result = await axios |
||||||
|
.post(PROJECT_PHASE_SEARCH, payload, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code == 200) { |
||||||
|
setDatatable(result.data.data); |
||||||
|
setTotalPage(result.data.totalRecord); |
||||||
|
} else { |
||||||
|
NotificationManager.error('Gagal Mengambil Data!!', 'Failed'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleSearch = e => { |
||||||
|
const value = e.target.value |
||||||
|
setSearch(value); |
||||||
|
setCurrentPage(1) |
||||||
|
}; |
||||||
|
|
||||||
|
const handleOpenDialog = (type) => { |
||||||
|
setOpenDialog(true) |
||||||
|
setTypeDialog(type) |
||||||
|
} |
||||||
|
|
||||||
|
const handleExportExcel = async () => { |
||||||
|
let start = 0; |
||||||
|
|
||||||
|
const payload = { |
||||||
|
"paging": { "start": start, "length": -1 }, |
||||||
|
"columns": [ |
||||||
|
{ "name": "name", "logic_operator": "ilike", "value": search, "operator": "AND" } |
||||||
|
], |
||||||
|
"joins": [], |
||||||
|
"orders": { "columns": ["id"], "ascending": false } |
||||||
|
} |
||||||
|
|
||||||
|
const result = await axios |
||||||
|
.post(PROJECT_PHASE_SEARCH, payload) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
} |
||||||
|
|
||||||
|
const handleEdit = (data) => { |
||||||
|
setDataEdit(data) |
||||||
|
handleOpenDialog('Edit'); |
||||||
|
} |
||||||
|
|
||||||
|
const handleDelete = async (id) => { |
||||||
|
await setAlertDelete(true) |
||||||
|
await setIdDelete(id) |
||||||
|
} |
||||||
|
|
||||||
|
const handleCloseDialog = (type, data) => { |
||||||
|
if (type === "save") { |
||||||
|
saveProjectPhase(data); |
||||||
|
} else if (type === "edit") { |
||||||
|
editMaterialR(data); |
||||||
|
} |
||||||
|
setDataEdit([]) |
||||||
|
setOpenDialog(false) |
||||||
|
} |
||||||
|
|
||||||
|
const saveProjectPhase = async (data) => { |
||||||
|
const formData = data |
||||||
|
const result = await axios.post(PROJECT_PHASE_ADD, formData, HEADER) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataProjectPhase() |
||||||
|
NotificationManager.success(`Data project type berhasil ditambah`, 'Success!!'); |
||||||
|
} else { |
||||||
|
NotificationManager.error(`${result.data.message}`, 'Failed!!'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const editMaterialR = async (data) => { |
||||||
|
let urlEdit = PROJECT_PHASE_EDIT(data.id) |
||||||
|
const formData = data |
||||||
|
|
||||||
|
const result = await axios.put(urlEdit, formData, HEADER) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataProjectPhase(); |
||||||
|
NotificationManager.success(`Data project phase berhasil diedit`, 'Success!!'); |
||||||
|
} else { |
||||||
|
NotificationManager.error(`Data project phase gagal di edit`, `Failed!!`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const toggleAddDialog = () => { |
||||||
|
setOpenDialog(!openDialog) |
||||||
|
} |
||||||
|
|
||||||
|
const handleDialogIg = (id) => { |
||||||
|
setIdPhaseProject(id) |
||||||
|
setOpenDialogIG(true) |
||||||
|
} |
||||||
|
|
||||||
|
const closeDialogIG = () => { |
||||||
|
setIdPhaseProject(0) |
||||||
|
setOpenDialogIG(false) |
||||||
|
} |
||||||
|
|
||||||
|
const toggleDialogIG = () => { |
||||||
|
if (openDialogIG) { |
||||||
|
setIdPhaseProject(0) |
||||||
|
} |
||||||
|
setOpenDialogIG(!openDialogIG); |
||||||
|
} |
||||||
|
|
||||||
|
const onConfirmDelete = async () => { |
||||||
|
let url = PROJECT_PHASE_DELETE(idDelete); |
||||||
|
|
||||||
|
const result = await axios.delete(url, config) |
||||||
|
.then(res => res) |
||||||
|
.catch((error) => error.response); |
||||||
|
|
||||||
|
if (result && result.data && result.data.code === 200) { |
||||||
|
getDataProjectPhase() |
||||||
|
setIdDelete(0) |
||||||
|
setAlertDelete(false) |
||||||
|
NotificationManager.success(`Data project phase berhasil dihapus!`, 'Success!!'); |
||||||
|
} else { |
||||||
|
setIdDelete(0) |
||||||
|
setAlertDelete(false) |
||||||
|
NotificationManager.error(`Data project phase gagal dihapus!}`, 'Failed!!'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const cancelDelete = () => { |
||||||
|
setAlertDelete(false) |
||||||
|
setIdDelete(0) |
||||||
|
} |
||||||
|
|
||||||
|
const onShowSizeChange = (current, pageSize) => { |
||||||
|
setRowsPerPage(pageSize) |
||||||
|
} |
||||||
|
|
||||||
|
const onPagination = (current, pageSize) => { |
||||||
|
setCurrentPage(current) |
||||||
|
} |
||||||
|
|
||||||
|
const dataNotAvailable = () => { |
||||||
|
if (dataTable.length === 0) { |
||||||
|
return ( |
||||||
|
<tr> |
||||||
|
<td align="center" colSpan="3">Tidak ada data project phase</td> |
||||||
|
</tr> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const renderTable = useMemo(() => { |
||||||
|
const columns = [ |
||||||
|
{ |
||||||
|
title: 'Action', |
||||||
|
dataIndex: '', |
||||||
|
key: 'x', |
||||||
|
className: 'nowrap', |
||||||
|
render: (text, record) => <> |
||||||
|
<Tooltip title="Delete"> |
||||||
|
<i className="fa fa-trash" style={{ color: 'red', marginRight: '10px', cursor: "pointer" }} onClick={() => handleDelete(text.id)}></i> |
||||||
|
</Tooltip> |
||||||
|
<Tooltip title="Edit"> |
||||||
|
<i className="fa fa-edit" style={{ color: 'green', cursor: "pointer" }} onClick={() => handleEdit(text)}></i> |
||||||
|
</Tooltip>{" "} |
||||||
|
</>, |
||||||
|
}, |
||||||
|
{ title: 'Fase', dataIndex: 'name', key: 'name', className: "nowrap" }, |
||||||
|
{ title: 'Warna', dataIndex: 'color', key: 'color' }, |
||||||
|
]; |
||||||
|
return ( |
||||||
|
<Table |
||||||
|
rowKey="id" |
||||||
|
size="small" |
||||||
|
columns={columns} |
||||||
|
dataSource={dataTable} |
||||||
|
pagination={false} |
||||||
|
/> |
||||||
|
) |
||||||
|
}, [dataTable]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<NotificationContainer /> |
||||||
|
<SweetAlert |
||||||
|
show={alertDelete} |
||||||
|
warning |
||||||
|
showCancel |
||||||
|
confirmBtnText="Delete" |
||||||
|
confirmBtnBsStyle="danger" |
||||||
|
title={`Are you sure?`} |
||||||
|
onConfirm={onConfirmDelete} |
||||||
|
onCancel={() => cancelDelete()} |
||||||
|
focusCancelBtn |
||||||
|
> |
||||||
|
Delete this data |
||||||
|
</SweetAlert> |
||||||
|
<DialogForm |
||||||
|
openDialog={openDialog} |
||||||
|
closeDialog={handleCloseDialog} |
||||||
|
toggleDialog={() => toggleAddDialog} |
||||||
|
typeDialog={typeDialog} |
||||||
|
dataEdit={dataEdit} |
||||||
|
clickOpenModal={clickOpenModal} |
||||||
|
dataParent={allDataMenu} |
||||||
|
/> |
||||||
|
<DialogInitialGantt |
||||||
|
closeDialog={closeDialogIG} |
||||||
|
openDialog={openDialogIG} |
||||||
|
toggleDialog={toggleDialogIG} |
||||||
|
idPhaseProject={idPhaseProject} |
||||||
|
/> |
||||||
|
<Card> |
||||||
|
<CardHeader style={{ display: "flex", justifyContent: "space-between" }}> |
||||||
|
<h4 className="capitalize">{pageName}</h4> |
||||||
|
<Row> |
||||||
|
<Col> |
||||||
|
<Tooltip title="Add Material Resource"> |
||||||
|
<Button style={{ background: "#4caf50", color: "#fff" }} onClick={() => handleOpenDialog('Save')}><i className="fa fa-plus"></i></Button> |
||||||
|
</Tooltip> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</CardHeader> |
||||||
|
<CardBody> |
||||||
|
{renderTable} |
||||||
|
<Pagination |
||||||
|
style={{ marginTop: "25px" }} |
||||||
|
showSizeChanger |
||||||
|
onShowSizeChange={onShowSizeChange} |
||||||
|
onChange={onPagination} |
||||||
|
defaultCurrent={currentPage} |
||||||
|
pageSize={rowsPerPage} |
||||||
|
total={totalPage} |
||||||
|
pageSizeOptions={["10", "15", "20", "25", "30", "35", "40"]} |
||||||
|
/> |
||||||
|
</CardBody> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default ProjectPhase; |
Loading…
Reference in new issue