diff --git a/Dockerfile b/Dockerfile index f6b8ea0..6894b88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,22 @@ -FROM node:12 +# Stage 1 +FROM node:14 as build-stage + WORKDIR /react-docker -COPY ./package*.json ./ -ENV PATH /app/node_modules/.bin:$PATH +COPY package.json . RUN npm install -RUN npm i react@16.14.0 -RUN npm i react-scripts -RUN npm audit fix +COPY . . + +#ARG REACT_APP_API_BASE_URL +#ENV REACT_APP_API_BASE_URL=$REACT_APP_API_BASE_URL -EXPOSE 8445 +RUN npm run build -# Comment as needed (Production / Dev) -# [PROD] Use for Production -# COPY . . # uncomment prod +# Stage 2 +#FROM nginx:1.17.0-alpine +FROM nginx:alpine -# [DEV] Live Reload -RUN mkdir -p /react-docker/src # comment for prod -RUN mkdir -p /react-docker/public # comment for prod +COPY --from=build-stage /react-docker/build /usr/share/nginx/html +COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 -CMD PORT=8445 npm start #--host 0.0.0.0 --port 3000 --disableHostCheck true +CMD nginx -g 'daemon off;' diff --git a/docker-compose.yml b/docker-compose.yml index b5ad061..b95feba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.3" services: frontend: - container_name: ADW-Frontend + container_name: ct-frontend tty: true build: context: . @@ -11,6 +11,12 @@ services: - ./src:/react-docker/src - ./public:/react-docker/public ports: - - 8445:8445 + - 8455:80 + networks: + - ospro_ct environment: - CHOKIDAR_USEPOLLING=true + +networks: + ospro_ct: + driver: bridge diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..40a878b --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,17 @@ +server { + + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } + +} diff --git a/src/assets/img/OSPRO.png b/src/assets/img/OSPRO.png new file mode 100644 index 0000000..2a73619 Binary files /dev/null and b/src/assets/img/OSPRO.png differ diff --git a/src/const/ApiConst.js b/src/const/ApiConst.js index 4241749..c5ba5c3 100644 --- a/src/const/ApiConst.js +++ b/src/const/ApiConst.js @@ -107,7 +107,7 @@ export const API_DAILY_INFO = `${BASE_URL_GEOHR_API2}/employee.php?act=get_daily export const API_DAILY_INFO_DETAIL = (start, length) => `${BASE_URL_GEOHR_API2}/employee.php?act=get_daily_detail&start=${start}&length=${length}&role_name=${roleName}`; -export const APP_MODE = "ADW"; // KIT / ADW / NAWAKARA / IU +export const APP_MODE = "ADW"; // ADW == Konstruksi export const BASE_SIMPRO = "https://oslog.id/simpro-api/v1"; export const BASE_SIMPRO_V2 = "https://oslog.id/simpro-api/v2"; export const BASE_SIMPRO_LUMENNIAGA = "https://ospro-api.odm-iu.com/api"; @@ -116,7 +116,7 @@ export const TOKEN_ADW = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIxMjAyIiwiZXhwIjoxNjkxODMwNDkzfQ.DvBQIOZsdFndWsliPCZT65Y6G5Xx4vWBKz8Rhe7rvRA"; // export let BASE_OSPRO = "https://ospro-api.ospro.id"; -export let BASE_OSPRO = "https://adw-api.ospro.id"; +export let BASE_OSPRO = "https://konstruksi-api.ospro.id"; // export let BASE_OSPRO = "http://localhost:8444/adw-backend"; // export let BASE_OSPRO = "http://103.73.125.81:8444"; // ip public adw export let BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`; diff --git a/src/views/Dashboard/DashboardCustomer.js b/src/views/Dashboard/DashboardCustomer.js index eef2fd6..10bed77 100644 --- a/src/views/Dashboard/DashboardCustomer.js +++ b/src/views/Dashboard/DashboardCustomer.js @@ -34,7 +34,7 @@ const DashboardCustomer = () => { } } const { PROJECT_ID, GANTT_ID, SCURVE } = useParams(); - const URL_GANTT = `https://adw-gantt.ospro.id/view-mode/index.html?base_url=${BASE_OSPRO}/api&gantt_id=${GANTT_ID}&proyek_id=${PROJECT_ID}&token=${token}&ro=1`; + const URL_GANTT = `https://konstruksi-gantt.ospro.id/view-mode/index.html?base_url=${BASE_OSPRO}/api&gantt_id=${GANTT_ID}&proyek_id=${PROJECT_ID}&token=${token}&ro=1`; const mapRef = useRef() const [projectName, setProjectName] = useState(""); const [projectManagerName, setProjectManagerName] = useState(''); diff --git a/src/views/Dashboard/DashboardProject.js b/src/views/Dashboard/DashboardProject.js index 4acf02a..79cb44d 100644 --- a/src/views/Dashboard/DashboardProject.js +++ b/src/views/Dashboard/DashboardProject.js @@ -73,7 +73,7 @@ const DashboardProject = () => { }, }; const { PROJECT_ID, GANTT_ID, SCURVE } = useParams(); - const URL_GANTT = `https://adw-gantt.ospro.id/view-mode/index.html?base_url=${BASE_OSPRO}/api&gantt_id=${GANTT_ID}&proyek_id=${PROJECT_ID}&token=${token}&ro=1`; + const URL_GANTT = `https://konstruksi-gantt.ospro.id/view-mode/index.html?base_url=${BASE_OSPRO}/api&gantt_id=${GANTT_ID}&proyek_id=${PROJECT_ID}&token=${token}&ro=1`; const mapRef = useRef(); const [projectName, setProjectName] = useState(""); const [projectManagerName, setProjectManagerName] = useState(""); diff --git a/src/views/Dashboard/DashboardProject.js.save b/src/views/Dashboard/DashboardProject.js.save new file mode 100644 index 0000000..f534d5f --- /dev/null +++ b/src/views/Dashboard/DashboardProject.js.save @@ -0,0 +1,1698 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import axios from "axios"; +import { Row, Col, Button, Input } from "antd"; +import { + CardDashboard, + CardExpenditure, + CardScheduleHealthPerDivision, +} from "../../components/CardDashboard/CardDashboard"; +import L from "leaflet"; +import { useParams } from "react-router-dom"; +import "../../assets/css/customscroll.css"; +import moment from "moment"; +import { renderFormatRupiah } from "../../const/CustomFunc"; +import { BASE_OSPRO } from "../../const/ApiConst"; +import { SendOutlined } from "@ant-design/icons"; +import { + NotificationContainer, + NotificationManager, +} from "react-notifications"; +import ContentLoader from "react-content-loader"; +import { + BehindTaskItem, + Comment, + HealthByBudget, + HealthBySchedule, + ListLoader, + PopupContent, + ProgressActualBar, + ProgressPlanningBar, + SingleTextLoader, +} from "./Components"; +import { Fab, Action } from "react-tiny-fab"; +import "react-tiny-fab/dist/styles.css"; +import { useHistory } from "react-router-dom"; + +const { TextArea } = Input; + +const styles = { + cardContainer: { + backgroundColor: "#F8F8F8", + margin: 2, + paddingLeft: 20, + paddingRight: 20, + paddingTop: 10, + }, + cardHeaderContainer: { + display: "flex", + flexDirection: "row", + marginBottom: 10, + }, + cardChartContainer: { + position: "relative", + height: "21vh", + margin: "auto", + paddingBottom: 10, + justifyContent: "center", + }, + cardTitle: { color: "#444444", fontSize: 16, fontWeight: "bold" }, + cardSubtitle: { color: "#888888", fontSize: 12 }, +}; + +const center = { + lat: -6.2, + lng: 106.816666, +}; + +const DashboardProject = () => { + const token = localStorage.getItem("token"); + const HEADER = { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }; + const { PROJECT_ID, GANTT_ID, SCURVE } = useParams(); + const URL_GANTT = `https://konstruksi-gantt.ospro.id17/04/2024/view-mode/index.html?base_url=${BASE_OSPRO}/api&gantt_id=${GANTT_ID}&proyek_id=${PROJECT_ID}&token=${token}&ro=1`; + const mapRef = useRef(); + const [projectName, setProjectName] = useState(""); + const [projectManagerName, setProjectManagerName] = useState(""); + const [customerName, setCustomerName] = useState(""); + const [plannedStart, setPlannedStart] = useState(null); + const [plannedFinish, setPlannedFinish] = useState(null); + const [plannedCost, setPlannedCost] = useState(null); + const [totalCost, setTotalCost] = useState(null); + const [actualStart, setActualStart] = useState(null); + const [actualFinish, setActualFinish] = useState(null); + const [estimatedFinish, setEstimatedFinish] = useState(null); + const [mymap, setMymap] = useState(null); + const [activeTabIdx, setActiveTabIdx] = useState(0); + const [activeTabCommentIdx, setActiveTabCommentIdx] = useState(0); + const [planningProgress, setPlanningProgress] = useState(0); + const [actualProgress, setActualProgress] = useState(0); + const [currentBudget, setCurrentBudget] = useState(null); + const [addCostToComplete, setAddCostToComplete] = useState(null); + const [actualToDate, setActualToDate] = useState(null); + const [estAtCompletion, setEstAtCompletion] = useState(null); + const [bcwp, setBcwp] = useState(null); + const [costDeviation, setCostDeviation] = useState(null); + const [remToComplete, setRemToComplete] = useState(0); + const [totalInvoice, setTotalInvoice] = useState(null); + const [cashIn, setCashIn] = useState(null); + const [outstandingBalance, setOutstandingBalance] = useState(null); + const [comment, setComment] = useState(""); + const [comments, setComments] = useState([]); + const [isReadyComments, setIsReadyComments] = useState(false); + const [isSendingComment, setIsSendingComment] = useState(false); + const [isReadyProjectDetail, setIsReadyProjectDetail] = useState(false); + const [isReadySCurve, setIsReadySCurve] = useState(false); + const [isReadyGantt, setIsReadyGantt] = useState(false); + const [isReadyOverdueActivities, setIsReadyOverdueActivities] = + useState(false); + const [isReadyIntegrationInvoice, setIsReadyIntegrationInvoice] = + useState(false); + const [isReadyReportDistribution, setIsReadyReportDistribution] = + useState(false); + const [overdueActivities, setOverdueActivities] = useState([]); + const [healthBySchedule, setHealthBySchedule] = useState("-"); + const [healthByBudget, setHealthByBudget] = useState("-"); + const [reportDistribution, setReportDistribution] = useState([]); + const [manPower, setManPower] = useState(0); + const [assignedHr, setAssignedHr] = useState([]); + const [assignedHrCount, setAssignedHrCount] = useState(0); + const [actualHrCount, setActualHrCount] = useState(0); + const [dataGantt, setDataGantt] = useState({}); + const [dataGanttParents, setDataGanttParents] = useState({}); + const [isReadyGanttParents, setIsReadyGanttParents] = useState(false); + const [calculationStatus, setCalculationStatus] = useState(false); + const [isHierarchy, setIsHierarchy] = useState(false); + let history = useHistory(); + + useEffect(() => { + getProjectDetail(); + getSCurve(); + getOverdueActivities(); + getReportDistribution(); + getComments(); + getGantt(); + getGanttParents(); + return () => { + console.log("unmount RenderMap"); + }; + }, []); + + useEffect(() => { + if (activeTabIdx === 1) { + initMap(); + } + }, [activeTabIdx]); + + useEffect(() => { + async function fetchData() { + await Promise.all([ + getManpower(), + getAssignedHR(), + ...(assignedHr.length > 0 ? [getActualHR()] : []), + ]); + } + fetchData(); + }, []); + + useEffect(() => { + let deviation = 0; + if (plannedCost && totalCost) { + deviation = plannedCost - totalCost; + } + setRemToComplete(deviation.toString()); + }, [plannedCost, totalCost]); + + const handleRedirect = () => { + history.push("/projects/" + GANTT_ID + "/" + PROJECT_ID + "/gantt"); + }; + + const getManpower = async () => { + const url = `${BASE_OSPRO}/api/project/manpower/${PROJECT_ID}`; + try { + const response = await axios.get(url, HEADER); + setManPower(response.data.totalRecord); + } catch (error) { + console.error("Failed to get manpower:", error); + } + }; + + const getGantt = async () => { + setIsReadyGantt(false); + const url = `${BASE_OSPRO}/api/version-gantt/edit/${GANTT_ID}`; + try { + const response = await axios.get(url, HEADER); + setDataGantt(response); + if (response.data.data.hierarchy_ftth_id) { + setIsHierarchy(true); + } + setIsReadyGantt(true); + } catch (error) { + console.error("Failed to get gantt data:", error); + setIsReadyGantt(true); + } + }; + + const getGanttParents = async () => { + setIsReadyGanttParents(false); + const url = `${BASE_OSPRO}/api/hierarchy-ftths/tree-gantt/${GANTT_ID}`; + try { + const response = await axios.get(url, HEADER); + setDataGanttParents(response); + setIsReadyGanttParents(true); + } catch (error) { + console.error("Failed to get gantt data:", error); + setIsReadyGanttParents(true); + } + }; + + const getAssignedHR = async () => { + const url = `${BASE_OSPRO}/api/project/manpower/assigned/${GANTT_ID}`; + try { + const response = await axios.get(url, HEADER); + const today = moment(); + const assignedList = response.data.data + .filter((item) => + today.isBetween(moment(item.start_date), moment(item.end_date)) + ) + .map((item) => item.user_id); + setAssignedHrCount(assignedList.length); + setAssignedHr(assignedList); + } catch (error) { + console.error("Failed to get assigned HR:", error); + } + }; + + const getActualHR = async () => { + const dateStart = moment().startOf("day").toDate(); + const dateEnd = moment().endOf("day").toDate(); + try { + const payload = { + paging: { start: 0, length: -1 }, + columns: [ + { + name: "name", + logic_operator: "ilike", + value: "", + table_name: "m_users", + }, + { + name: "clock_in", + logic_operator: "range", + value: dateStart, + value1: dateEnd, + }, + ], + joins: [ + { + name: "m_users", + column_join: "user_id", + column_results: ["name", "ktp_number"], + }, + ], + orders: { columns: ["id"], ascending: false }, + }; + const url = `${BASE_OSPRO}/api/presence/search`; + const response = await axios.post(url, payload, HEADER); + const actualHrCount = response.data.data.filter((item) => + assignedHr.includes(item.user_id) + ).length; + setActualHrCount(actualHrCount); + } catch (error) { + console.error("Failed to get actual HR:", error); + } + }; + + const getProjectDetail = async () => { + setIsReadyProjectDetail(false); + let URL = `${BASE_OSPRO}/api/project/detail/${PROJECT_ID}`; + if (GANTT_ID) { + URL = `${BASE_OSPRO}/api/project/detail/${PROJECT_ID}/${GANTT_ID}`; + } + if (SCURVE) { + URL = `${BASE_OSPRO}/api/project/detail/${PROJECT_ID}/${GANTT_ID}/${SCURVE}`; + } + const result = await axios + .get(URL, HEADER) + .then((res) => res) + .catch((err) => err.response); + console.log("getProjectDetail", result); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadyProjectDetail(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get project detail failed, ${result.data.message}`, + "Failed" + ); + setIsReadyProjectDetail(true); + return; + } else if (result.status == 200 && result.data.data) { + console.log(result.data.data); + // setComments(result.data.data); + setProjectName(result.data.data.nama ? result.data.data.nama : "-"); + setProjectManagerName( + result.data.data.projectManager ? result.data.data.projectManager : "-" + ); + setCustomerName( + result.data.data.company ? result.data.data.company : "-" + ); + setPlannedStart( + SCURVE + ? result.data.data.mulai_proyek + ? result.data.data.mulai_proyek + : null + : result.data.data.header?.planned_start + ? result.data.data.header?.planned_start + : null + ); + setPlannedFinish( + SCURVE + ? result.data.data.akhir_proyek + ? result.data.data.akhir_proyek + : null + : result.data.data.header?.planned_end + ? result.data.data.header?.planned_end + : null + ); + setActualStart( + result.data.data.header?.start_date + ? result.data.data.header.start_date + : null + ); + setEstimatedFinish( + result.data.data.header?.end_date + ? result.data.data.header.end_date + : null + ); + setPlannedCost( + result.data.data.rencana_biaya ? result.data.data.rencana_biaya : null + ); + setIsReadyProjectDetail(true); + setCalculationStatus( + result.data.data.calculation_status + ? result.data.data.calculation_status + : false + ); + + if ( + result.data.data.kode_sortname && + result.data.data.kode_sortname !== "" + ) { + if (SCURVE) { + getIntegrationInvoice(result.data.data.kode_sortname, result.data.data.id); + } else { + getIntegrationInvoice(result.data.data.kode_sortname, result.data.data.id, GANTT_ID); + } + } + } + }; + + const getSCurve = async () => { + setIsReadySCurve(false); + let URL = `${BASE_OSPRO}/api/project/get-s-curve`; + if (SCURVE && SCURVE == "1" && isHierarchy) { + URL = `${BASE_OSPRO}/api/project/calculate-s-curve`; + } + + const payload = { + project_id: PROJECT_ID.toString(), + gantt_id: GANTT_ID.toString(), + period: "week", + // "end_date": moment(new Date()).format('YYYY-MM-DD') + }; + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadySCurve(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get S Curve failed, ${result.data.message}`, + "Failed" + ); + setIsReadySCurve(true); + return; + } else if (result.status == 200 && result.data.data) { + let selisihProgress = 0; + let planningProgress = 0; + let actualProgress = 0; + let statusHealthBySchedule = "on-schedule"; + if ( + result.data.data.length > 0 && + result.data.data[0].data?.budget_control + ) { + setCurrentBudget( + result.data.data[0].data.budget_control.current_budget?.toString() + ); + setActualToDate( + result.data.data[0].data.budget_control.acwp?.toString() + ); + setBcwp(result.data.data[0].data.budget_control.bcwp?.toString()); + // setRemToComplete(result.data.data[0].data.budget_control.rem_to_complete?.toString()) + setAddCostToComplete( + result.data.data[0].data.budget_control.add_cost_to_complete?.toString() + ); + setEstAtCompletion( + result.data.data[0].data.budget_control.estimated_at_completion?.toString() + ); + setCostDeviation( + result.data.data[0].data.budget_control.cost_deviation?.toString() + ); + } + let now = new Date().toISOString().slice(0, 10); + let dates = result.data.data[0].data?.date; + let n = dates.findIndex( + (element) => new Date(now) < new Date(element[0]) + ); + if (n < 0) { + n = dates.length - 1; + } + if ( + result.data.data.length > 0 && + result.data.data[0].data?.percentagePlan && + result.data.data[0].data?.percentagePlan.length > 0 + ) { + if (SCURVE && SCURVE == "1") { + planningProgress = + result.data.data[0].data?.percentagePlan[ + result.data.data[0].data?.percentagePlan.length - 1 + ]; + } else { + planningProgress = result.data.data[0].data?.percentagePlan[n]; + } + setPlanningProgress(planningProgress); + } + if ( + result.data.data.length > 0 && + result.data.data[0].data?.percentageReal && + result.data.data[0].data?.percentageReal.length > 0 + ) { + actualProgress = + result.data.data[0].data?.percentageReal[ + result.data.data[0].data?.percentageReal.length - 1 + ]; + setActualProgress(actualProgress); + } + + selisihProgress = planningProgress - actualProgress; + if (selisihProgress > 0 && selisihProgress <= 5) { + statusHealthBySchedule = "warning"; + } else if (selisihProgress > 5) { + statusHealthBySchedule = "danger"; + } + + setHealthBySchedule(statusHealthBySchedule); + setIsReadySCurve(true); + } + }; + + const getOverdueActivities = async () => { + setIsReadyOverdueActivities(false); + const URL = `${BASE_OSPRO}/api/project/get-overdue-activities`; + const payload = { + id: PROJECT_ID.toString(), + gantt: GANTT_ID.toString(), + scurve: SCURVE ? SCURVE.toString() : null, + till_date: moment(new Date()).format("YYYY-MM-DD"), + }; + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + console.log("getOverdueActivities", result); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadyOverdueActivities(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get Overdue Activities failed, ${result.data.message}`, + "Failed" + ); + setIsReadyOverdueActivities(true); + return; + } else if (result.status == 200 && result.data.data) { + if (result.data.data.overdueActivities) { + setOverdueActivities(result.data.data.overdueActivities); + } + setHealthByBudget(result.data.data.budget_health); + setIsReadyOverdueActivities(true); + } + }; + + const getIntegrationInvoice = async (kode_sortname, id, gantt_id = null) => { + setIsReadyIntegrationInvoice(false); + const URL = `${BASE_OSPRO}/api/project/get-integration-invoice`; + let payload = { + search: kode_sortname, + id: id + }; + if (gantt_id) { + payload.gantt_id = gantt_id; + } + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + console.log("getIntegrationInvoice", result); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadyIntegrationInvoice(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get integration invoice failed, ${result.data.message}`, + "Failed" + ); + setIsReadyIntegrationInvoice(true); + return; + } else if (result.status == 200 && result.data.data) { + if (result.data.data.data) { + let total_invoice = result.data.data.data.total_invoice_amount; + let cash_in = result.data.data.data.total_invoice_paid_amount; + let total_cost = result.data.data.data.total_cost; + let outstanding_balance = total_invoice - cash_in; + setTotalInvoice(total_invoice ? total_invoice.toString() : null); + setCashIn(cash_in ? cash_in.toString() : null); + total_cost = total_cost.toString().split(".")[0]; + setTotalCost(total_cost ? total_cost.toString() : null); + setOutstandingBalance( + outstanding_balance ? outstanding_balance.toString() : null + ); + } + setIsReadyIntegrationInvoice(true); + } + }; + + const getReportDistribution = async () => { + setIsReadyReportDistribution(false); + const URL = `${BASE_OSPRO}/api/project/get-report-distribution`; + const payload = { + project_id: PROJECT_ID, + start_date: moment() + .startOf("month") + .subtract(1, "years") + .format("YYYY-MM-DD"), + end_date: moment(new Date()).subtract(1, "years").format("YYYY-MM-DD"), + }; + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + console.log("getReportDistribution", result); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadyReportDistribution(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get report distribution failed, ${result.data.message}`, + "Failed" + ); + setIsReadyReportDistribution(true); + return; + } else if (result.status == 200 && result.data.data) { + setReportDistribution(result.data.data); + setIsReadyReportDistribution(true); + } + }; + + const getComments = async () => { + setIsReadyComments(false); + const URL = `${BASE_OSPRO}/api/project-comment/search`; + const payload = { + columns: [ + { + name: "project_id", + logic_operator: "=", + value: PROJECT_ID.toString(), + operator: "AND", + }, + { + name: "gantt_id", + logic_operator: "=", + value: GANTT_ID.toString(), + operator: "AND", + }, + ], + joins: [ + { + name: "m_users", + column_join: "sender_id", + column_results: ["name", "username"], + }, + ], + orders: { columns: ["created_at"], ascending: false }, + paging: { start: 0, length: -1 }, + }; + + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + console.log("getComments", result); + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsReadyComments(true); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Get comments failed, ${result.data.message}`, + "Failed" + ); + setIsReadyComments(true); + return; + } else if (result.status == 200 && result.data.data) { + setComments(result.data.data); + setIsReadyComments(true); + } + }; + + const handleSendComment = async () => { + // console.log('handleSendComment', comment); + setIsSendingComment(true); + if (comment === "") { + NotificationManager.error( + "Please leave a comment before you send it.", + "Failed" + ); + setIsSendingComment(false); + return; + } + const URL = `${BASE_OSPRO}/api/project-comment/add`; + const payload = { + sender_id: localStorage.getItem("user_id"), + project_id: PROJECT_ID, + gantt_id: GANTT_ID, + comment: comment, + }; + const result = await axios + .post(URL, payload, HEADER) + .then((res) => res) + .catch((err) => err.response); + + if (!result) { + NotificationManager.error(`Could not connect to internet.`, "Failed"); + setIsSendingComment(false); + return; + } + + if (result.status !== 200) { + NotificationManager.error( + `Post comment failed, ${result.data.message}`, + "Failed" + ); + setIsSendingComment(false); + return; + } else if (result.status === 200) { + NotificationManager.success(`Post comment success`, "Success"); + resetInputComment(); + getComments(); + } + }; + + const resetInputComment = () => { + setComment(""); + setIsSendingComment(false); + }; + + const initMap = () => { + let mymap = L.map("map-area", { + center: center, + zoom: 13, + }); + + setMymap(mymap); + L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: + '© OpenStreetMap contributors', + }).addTo(mymap); + }; + + useEffect(() => { + if (mymap) { + if (reportDistribution.length > 0) { + reportDistribution.map((item, idx) => { + L.marker([item.lat, item.lon]) + .addTo(mymap) + .bindPopup(PopupContent(item)); + }); + } + } + }, [mymap, reportDistribution]); + + useEffect(() => { + // Add event listener for receiving messages from the iframe + window.addEventListener("message", handleIframeMessage); + + // Clean up the event listener on component unmount + return () => { + window.removeEventListener("message", handleIframeMessage); + }; + }, []); + + const handleIframeMessage = (event) => { + if (event.data && event.data.action === "getUrl") { + const childUrl = window.location.href; + + // Send the URL back to the iframe + event.source.postMessage( + { action: "sendUrl", url: childUrl }, + event.origin + ); + } + }; + + const RenderGantt = useMemo( + () => ( + + ), + [activeTabIdx] + ); + + const RenderComments = useMemo(() => { + return ( + <> +
+