This is the main code. The error only happens when running ‘webpack --config webpack.prod.js’
import { useEffect, useRef, useState } from "react";
import { HotTable, HotColumn } from "@handsontable/react";
import { registerAllModules } from "handsontable/registry";
import { useIntl } from "react-intl";
import { useDispatch } from "react-redux";
import { Button, Checkbox } from "@material-ui/core";
import * as TaskAction from "src/redux/actions/TaskAction";
import * as ManHourListAction from "src/redux/actions/ManHourListAction";
import * as ProjectTagAction from "src/redux/actions/ProjectTagAction";
import * as EstimationSettingAction from "src/redux/actions/EstimationSettingAction";
import Handsontable from "handsontable";
import SaveAsTemplateDialog from "src/components/estimate/SaveAsTemplateDialog";
import UnitPriceSetting from "src/pages/Estimate/components/UnitPriceSetting/UnitPriceSetting";
import "./handsongrid.css";
import { timeUnitMap, difficulty, difficultyMap, colorMap } from "src/data/Maps";
import { getRiskTimeValueString } from "src/utils/taskCalculation";
import { currencyList } from "src/data/Currency";
import moment from "moment";
import * as Style from "./style";
import editIcon from "src/images/pen.svg";
import arrowLeftIcon from "src/images/arrow-left.svg";
import arrowRightIcon from "src/images/arrow-right.svg";
import visibilityOffIcon from "src/images/visibility-off.svg";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { DragIndicator } from "@material-ui/icons";
// register Handsontable's modules
registerAllModules();
function HandsOnGrid({ data }) {
const { formatMessage } = useIntl();
const dispatch = useDispatch();
// console.log("HandsOnGrid", data);
const { taskList, selectedTaskFields, unitPriceSettingTags, currency, projectState, currentTeam } = data;
const [checkAll, setCheckAll] = useState(false);
const [openUnitPriceSetting, setOpenUnitPriceSetting] = useState(false);
const dollorSymbol = currencyList.find((c) => c.label === currency).symbol;
const timeUnitOptions = [
{ name: formatMessage({ id: "Hour" }), value: timeUnitMap.H },
{ name: formatMessage({ id: "Day" }), value: timeUnitMap.D },
{ name: formatMessage({ id: "Week" }), value: timeUnitMap.W },
{ name: formatMessage({ id: "Month" }), value: timeUnitMap.M }
];
const difficultyOptions = [
{ name: "", value: "" },
{ name: formatMessage({ id: difficultyMap.E }), value: difficulty.Easy },
{ name: formatMessage({ id: difficultyMap.M }), value: difficulty.Medium },
{ name: formatMessage({ id: difficultyMap.H }), value: difficulty.Hard }
];
const tagsList = unitPriceSettingTags.map((tag, index) => ({
name: tag.name,
tagId: tag.tagId,
price: {
hour: tag.pricings.find((price) => price.currencyUnit === currency)?.unitPriceHour,
day: tag.pricings.find((price) => price.currencyUnit === currency)?.unitPriceDay,
week: tag.pricings.find((price) => price.currencyUnit === currency)?.unitPriceWeek,
month: tag.pricings.find((price) => price.currencyUnit === currency)?.unitPriceMonth
},
cost: {
hour: tag.pricings.find((price) => price.currencyUnit === currency)?.costPriceHour,
day: tag.pricings.find((price) => price.currencyUnit === currency)?.costPriceDay,
week: tag.pricings.find((price) => price.currencyUnit === currency)?.costPriceWeek,
month: tag.pricings.find((price) => price.currencyUnit === currency)?.costPriceMonth
},
color: colorMap[index] || colorMap[0]
}));
const sortTasks = (tasks, parent) => {
let node = [];
tasks
.filter((t) => t.parent === parent)
.forEach((d) => {
let cd = d;
cd.children = sortTasks(tasks, d.id);
return node.push(cd);
});
return node;
};
const createRows = () => {
const rows = [];
const sortedTasks = sortTasks(JSON.parse(JSON.stringify(taskList)), null);
const renderCanDeleteFrom = ({ sIndex, mIndex, medium }) => {
if (sIndex == 0 && mIndex < 1) return "Large";
else if (medium.children.length > 1 && sIndex == 0 || sIndex == 0) return "Medium";
else return "Small";
};
let count = 0;
sortedTasks.forEach((large, lIndex) => {
large.children.forEach((medium, mIndex) => {
medium.children.forEach((small, sIndex) => {
// console.log("small.difficulty", small.difficulty);
rows.push({
idx: count,
ltask: sIndex === 0 && mIndex < 1 ? large.taskName : (renderCanDeleteFrom({ sIndex, mIndex, medium }) === "Small" || renderCanDeleteFrom({ sIndex, mIndex, medium }) === "Medium" ? null : ""),
mtask: (medium.children.length > 1 && sIndex === 0 || sIndex === 0) ? medium.taskName : (renderCanDeleteFrom({ sIndex, mIndex, medium }) === "Small" ? null : ""),
stask: small.taskName,
ltaskId: large.id,
mtaskId: medium.id,
staskId: small.id,
ltaskIdAndcanDeleteFrom: `${large.id},${(large?.children[0].id === medium.id && medium?.children[0].id === small.id && 3) || (medium?.children[0].id === small.id && 2) || (small.level === 2 && 1) || small.perm || medium.perm || large.perm},ThisIsLarge,notUnitTask`,
mtaskIdAndcanDeleteFrom: `${medium.id},${(large?.children[0].id === medium.id && medium?.children[0].id === small.id && 3) || (medium?.children[0].id === small.id && 2) || (small.level === 2 && 1) || small.perm || medium.perm || large.perm},ThisIsMedium,notUnitTask`,
staskIdAndcanDeleteFrom: `${small.id},${(large?.children[0].id === medium.id && medium?.children[0].id === small.id && 3) || (medium?.children[0].id === small.id && 2) || (small.level === 2 && 1) || small.perm || medium.perm || large.perm},ThisIsSmall,notUnitTask`,
perm: (large?.children[0].id === medium.id && medium?.children[0].id === small.id && 3) || (medium?.children[0].id === small.id && 2) || (small.level === 2 && 1) || small.perm || medium.perm || large.perm,
complete: Math.min(100, Math.round(Math.random() * 110)),
// assignee: assignees.find((ass) => ass.memberId === small.assignee.id)?.memberId,
tag: small.tag,
tagName: tagsList.find(tag => tag.tagId === small.tag)?.name || "",
unitPrice: tagsList.find(tag => tag.tagId === small.tag)?.price[small.timeUnit] || "",
timeUnit: small.timeUnit,
timeUnitName: formatMessage({ id: small.timeUnit }),
timeValue: small.timeValue,
// assigneeList: assignees,
unitPriceSettingTags: tagsList,
currency: currency,
riskCoefficient: small.task_risk_coe,
riskManHour: getRiskTimeValueString(small, formatMessage({ id: "MM" }), formatMessage({ id: "WW" }), formatMessage({ id: "DD" }), formatMessage({ id: "HH" })),
description: small.description,
taskSize: small.taskSize,
taskSizeUnit: small.taskSizeUnit,
productivity: small.productivity,
difficulty: small.difficulty,
difficultyName: (small.difficulty !== null && small.difficulty !== "" && small.difficulty !== undefined) ? formatMessage({ id: difficultyOptions.find(d => d.value === small.difficulty).name }) : "",
taskStartEndDateS: small.taskStartEndDate && moment(small.taskStartEndDate[0]).format("YYYY-MM-DD"),
taskStartEndDateE: small.taskStartEndDate && moment(small.taskStartEndDate[1]).format("YYYY-MM-DD"),
canDeleteFrom: renderCanDeleteFrom({ sIndex, mIndex, medium }),
checkboxChecked: small.checkboxChecked,
unitTask: false,
lastRow: lIndex == sortedTasks.length - 1 && mIndex == large.children.length - 1 && sIndex == medium.children.length - 1,
});
count++;
});
});
if (large.unitTask) {
rows.push({
idx: count,
ltask: large.taskName,
mtask: "",
stask: "",
ltaskId: large.id,
perm: 0,
mtaskId: null,
staskId: null,
ltaskIdAndcanDeleteFrom: `${large.id},0,ThisIsLarge,UnitTask`,
mtaskIdAndcanDeleteFrom: ",,,notUnitTask",
staskIdAndcanDeleteFrom: ",,,notUnitTask",
complete: Math.min(100, Math.round(Math.random() * 110)),
// assignee: assignees.find((ass) => ass.memberId === large.assignee.id)?.memberId,
tag: typeof large.tag !== "undefined" ? large.tag : large.tag_id,
tagName: tagsList.find(tag => tag.tagId === (typeof large.tag !== "undefined" ? large.tag : large.tag_id))?.name || "",
unitPrice: tagsList.find(tag => tag.tagId === (typeof large.tag !== "undefined" ? large.tag : large.tag_id))?.price[large.timeUnit] || "",
timeUnit: large.timeUnit,
timeUnitName: formatMessage({ id: large.timeUnit }),
timeValue: large.timeValue,
// assigneeList: assignees,
unitPriceSettingTags: tagsList,
currency: currency,
riskCoefficient: large.task_risk_coe,
riskManHour: getRiskTimeValueString(large, formatMessage({ id: "MM" }), formatMessage({ id: "WW" }), formatMessage({ id: "DD" }), formatMessage({ id: "HH" })),
description: large.description,
taskSize: large.taskSize,
taskSizeUnit: large.taskSizeUnit,
productivity: large.productivity,
difficulty: large.difficulty,
difficultyName: (large.difficulty !== null && large.difficulty !== "" && large.difficulty !== undefined) ? formatMessage({ id: difficultyOptions.find(d => d.value === large.difficulty).name }) : "",
taskStartEndDateS: large.taskStartEndDate && moment(large.taskStartEndDate[0]).format("YYYY-MM-DD"),
taskStartEndDateE: large.taskStartEndDate && moment(large.taskStartEndDate[1]).format("YYYY-MM-DD"),
canDeleteFrom: "Large",
checkboxChecked: large.checkboxChecked,
unitTask: true,
lastRow: true,
});
count++;
}
});
return rows;
};
const rows = createRows();
const hotTableComponent = useRef(null);
const afterChange = (changes, source) => {
if (source !== "loadData" && hotTableComponent.current) {
const hotInstance = hotTableComponent.current.hotInstance;
// console.log("afterChange", changes, source);
changes !== null && changes.forEach(([row, prop, oldVal, newVal]) => {
const ltaskId = hotInstance.getDataAtRowProp(row, "ltaskId");
const mtaskId = hotInstance.getDataAtRowProp(row, "mtaskId");
const staskId = hotInstance.getDataAtRowProp(row, "staskId");
const unitTask = hotInstance.getDataAtRowProp(row, "unitTask");
const tagId = hotInstance.getDataAtRowProp(row, "tag");
const timeUnit = hotInstance.getDataAtRowProp(row, "timeUnit");
const taskStartEndDateS = hotInstance.getDataAtRowProp(row, "taskStartEndDateS");
const taskStartEndDateE = hotInstance.getDataAtRowProp(row, "taskStartEndDateE");
// console.log("afterChange", hotInstance.getDataAtRowProp(row, "staskId"), prop, oldVal, newVal); // log the cell value of the specified column
switch (prop) {
case "ltask":
handleTaskNameChange(unitTask, ltaskId, mtaskId, ltaskId, prop, newVal);
break;
case "mtask":
handleTaskNameChange(unitTask, ltaskId, mtaskId, mtaskId, prop, newVal);
break;
case "stask":
handleTaskNameChange(unitTask, ltaskId, mtaskId, staskId, prop, newVal);
break;
case "tagName": {
if (newVal !== "") handleTagSettingChange(unitTask, unitTask ? ltaskId : staskId, mtaskId, ltaskId, newVal, oldVal, timeUnit);
break;
}
case "timeValue": {
handleManHourChange(unitTask, unitTask ? ltaskId : staskId, mtaskId, ltaskId, newVal, oldVal);
break;
}
case "timeUnitName": {
const taskId = !unitTask ? staskId : ltaskId;
const timeUnit = newVal === null ? timeUnitOptions.find(unit => unit.name === oldVal).value : timeUnitOptions.find(unit => unit.name === newVal).value;
handleTimeUnitChange(unitTask, taskId, mtaskId, ltaskId, tagId, timeUnit);
break;
}
case "riskCoefficient": {
const taskId = !unitTask ? staskId : ltaskId;
handleRiskCoeChanges(unitTask, mtaskId, ltaskId, taskId, newVal === null ? oldVal : newVal);
break;
}
case "description": {
const taskId = !unitTask ? staskId : ltaskId;
dispatchRowValue(taskId, "description", newVal === null ? oldVal : newVal);
break;
}
case "difficultyName": {
const taskId = !unitTask ? staskId : ltaskId;
const difficulty = newVal === null ? difficultyOptions.find(difficulty => difficulty.name === oldVal).value : difficultyOptions.find(difficulty => difficulty.name === newVal).value;
dispatchRowValue(taskId, "difficulty", difficulty);
break;
}
case "taskStartEndDateS": {
const taskId = !unitTask ? staskId : ltaskId;
dispatch({
type: TaskAction.TASKLISTREVISED_SET_ROW_VALUES,
payload: {
index: taskId,
name: "taskStartEndDate",
value: [newVal === null ? oldVal : newVal, taskStartEndDateE]
},
});
break;
}
case "taskStartEndDateE": {
const taskId = !unitTask ? staskId : ltaskId;
dispatch({
type: TaskAction.TASKLISTREVISED_SET_ROW_VALUES,
payload: {
index: taskId,
name: "taskStartEndDate",
value: [taskStartEndDateS, newVal === null ? oldVal : newVal]
},
});
break;
}
case "taskSize": {
const taskId = !unitTask ? staskId : ltaskId;
dispatchRowValue(taskId, "taskSize", newVal === null ? oldVal : newVal);
break;
}
case "taskSizeUnit": {
const taskId = !unitTask ? staskId : ltaskId;
dispatchRowValue(taskId, "taskSizeUnit", newVal === null ? oldVal : newVal);
break;
}
case "productivity": {
const taskId = !unitTask ? staskId : ltaskId;
dispatchRowValue(taskId, "productivity", newVal === null ? oldVal : newVal);
break;
}
default:
break;
}
});
}
};
const dispatchRowValue = (taskId, column, value) => {
dispatch({
type: TaskAction.TASKLISTREVISED_SET_ROW_VALUES,
payload: {
index: taskId,
name: column,
value: value
},
});
};
const handleTaskNameChange = (unitTask, ltaskId, mtaskId, targetTaskId, taskType, value) => {
if (unitTask === false || (unitTask === true && taskType === "ltask")) {
dispatchRowValue(targetTaskId, "taskName", value === null ? "" : value);
if (taskType === "ltask" || taskType === "mtask") {
if (unitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_SET_VALUE,
payload: {
unitTaskId: ltaskId,
name: taskType === "ltask" ? "largeTaskName" : "mediumTaskName",
value: value
}
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_SET_VALUE,
payload: {
mediumTaskId: mtaskId,
name: taskType === "ltask" ? "largeTaskName" : "mediumTaskName",
value: value
}
});
}
}
}
};
const handleManHourChange = (unitTask, staskId, mtaskId, ltaskId, newvalue, oldValue) => {
const regex = /^[+-]?([0-9]*[.])?[0-9]+$/;
if (regex.test(newvalue)) dispatchRowValue(staskId, "timeValue", newvalue);
else dispatchRowValue(staskId, "timeValue", oldValue);
if (!unitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "mediumTaskId", taskId: mtaskId }
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "unitTaskId", taskId: ltaskId }
});
}
dispatch({
type: TaskAction.GANTTREVISED_REFRESH,
});
};
const handleTimeUnitChange = (unitTask, targetId, mtaskId, ltaskId, tagId, timeUnit) => {
dispatch({
type: TaskAction.TASKLISTREVISED_RESET_TIMEVALUES,
payload: {
index: targetId,
name: "timeUnit",
value: timeUnit,
},
});
dispatchRowValue(targetId, "unitPrice", tagsList.find((a) => a.tagId === tagId).price[timeUnit]);
dispatchRowValue(targetId, "cost", tagsList.find((a) => a.tagId === tagId).cost[timeUnit]);
dispatchRowValue(targetId, "timeUnit", timeUnit);
if (!unitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: {
taskType: "mediumTaskId",
taskId: mtaskId,
updateTimeUnit: true,
timeUnit: timeUnit
}
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: {
taskType: "unitTaskId",
taskId: ltaskId,
updateTimeUnit: true,
timeUnit: timeUnit
}
});
}
dispatch({
type: TaskAction.GANTTREVISED_REFRESH,
});
};
const handleRiskCoeChanges = (unitTask, mtaskId, ltaskId, targetTaskId, value) => {
dispatchRowValue(targetTaskId, "task_risk_coe", value);
if (!unitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "mediumTaskId", taskId: mtaskId }
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "unitTaskId", taskId: ltaskId }
});
}
dispatch({
type: TaskAction.GANTTREVISED_REFRESH,
});
};
const handleTagSettingChange = (unitTask, targetTaskId, mtaskId, ltaskId, newValue, oldValue, timeUnit) => {
dispatchRowValue(targetTaskId, "unitPrice", (newValue === null || newValue === "") ? tagsList.find((a) => a.name === oldValue).price[timeUnit] : tagsList.find((a) => a.name === newValue).price[timeUnit]);
dispatchRowValue(targetTaskId, "cost", (newValue === null || newValue === "") ? tagsList.find((a) => a.name === oldValue).cost[timeUnit] : tagsList.find((a) => a.name === newValue).cost[timeUnit]);
dispatchRowValue(targetTaskId, "tag", (newValue === null || newValue === "") ? tagsList.find(tag => tag.name === oldValue).tagId : tagsList.find(tag => tag.name === newValue).tagId);
if (!unitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "mediumTaskId", taskId: mtaskId }
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_LIST_VALUE_CHANGED,
payload: { taskType: "unitTaskId", taskId: ltaskId }
});
}
dispatch({
type: TaskAction.GANTTREVISED_REFRESH,
});
};
const handleTaskAdd = () => {
dispatch({
type: ManHourListAction.MAN_HOUR_ADD_MEDIUM_TASK,
payload: { taskList, taskType: "ltask", isUnitTask: false }
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_ROW,
});
};
const handleUnitTaskAdd = () => {
dispatch({
type: ManHourListAction.MAN_HOUR_ADD_MEDIUM_TASK,
payload: { taskList, taskType: "ltask", isUnitTask: true }
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_UNIT_TASK_ROW,
});
};
const CustomDropdownEditor = Handsontable.editors.DropdownEditor.prototype.extend();
CustomDropdownEditor.prototype.prepare = function () {
Handsontable.editors.DropdownEditor.prototype.prepare.apply(this, arguments);
this.TEXTAREA.readOnly = true; // disable typing in the dropdown field
};
const buttonRenderer = (instance, td, row, col, prop, value, cellProperties) => {
Handsontable.dom.empty(td); // First, clean cell and append button
const data = instance.getDataAtRowProp(row, prop);
const taskId = parseInt(data.split(",")[0]);
const permission = parseInt(data.split(",")[1]);
const taskType = data.split(",")[2];
const isUnitTask = data.split(",")[3] === "UnitTask" ? true : false;
let showAddButton = false;
switch (permission) {
case 3:
showAddButton = true;
break;
case 2:
if (taskType === "ThisIsLarge") showAddButton = false;
else showAddButton = true;
break;
case 1:
if (taskType === "ThisIsLarge" || taskType === "ThisIsMedium") showAddButton = false;
else showAddButton = true;
break;
case 0: // unit task
showAddButton = true;
break;
default:
showAddButton = false;
break;
}
if (showAddButton) {
// Create button
const button = document.createElement("button");
button.innerHTML = "+";
button.onclick = () => {
if (taskType === "ThisIsLarge") {
if (isUnitTask) {
dispatch({
type: ManHourListAction.MAN_HOUR_ADD_MEDIUM_TASK,
payload: { taskList, taskType: "ltask", isUnitTask: true }
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_UNIT_TASK_ROW,
payload: {
currentTaskId: taskId,
}
});
} else {
dispatch({
type: ManHourListAction.MAN_HOUR_ADD_MEDIUM_TASK,
payload: { taskList, taskType: "ltask", isUnitTask: false }
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_ROW,
payload: {
currentTaskId: taskId,
}
});
}
} else if (taskType === "ThisIsMedium") {
dispatch({
type: ManHourListAction.MAN_HOUR_ADD_MEDIUM_TASK,
payload: {
taskList,
taskType: "mtask",
currentTaskId: taskId
}
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_MEDIUM_TASK,
payload: {
currentTaskId: taskId,
parent: taskList.find(task => task.id === taskId).parent,
level: 1
}
});
} else {
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_SMALL_TASK,
payload: {
currentTaskId: taskId,
parent: taskList.find(task => task.id === taskId).parent,
level: 2
}
});
}
};
// Add button styles
button.style.display = "flex";
button.style.justifyContent = "center";
button.style.alignItems = "center";
button.style.marginTop = "5px";
button.style.width = "15px";
button.style.height = "15px";
button.style.borderColor = "black";
button.style.borderRadius = "50%"; // This will make the button circular
button.style.backgroundColor = "white"; // Change the color as per your need
button.style.color = "black"; // Set the color of the text
button.style.cursor = "pointer";
td.appendChild(button);
return td;
}
};
const taskNameCell = {
editor: Handsontable.editors.TextEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (value === "") {
switch (prop) {
case "ltask":
td.innerHTML = formatMessage({ id: "Please-input-large-item" }); // Placeholder text
break;
case "mtask":
td.innerHTML = formatMessage({ id: "Please-input-medium-item" }); // Placeholder text
break;
default:
td.innerHTML = formatMessage({ id: "Please-input-small-item" }); // Placeholder text
break;
}
td.style.color = "#888"; // Placeholder text color
} else if (value === null) {
td.innerHTML = "";
td.style.color = "#888"; // Placeholder text color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
};
const tagDropDownCell = {
editor: CustomDropdownEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (prop !== "tagName") return;
if (value === null || value === undefined || value === "") {
td.style.color = "#999";
// td.innerHTML = formatMessage({ id: "Please-select" });
const divElement = document.createElement("div");
divElement.innerHTML = `<div class="tag-setting"><span>${formatMessage({ id: "Please-select" })}</span><span>▼</span></div>`;
td.appendChild(divElement);
} else {
td.innerHTML = `<span style="display: block; color: #000; border-radius: 16px; text-align: center; background-color: ${tagsList.find(tag => tag.name === value).color}; height: 20px; margin-top: 1px;">${value}</span>`;
}
},
source: tagsList.map(tag => tag.name),
};
const unitPriceCell = function (hotInstance, td, row, col, prop, value, cellProperties) {
// Clear any content from the cell
Handsontable.dom.empty(td);
const tag = hotInstance.getDataAtRowProp(row, "tag");
// Create button
const iconBtn = document.createElement("img");
iconBtn.src = editIcon;
iconBtn.style.cursor = "pointer";
iconBtn.style.width = "16px";
iconBtn.style.height = "16px";
iconBtn.style.verticalAlign = "middle";
iconBtn.style.marginLeft = "10px";
if (tag !== "") {
iconBtn.addEventListener("click", function() {
dispatch({
type: ProjectTagAction.UPDATE_PROJECT_SELECTED_TAG,
payload: { selectedTag: tag }
});
setOpenUnitPriceSetting(true);
});
}
td.appendChild(iconBtn);
// Create a span for dollor symbol
const dollor = document.createElement("span");
dollor.style.marginLeft = "5px";
dollor.textContent = dollorSymbol;
td.appendChild(dollor);
const unitPrice = hotInstance.getDataAtRowProp(row, "unitPrice");
// Create a span for unit price
const timeunitprice = document.createElement("span");
timeunitprice.style.marginLeft = "3px";
timeunitprice.textContent = unitPrice.toLocaleString();
td.appendChild(timeunitprice);
// Create a span for time unit
const timeunit_span = document.createElement("span");
timeunit_span.style.width = "60px";
timeunit_span.style.height = "20px";
timeunit_span.style.borderRadius = "12px";
timeunit_span.style.display = "inline-block"; // To make sure width and height are respected
timeunit_span.style.verticalAlign = "middle"; // To vertically align with the button
timeunit_span.style.textAlign = "center";
timeunit_span.style.lineHeight = "23px"; // To vertically center the text inside the span
timeunit_span.style.backgroundColor = "#DDDDDD";
timeunit_span.style.marginLeft = "5px"; // Add some spacing between the button and the span
timeunit_span.textContent = value; // You can get the text from the 'value' argument
td.appendChild(timeunit_span);
if (tag === "") {
td.style.background = "#DADADA"; // cell's background color
}
return td;
};
const timeValueCell = {
editor: Handsontable.editors.NumericEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
// console.log("timeValueCell", hotInstance.getDataAtRowProp(row, "tag"), prop, value);
const tag = hotInstance.getDataAtRowProp(row, "tag");
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
};
const timeUnitDropDownCell = {
editor: CustomDropdownEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const tag = hotInstance.getDataAtRowProp(row, "tag");
td.innerHTML = `<div class="timeunit-setting"><span>${value}</span><span>▼</span></div>`;
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
source: timeUnitOptions.map(unit => unit.name),
};
const riskCoefficientCell = {
editor: Handsontable.editors.NumericEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const tag = hotInstance.getDataAtRowProp(row, "tag");
td.innerHTML = `<div><span>${value}</span><span>%</span></div>`;
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
};
const riskManHourCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const tag = hotInstance.getDataAtRowProp(row, "tag");
td.innerHTML = value;
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
};
const descriptionCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const tag = hotInstance.getDataAtRowProp(row, "tag");
td.innerHTML = value;
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
};
const difficultyDownCell = {
editor: CustomDropdownEditor,
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const tag = hotInstance.getDataAtRowProp(row, "tag");
td.innerHTML = `<div class="timeunit-setting"><span>${value === null ? "" : value}</span><span>▼</span></div>`;
if (tag === "") {
td.style.color = "#999";
td.style.background = "#DADADA"; // cell's background color
cellProperties.readOnly = true; // Disable the cell
}
return td;
},
source: difficultyOptions.map(difficulty => difficulty.name),
};
const taskSizeCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const taskSize = hotInstance.getDataAtRowProp(row, "taskSize");
td.innerHTML = value;
if (taskSize === "") {
td.style.color = "#A4ACB9";
td.innerHTML = 0;
}
return td;
},
};
const taskSizeUnitCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const taskSizeUnit = hotInstance.getDataAtRowProp(row, "taskSizeUnit");
td.innerHTML = value;
if (taskSizeUnit === "") {
td.style.color = "#A4ACB9";
td.innerHTML = formatMessage({ id: "Unit" });
}
return td;
},
};
const productivityCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const productivity = hotInstance.getDataAtRowProp(row, "productivity");
td.innerHTML = value;
if (productivity === "") {
td.style.color = "#A4ACB9";
td.innerHTML = 0;
}
return td;
},
};
const productivityUnitCell = {
renderer: function (hotInstance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
const taskSizeUnit = hotInstance.getDataAtRowProp(row, "taskSizeUnit");
const timeUnit = hotInstance.getDataAtRowProp(row, "timeUnit");
td.innerHTML = value;
if (taskSizeUnit && timeUnit) {
td.innerHTML = `<span style="display: block; color: #000; border-radius: 16px; text-align: center; background-color: #F2F2F2; height: 20px; margin-top: 1px;">${taskSizeUnit}/${formatMessage({ id: timeUnit })}</span>`;
} else {
td.innerHTML = `<span style="display: block; color: #000; border-radius: 16px; text-align: center; background-color: #F2F2F2; height: 20px; margin-top: 1px;">${formatMessage({ id: "Unit" })}/${formatMessage({ id: timeUnit })}</span>`;
}
return td;
},
};
Handsontable.cellTypes.registerCellType("taskNameCell", taskNameCell);
Handsontable.cellTypes.registerCellType("tagDropDownCell", tagDropDownCell);
Handsontable.cellTypes.registerCellType("unitPriceCell", unitPriceCell);
Handsontable.cellTypes.registerCellType("timeValueCell", timeValueCell);
Handsontable.cellTypes.registerCellType("timeUnitDropDownCell", timeUnitDropDownCell);
Handsontable.cellTypes.registerCellType("riskCoefficientCell", riskCoefficientCell);
Handsontable.cellTypes.registerCellType("riskManHourCell", riskManHourCell);
Handsontable.cellTypes.registerCellType("descriptionCell", descriptionCell);
Handsontable.cellTypes.registerCellType("difficultyDownCell", difficultyDownCell);
Handsontable.cellTypes.registerCellType("taskSizeCell", taskSizeCell);
Handsontable.cellTypes.registerCellType("taskSizeUnitCell", taskSizeUnitCell);
Handsontable.cellTypes.registerCellType("productivityCell", productivityCell);
Handsontable.cellTypes.registerCellType("productivityUnitCell", productivityUnitCell);
let mergeCells = [];
rows.forEach((r, index) => {
if (r.unitTask) mergeCells.push({ row: index, col: 1, rowspan: 1, colspan: 5 });
});
const handleCheckboxAllChecked = (event) => {
dispatch({
type: TaskAction.TASKLISTREVISED_CHECKBOX_ALL_CHECKED,
payload: { checked: event.target.checked },
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_CHECKED_ALLTASK,
payload: {
taskList: rows,
checked: event.target.checked
}
});
setCheckAll(event.target.checked);
};
const handleCheckboxChecked = (event, row) => {
// dispatch({
// type: TaskAction.TASKLISTREVISED_SET_ROW_VALUES,
// payload: {
// index: !row.unitTask ? row.staskId : row.ltaskId,
// name: "checkboxChecked",
// value: event.target.checked
// },
// });
dispatch({
type: TaskAction.TASKLISTREVISED_UPDATE_CHECKBOX,
payload: {
id: !row.unitTask ? row.staskId : row.ltaskId,
checked: event.target.checked,
canDeleteFrom: row.canDeleteFrom,
ltaskId: row.ltaskId,
mtaskId: row.mtaskId,
staskId: row.staskId,
unitTask: row.unitTask
}
});
dispatch({
type: TaskAction.TASKLISTREVISED_ADD_CHECKED_TASK,
payload: {
id: !row.unitTask ? row.staskId : row.ltaskId,
checked: event.target.checked,
canDeleteFrom: row.canDeleteFrom,
ltaskId: row.ltaskId,
mtaskId: row.mtaskId,
staskId: row.staskId,
unitTask: row.unitTask
}
});
};
function afterGetColHeader(col, TH) {
const headerTitle = TH.textContent || TH.innerText;
const existingDifficultyIcon = TH.querySelector(".difficulty-inner-icon-class");
if (existingDifficultyIcon) TH.firstChild.removeChild(existingDifficultyIcon);
const existingTaskDateIcon = TH.querySelector(".taskdate-inner-icon-class");
if (existingTaskDateIcon) TH.firstChild.removeChild(existingTaskDateIcon);
const existingTaskSizeIcon = TH.querySelector(".tasksize-inner-icon-class");
if (existingTaskSizeIcon) TH.firstChild.removeChild(existingTaskSizeIcon);
const existingTaskProductivityIcon = TH.querySelector(".taskproductivity-inner-icon-class");
if (existingTaskProductivityIcon) TH.firstChild.removeChild(existingTaskProductivityIcon);
switch (headerTitle) {
case "小項目":
case "Small Item": {
if (selectedTaskFields.length < 4) {
const existingInnerIcon = TH.querySelector(".inner-icon-class");
if (!existingInnerIcon) {
const innerIconBtn = document.createElement("img");
innerIconBtn.src = arrowLeftIcon;
innerIconBtn.style.backgroundColor = "#fff";
innerIconBtn.style.cursor = "pointer";
innerIconBtn.classList.add("inner-icon-class");
innerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: [
"Task-Start-End-Date",
"Task-Size",
"Task-Productivity",
"Task-Difficulty"
]
}
});
};
TH.firstChild.appendChild(innerIconBtn);
} else {
existingInnerIcon.style.display = "block";
}
// Outer "right icon" for the right outer edge of the fifth header
const existingOuterIcon = TH.querySelector(".outer-icon-class");
if (!existingOuterIcon) {
const outerIconBtn = document.createElement("img");
outerIconBtn.src = arrowRightIcon;
outerIconBtn.style.backgroundColor = "#fff";
outerIconBtn.style.cursor = "pointer";
outerIconBtn.classList.add("outer-icon-class");
outerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: [
"Task-Start-End-Date",
"Task-Size",
"Task-Productivity",
"Task-Difficulty"
]
}
});
};
TH.firstChild.appendChild(outerIconBtn);
} else {
existingOuterIcon.style.display = "block";
}
} else {
// If selectedTaskFields.length == 4, hide the icons (if they exist)
const existingInnerIcon = TH.querySelector(".inner-icon-class");
const existingOuterIcon = TH.querySelector(".outer-icon-class");
if (existingInnerIcon) {
existingInnerIcon.style.display = "none";
}
if (existingOuterIcon) {
existingOuterIcon.style.display = "none";
}
}
break;
}
case "Work Period (Start)":
case "作業期間 (開始)": {
if (selectedTaskFields.includes("Task-Start-End-Date")) {
const innerIconBtn = document.createElement("img");
innerIconBtn.src = visibilityOffIcon;
innerIconBtn.style.width = "12px";
innerIconBtn.style.height = "12px";
innerIconBtn.style.backgroundColor = "#F0F0F0";
innerIconBtn.style.cursor = "pointer";
innerIconBtn.classList.add("taskdate-inner-icon-class");
innerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: selectedTaskFields.filter(field => field !== "Task-Start-End-Date")
}
});
};
TH.firstChild.appendChild(innerIconBtn);
}
break;
}
case "Task Size":
case "作業規模": {
if (selectedTaskFields.includes("Task-Size")) {
const innerIconBtn = document.createElement("img");
innerIconBtn.src = visibilityOffIcon;
innerIconBtn.style.width = "12px";
innerIconBtn.style.height = "12px";
innerIconBtn.style.backgroundColor = "#F0F0F0";
innerIconBtn.style.cursor = "pointer";
innerIconBtn.classList.add("tasksize-inner-icon-class");
innerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: selectedTaskFields.filter(field => field !== "Task-Size")
}
});
};
TH.firstChild.appendChild(innerIconBtn);
}
break;
}
case "Task Productivity":
case "生産性": {
if (selectedTaskFields.includes("Task-Productivity")) {
const innerIconBtn = document.createElement("img");
innerIconBtn.src = visibilityOffIcon;
innerIconBtn.style.width = "12px";
innerIconBtn.style.height = "12px";
innerIconBtn.style.backgroundColor = "#F0F0F0";
innerIconBtn.style.cursor = "pointer";
innerIconBtn.classList.add("taskproductivity-inner-icon-class");
innerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: selectedTaskFields.filter(field => field !== "Task-Productivity")
}
});
};
TH.firstChild.appendChild(innerIconBtn);
}
break;
}
case "Difficulty":
case "難易度": {
if (selectedTaskFields.includes("Task-Difficulty")) {
const innerIconBtn = document.createElement("img");
innerIconBtn.src = visibilityOffIcon;
innerIconBtn.style.width = "12px";
innerIconBtn.style.height = "12px";
innerIconBtn.style.backgroundColor = "#F0F0F0";
innerIconBtn.style.cursor = "pointer";
innerIconBtn.classList.add("difficulty-inner-icon-class");
innerIconBtn.onclick = function() {
dispatch({
type: EstimationSettingAction.UPDATE_ESTIMATION_SETTING,
payload: {
selectedTaskFields: selectedTaskFields.filter(field => field !== "Task-Difficulty")
}
});
};
TH.firstChild.appendChild(innerIconBtn);
}
break;
}
default:
break;
}
}
let hiddenColumns = [];
if (!selectedTaskFields.includes("Task-Start-End-Date")) { hiddenColumns.push(6); hiddenColumns.push(7); }
if (!selectedTaskFields.includes("Task-Size")) { hiddenColumns.push(8); hiddenColumns.push(9); }
if (!selectedTaskFields.includes("Task-Productivity")) { hiddenColumns.push(10); hiddenColumns.push(11); }
if (!selectedTaskFields.includes("Task-Difficulty")) hiddenColumns.push(12);
let nestedHeaders = ["",
formatMessage({ id: "Large-item" }),
"",
formatMessage({ id: "Medium-item" }),
"",
formatMessage({ id: "Small-item" })
];
nestedHeaders.push(formatMessage({ id: "Start-End-Date-S" }));
nestedHeaders.push(formatMessage({ id: "Start-End-Date-E" }));
nestedHeaders.push({label: formatMessage({ id: "Task-Size" }), colspan: 2 });
nestedHeaders.push({label: formatMessage({ id: "Task-Productivity" }), colspan: 2 });
nestedHeaders.push(formatMessage({ id: "Task-Difficulty" }));
nestedHeaders.push(formatMessage({ id: "Cost-setting" }));
nestedHeaders.push(formatMessage({ id: "Unit-price" }));
nestedHeaders.push(formatMessage({ id: "Man-hour" }));
nestedHeaders.push(formatMessage({ id: "Man-hour-time-unit" }));
nestedHeaders.push(formatMessage({ id: "Risk-factor" }));
nestedHeaders.push(formatMessage({ id: "Efforts-including-risk" }));
nestedHeaders.push(formatMessage({ id: "Comment" }));
const getListStyle = isDraggingOver => ({
background: isDraggingOver ? "lightblue" : "#fff",
padding: 0,
maxWidth: "960px"
});
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: 0,
margin: "0 0 0 0",
// change background colour if dragging
background: "#fff",
// styles we need to apply on draggables
...draggableStyle
});
const onDragEnd = (result) => {
if (result.destination !== null) {
const source = rows.find((row) => row.idx == result.source.index);
const destination = rows.find((row) => row.idx == result.destination.index);
if (source.canDeleteFrom !== destination.canDeleteFrom) return;
if (source.unitTask !== destination.unitTask) return;
dispatch({
type: TaskAction.TASKLISTREVISED_CHANGE_POSITION,
payload: { source, destination }
});
}
};
return (
<div>
{
rows.length > 0
? <div style={{ display: "grid", gridAutoFlow: "column", gap: 0, gridTemplateColumns: "28px 28px auto" }}>
<div style={{ border: "1px solid #DDDDDD", width: "28px" }}>
<Style.Checkbox>
<Checkbox size="small" style={{ width: "20px", height: "20px" }} onChange={(event) => handleCheckboxAllChecked(event)} checked={checkAll} />
</Style.Checkbox>
<Style.CheckboxContainer>
{
rows.map((row, index) => (
<Style.Checkbox key={index}>
<Checkbox size="small" style={{ width: "20px", height: "20px" }} onChange={(event) => handleCheckboxChecked(event, row)} checked={row.checkboxChecked} />
</Style.Checkbox>
))
}
</Style.CheckboxContainer>
</div>
<div style={{ border: "1px solid #DDDDDD", width: "28px" }}>
<Style.Checkbox />
<Style.CheckboxContainer>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{
rows.map((data, index) => (
<Draggable key={index} draggableId={`${data.idx}`} index={data.idx}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<Style.IndicatorContainer key={index}>
<DragIndicator />
{snapshot.isDragging && <div style={{ position: "absolute", marginLeft: "25px", border: "1px solid #CCCCCC", backgroundColor: "#fff", minWidth: "1200px", height: "23px", fontSize: "14px", padding: "2px", display: "grid", gridAutoFlow: "column", gap: 0, gridTemplateColumns: "200px 200px 200px auto" }}>
<span>{data.ltask}</span>
<span>{data.mtask}</span>
<span>{data.stask}</span>
</div>}
</Style.IndicatorContainer>
</div>
)}
</Draggable>
))
}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</Style.CheckboxContainer>
</div>
<HotTable
ref={hotTableComponent}
data={rows}
nestedHeaders={[
nestedHeaders
]}
height={rows.length <= 10 ? 300 : "auto"}
// width="auto"
colWidths={100}
rowHeights={25}
manualColumnResize={true}
autoRowSize={false}
autoColumnSize={false}
minSpareRows={0}
fixedColumnsStart={6}
licenseKey="eefeef"
afterChange={afterChange}
afterGetColHeader={afterGetColHeader}
mergeCells={mergeCells}
hiddenColumns={{
columns: hiddenColumns,
indicators: true,
}}
>
<HotColumn data="ltaskIdAndcanDeleteFrom" width={25} readOnly={true} renderer={buttonRenderer} />
<HotColumn data="ltask" width={200} type="taskNameCell" />
<HotColumn data="mtaskIdAndcanDeleteFrom" width={25} readOnly={true} renderer={buttonRenderer} />
<HotColumn data="mtask" width={200} type="taskNameCell" />
<HotColumn data="staskIdAndcanDeleteFrom" width={25} readOnly={true} renderer={buttonRenderer} />
<HotColumn data="stask" width={200} type="taskNameCell" />
<HotColumn data="taskStartEndDateS" width={150} type="date" dateFormat="YYYY-MM-DD" />
<HotColumn data="taskStartEndDateE" width={150} type="date" dateFormat="YYYY-MM-DD" />
<HotColumn data="taskSize" width={80} type="taskSizeCell" />
<HotColumn data="taskSizeUnit" width={100} type="taskSizeUnitCell" />
<HotColumn data="productivity" width={80} type="productivityCell" />
<HotColumn data="productivityUnit" width={100} type="productivityUnitCell" />
<HotColumn data="difficultyName" width={150} type="difficultyDownCell" />
<HotColumn data="tagName" width={200} type="tagDropDownCell" />
<HotColumn data="timeUnitName" width={200} readOnly={true} renderer={unitPriceCell} />
<HotColumn data="timeValue" width={100} type="timeValueCell" />
<HotColumn data="timeUnitName" width={100} type="timeUnitDropDownCell" />
<HotColumn data="riskCoefficient" width={100} type="riskCoefficientCell" />
<HotColumn data="riskManHour" width={150} readOnly={true} type="riskManHourCell" />
<HotColumn data="description" width={200} type="descriptionCell" />
</HotTable>
</div> : <div
style={{
width: "100%",
minHeight: "525px",
background: "#F9F9F9",
color: "#BCBCBC",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
fontSize: "28px"
}}
>
<span>+ {formatMessage({ id: "Make-Estimation" })}</span>
<div style={{ marginTop: "20px", display: "grid", gap: "10px", gridAutoFlow: "column" }}>
<Button
variant="contained"
onClick={handleUnitTaskAdd}
style={{ width: "270px" }}
>
{formatMessage({ id: "Easy-Mode-Estimation" })}
</Button>
<Button
variant="contained"
onClick={handleTaskAdd}
style={{ width: "270px" }}
>
{formatMessage({ id: "Detail-Mode-Estimation" })}
</Button>
</div>
</div>
}
<div style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}>
<div style={{ display: "grid", gridAutoFlow: "column", gap: "10px", padding: "10px 0 10px 0" }}>
<SaveAsTemplateDialog />
</div>
</div>
<UnitPriceSetting
open={openUnitPriceSetting}
handleCloseModal={() => setOpenUnitPriceSetting(false)}
currency={currency}
/>
</div>
);
}
export default HandsOnGrid;