let versionList,
tableData,
pivotTableData,
tableOptions,
TableInstance,
pivotType = "normal",
editContents = {},
folderDepth = {},
res,
maxDepth;
const ContentType = {
normal: {
},
version: {
},
owner: {
}
};
const ReqPriority = {
매우_높음: 7,
높음: 6,
중간: 5,
낮음: 4,
매우_낮음: 3
};
const ReqDifficulty = {
매우_어려움: 3,
어려움: 4,
보통: 5,
쉬움: 6,
매우_쉬움: 7
};
var ReqStatus = {};
var req_state_map = {};
// 상태 카테고리별 클래스 추가
const req_state_category_class_map = {
"3": "open",
"4": "investigation",
"5": "resolved",
"6": "closeStatus",
}
const getReqWriterName = (writerId) =>{
let writer = writerId.match(/\[(.*?)\]/);
return writer[1];
};
const createUUID = () => {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
const calcStatus = (arr) =>
arr.reduce((acc, cur) => {
// 상태별 개수 카운팅
const result = { statusTotal: 0 };
Object.keys(ReqStatus).map((state) => {
result[state] = Number(acc?.[state] ?? 0) + Number(cur[state]);
result.statusTotal += result[state];
});
return result;
}, {});
const getDate = (stamp) => {
if (!stamp || stamp < 0) return "";
const time = new Date(stamp);
return `${time.getFullYear()}-${addZero(time.getMonth() + 1)}-${addZero(time.getDate())}`;
};
const addZero = (n) => {
return n < 10 ? `0${n}` : n;
};
const setDepth = (data, parentId, titles = []) => {
const node = data.find((task) => task.c_id === parentId);
if (!node || node.c_parentid < 2) {
// 동적으로 깊이 정보 생성
const depths = {};
titles.reverse().forEach((title, index) => {
depths[`depth${index + 1}`] = title;
});
for (let i = 1; i <= maxDepth; i++) {
if (!depths[`depth${i}`]) {
depths[`depth${i}`] = "";
}
}
return depths;
}
if (node.c_type === 'folder') {
titles.push(node.c_title);
}
return setDepth(data, node.c_parentid, titles);
};
const getVersionTitle = (id) => {
if (!id) return "";
return versionList.find((v) => v.c_id === Number(id))?.c_title ?? "";
};
const CategoryName = {
folder: "Group",
default: "요구사항"
};
const mapperTableData = (data) => {
return data
?.sort((a, b) => a.c_parentid - b.c_parentid)
.filter(({ c_type }) => c_type === 'default') // 'default' 타입만 필터링
.reduce((acc, cur) => {
const {
c_id,
c_parentid,
c_title,
c_req_writer,
assignee,
c_req_contents,
reqStateEntity,
reqPriorityEntity,
reqDifficultyEntity,
c_req_create_date,
c_req_start_date,
c_req_end_date,
c_req_plan_progress,
c_req_pdservice_versionset_link,
c_type
} = cur;
if (cur.c_parentid < 2) return acc;
const versions = (c_req_pdservice_versionset_link !== null) ? JSON.parse(c_req_pdservice_versionset_link) : [];
versions.forEach((vid) => {
const assigneeNames = assignee.length > 0 ? assignee.map(a => a.담당자_이름).join(', ') : "담당자 미배정";
const depthInfo = setDepth(data, c_parentid);
let state_category_icon = null;
if (reqStateEntity && reqStateEntity.reqStateCategoryEntity && reqStateEntity.reqStateCategoryEntity.c_category_icon) {
state_category_icon = reqStateEntity.reqStateCategoryEntity.c_category_icon;
}
acc.push({
version: getVersionTitle(vid),
id: c_id,
category: CategoryName[c_type],
assignee: assigneeNames ,
status: c_type !== "folder" ? reqStateEntity?.data ?? "" : "",
...depthInfo,
content: c_title,
priority: reqPriorityEntity?.data ?? "",
difficulty: reqDifficultyEntity?.data ?? "",
createDate: getDate(c_req_create_date),
startDate: getDate(c_req_start_date),
endDate: getDate(c_req_end_date),
progress: c_req_plan_progress || 0,
origin: cur,
_status: reqStateEntity?.c_id,
_priority: reqPriorityEntity?.c_id,
_difficulty: reqDifficultyEntity?.c_id,
_state_category: state_category_icon,
_version: Number(vid)
});
});
return acc;
}, []);
};
const mapperPivotTableData = (data) => {
return data.reduce((acc, cur) => {
const {
c_id,
c_parentid,
c_title,
c_req_writer,
assignee,
c_req_contents,
reqStateEntity,
reqPriorityEntity,
reqDifficultyEntity,
c_req_create_date,
c_req_start_date,
c_req_end_date,
c_req_plan_progress,
c_req_pdservice_versionset_link
} = cur;
if (cur.c_parentid < 2) return acc;
if (assignee.length === 0) { // 담당자 데이터가 없는 경우 처리
const versions = c_req_pdservice_versionset_link ? JSON.parse(c_req_pdservice_versionset_link) : [""];
versions.forEach((version) => {
const depthInfo = setDepth(data, c_parentid);
let row_data = {
id: c_id,
version: version,
assignee: "담당자 미배정",
_assignee: "담당자 미배정",
statusTotal: "",
...depthInfo,
content: c_title,
origin: cur,
c_parentid: c_parentid
};
// 상태 컬럼 동적 반영
Object.keys(ReqStatus).map(state => {
row_data[state] = reqStateEntity?.c_id === ReqStatus[state] ? 1 : "";
});
acc.push(row_data);
});
}
else {
assignee.forEach(({ 담당자_아이디, 담당자_이름,요구사항_여부 }) => { // 담당자 별로 데이터 생성
const versions = c_req_pdservice_versionset_link ? JSON.parse(c_req_pdservice_versionset_link) : [""];
if(요구사항_여부){
versions.forEach((version) => {
// 각 버전별로 데이터 생성
const depthInfo = setDepth(data, c_parentid);
let row_data = {
id: c_id,
version: version,
assignee: 담당자_이름, //getReqWriterName(c_req_writer),
_assignee: 담당자_아이디,
statusTotal: "",
...depthInfo,
content: c_title,
origin: cur,
c_parentid: c_parentid
};
// 상태 컬럼 동적 반영
Object.keys(ReqStatus).map(state => {
row_data[state] = reqStateEntity?.c_id === ReqStatus[state] ? 1 : "";
});
acc.push(row_data);
});
}
});
}
return acc;
}, []);
};
const rearrangement = (
arr,
key,
root,
data // arr를 key 기준으로 그룹화 첫번째 한테는 root 속성을 넣어줌
) =>
arr.reduce((acc, cur) => {
const result = { ...cur, ...data };
const index = acc?.findIndex((item) => item.some((task) => task[key] === result[key]));
if (index >= 0) {
acc[index].push(result);
} else {
acc?.push([{ ...result, root }]);
}
return acc;
}, []);
class Table {
constructor() {
this.$table = this.makeElement("table");
this.$data = this.setTableData();
}
setTableData() {
folderDepth = {};
for (let i = 1; i <= maxDepth; i++) {
folderDepth[`depth${i}`] = `Depth${i}`;
}
const contentTypeBase = {
version: "버전",
assignee: "담당자",
...folderDepth,
content: "요구사항 제목"
};
const filtered_data = (data) => data.filter(item => item.origin && item.origin.attr && item.origin.attr.rel !== "folder");
const sortData = (data, ...criteria) => data.sort((a, b) => {
for (const criterion of criteria) {
const comparison = criterion(a, b);
if (comparison !== 0) return comparison;
}
return 0;
});
const sort_by_version_and_parent = (a, b) => a._version - b._version || a.c_parentid - b.c_parentid;
const sort_by_assignee_and_parent = (a, b) => a.assignee?.localeCompare(b.assignee) || a.c_parentid - b.c_parentid;
const sort_by_assignee_version_and_parent = (a, b) => a.assignee?.localeCompare(b.assignee) || a._version - b._version || a.c_parentid - b.c_parentid;
if (pivotType === "normal") {
ContentType["normal"] = {
...contentTypeBase,
status: "상태",
priority: "우선순위",
difficulty: "난이도",
createDate: "생성일",
startDate: "시작일",
endDate: "종료일"
};
return tableData
.filter(item => item.category !== "Group")
.sort(sort_by_version_and_parent);
}
if (pivotType === "version") {
ContentType["version"] = {
...contentTypeBase
};
Object.keys(ReqStatus).map(state => {
ContentType["version"][state] = state;
});
ContentType["version"].statusTotal = "총계";
return versionList.map(version => {
const filteredItems = filtered_data(pivotTableData)
.filter(item => item.version?.includes(`${version.c_id}`))
.sort(sort_by_assignee_and_parent);
const childrenItems = rearrangement(filteredItems, "assignee", "assignee", {
version: version.c_title,
_version: version.c_id
}).reduce((acc, cur) => ([
...acc,
{
...cur[0],
children: cur.slice(1),
lastChild: {
_version: version.c_id,
assignee: `${cur[0].assignee} 총계`,
_assignee: cur[0]._assignee,
col: 1,
colSpan: 2 + maxDepth,
node: "leaf",
origin: version,
...calcStatus(cur)
}
}
]), []);
return {
version: `${version.c_title} 총계`,
_version: version.c_id,
col: 0,
colSpan: 3 + maxDepth,
root: "version",
origin: version,
node: "root",
children: childrenItems,
...calcStatus(filteredItems)
};
});
}
if (pivotType === "owner") {
ContentType["owner"] = {
assignee: "담당자",
version: "버전",
...contentTypeBase
};
Object.keys(ReqStatus).map(state => {
ContentType["owner"][state] = state;
});
ContentType["owner"].statusTotal = "총계";
const all_requirement_data = filtered_data(pivotTableData)
.flatMap(task => {
const versions = Array.isArray(task.version) ? task.version : [task.version];
return versions.reduce((acc, cur) => {
const versionItem = versionList.find(item => item.c_id === Number(cur));
return [...acc, { ...task, _version: versionItem.c_id, version: versionItem.c_title }];
}, []);
})
.sort(sort_by_assignee_version_and_parent);
return rearrangement(all_requirement_data, "assignee", "assignee").map(group => ({
assignee: `${group[0].assignee} 총계`,
_assignee: group[0]._assignee,
col: 0,
colSpan: 3 + maxDepth,
root: "assignee",
node: "root",
children: rearrangement(group, "version", "version").reduce((acc, cur) => ([
...acc,
{
...cur[0],
children: cur.slice(1),
lastChild: {
assignee: `${cur[0].version}의 총계`,
_assignee: group[0]._assignee,
col: 0,
colSpan: 3 + maxDepth,
node: "leaf",
...calcStatus(cur)
}
}
]), []),
...calcStatus(group)
}));
}
}
makeElement(name) {
return document.createElement(name);
}
sortData(key, type) {
if (key === "category") {
type === "asc" && this.$data.sort((a, b) => a[key].localeCompare([key]));
type === "desc" && this.$data.sort((a, b) => b[key].localeCompare(a[key]));
} else if (["id", "progress"].includes(key)) {
type === "asc" && this.$data.sort((a, b) => a[key] - b[key]);
type === "desc" && this.$data.sort((a, b) => b[key] - a[key]);
} else if (["createDate", "startDate", "endDate"].includes(key)) {
type === "asc" && this.$data.sort((a, b) => (a[key] ? (b[key] ? new Date(a[key]) - new Date(b[key]) : -1) : 1));
type === "desc" && this.$data.sort((a, b) => (a[key] ? (b[key] ? new Date(b[key]) - new Date(a[key]) : -1) : 1));
} else {
type === "asc" &&
this.$data.sort((a, b) => (a[`_${key}`] ? (b[`_${key}`] ? a[`_${key}`] - b[`_${key}`] : -1) : 1));
type === "desc" &&
this.$data.sort((a, b) => (a[`_${key}`] ? (b[`_${key}`] ? b[`_${key}`] - a[`_${key}`] : -1) : 1));
}
const $tbody = document.getElementById("req_tbody");
$tbody.replaceChildren();
this.makeRow(this.$data, "td").forEach((r) => $tbody.append(r));
}
handleSorting(e, key) {
[...e.target.parentNode.childNodes]
.filter((node) => !node.classList.contains(key))
.forEach((node) => {
node.classList.remove("asc");
node.classList.remove("desc");
});
if (e.target.classList.contains("asc")) {
e.target.classList.remove("asc");
e.target.classList.add("desc");
this.sortData(key, "desc");
} else {
e.target.classList.remove("desc");
e.target.classList.add("asc");
this.sortData(key, "asc");
}
}
insertElement(root, list) {
list.forEach((row, index, arr) => {
if (index) arr[index - 1].after(row);
else root.after(row);
});
}
removeElement(data) {
let selector;
if(data.node === "root"){
selector = `[data-${data.root}="${data[`_${[data.root]}`]}"]`;
}else{
const assignee = `[data-assignee="${data._assignee}"]`;
const version = `[data-version="${data._version}"]`;
selector = `${assignee}${version}`;
}
const elements = document.querySelectorAll(selector);
if (data.node === "root") {
elements.forEach((el) => {
const buttons = el.querySelectorAll('.btn.pivot-toggle.active');
buttons.forEach((button) => {
button.classList.remove('active');
});
const keys = Object.keys(folderDepth);
keys.forEach(key => {
this.unmergeRoot(key,el);
});
});
}
Array.from(elements)
.filter((el, index) => index)
.forEach((row) => row.remove());
}
getElement(target, tag, className) {
if (className) {
return target.tagName === tag && target.classList.contains(className) && target;
}
return target.tagName !== tag ? this.getElement(target.parentElement, tag) : target;
}
makePivotButton($tr, data) {
const rows = this.makePivotRow(
data.children.flatMap((item) => {
if (item.lastChild) return [item, item.lastChild];
return item;
}),
"td"
);
const btn = this.makeElement("button");
const plusIcon = this.makeElement("i");
const minusIcon = this.makeElement("i");
btn.className = "btn pivot-toggle";
plusIcon.className = "fa fa-plus-square";
minusIcon.className = "fa fa-minus-square";
btn.appendChild(plusIcon);
btn.appendChild(minusIcon);
btn.addEventListener("click", (e) => {
btn.classList.toggle("active");
const keys = Object.keys(folderDepth);
if (btn.classList.contains("active")) {
this.insertElement(this.getElement(e.target, "TR"), rows);
keys.forEach(key => {
this.mergeRowsByClassName(key);
});
} else {
keys.forEach(key => {
this.unmergeRowsByClassName(key,this.getElement(e.target, "TR"));
});
this.removeElement(data);
}
});
return btn;
}
makeEditableButton(cur, key) {
const uuid = createUUID(); // Ensure a unique ID for each input element
const that = this;
const btn = this.makeElement("button");
const editIcon = this.makeElement("i");
btn.className = "btn pivot-toggle";
editIcon.className = "fa fa-pencil";
editIcon.style.marginRight = "10px";
btn.appendChild(editIcon);
const text = document.createTextNode(cur[key] ?? "");
btn.appendChild(text);
const tempText = cur[key] ;
btn.addEventListener("click", function(e) {
const input = that.makeElement("input");
input.id = uuid;
input.type = "text";
input.value = cur[key] ?? "";
input.className = "edit-input";
btn.parentNode.replaceChild(input, btn); // 버튼을 입력 필드로 바꿈
input.focus(); // 입력 필드에 자동으로 포커스를 줌
input.addEventListener("blur", () => {
console.log(input.value);
if (tempText !== input.value){
cur[key] = input.value;
const updatedBtn = that.makeEditableButton(cur, key);
input.parentNode.replaceChild(updatedBtn, input);
editContents.content = cur[key];
that.updateData(cur['id'],editContents);
}else{
cur[key] = input.value;
const updatedBtn = that.makeEditableButton(cur, key);
input.parentNode.replaceChild(updatedBtn, input);
}
});
input.addEventListener("keyup", function(e) {
if (e.key === "Enter") {
input.blur(); // 엔터 키를 누르면 blur 이벤트를 강제로 호출하여 업데이트 처리를 함
}
});
});
return btn;
}
makePivotRow(rows, tag) {
return rows.reduce((acc, cur) => {
const $tr = this.makeElement("tr");
$tr.className ="reqItems";
$tr.setAttribute("data-id", cur.id ?? "");
$tr.setAttribute("data-version", cur._version ?? "");
$tr.setAttribute("data-assignee", cur._assignee ?? "");
const radioButtonName = `status_${cur.id}`;
Object.keys(ContentType[pivotType]).forEach((key, index) => {
const $col = this.makeElement(tag);
$col.className = key;
if (req_state_map[ReqStatus[key]] && req_state_map[ReqStatus[key]].reqStateCategoryEntity) {
$col.className = req_state_category_class_map[req_state_map[ReqStatus[key]].reqStateCategoryEntity.c_id];
$col.setAttribute('data-title', key);
}
if(['content'].includes($col.className) && (tag !== "th")){
$col.prepend(this.makeEditableButton(cur, key));
}
else if (cur[key]) {
$col.innerHTML = cur[key];
}
if (tag === "th") {
$tr.appendChild($col);
return;
}
if (cur.col !== undefined && cur.col === index) {
$col.setAttribute("colspan", cur.colSpan);
}
if (index > cur.col && index < cur.col + cur.colSpan) {
$col.classList.add("remove");
}
if (cur.colSpan) {
$tr.classList.add("highlight");
if(cur.node ==="leaf"){
$tr.style.backgroundColor ="#4a4a4a";
}
if (pivotType === "version"){
$tr.removeAttribute('data-assignee');
}
}
else {
var data_title_value = $col.getAttribute('data-title');
if (data_title_value) {
const checkedAttribute = $col.innerHTML === "1" ? " checked" : "";
$col.innerHTML = ``;
}
}
if (['assignee'].includes($col.className) && $col.innerHTML === "담당자 미배정") {
$col.style.color = "red";
}
if (cur.root === key) {
if (cur.children.length !== 0) {
$col.prepend(this.makePivotButton($tr, cur));
}
$col.classList.add("root");
}
if(['assignee', 'version'].includes($col.className) && !cur.colSpan){
$col.innerHTML = ``;
}
!$col.classList.contains("remove") && $tr.appendChild($col);
});
return [...acc, $tr];
}, []);
}
makeRow(rows, tag) {
return rows.reduce((acc, cur) => {
const $tr = this.makeElement("tr");
$tr.className ="reqItems";
$tr.setAttribute("data-id", cur.id);
$tr.setAttribute("data-version", cur._version ?? "");
Object.keys(ContentType[pivotType]).forEach((key) => {
if ([`_${key}`].includes(key)) return;
const $col = this.makeElement(tag);
$col.className = key;
if (tag === "td") {
if ((["status"].includes(key) && cur.category !== "Group")) {
let iconData;
if (cur["_state_category"]) {
// 카테고리별 아이콘 적용
iconData = cur["_state_category"] + " " + cur[key];
}
else {
// 카테고리 미적용 상태는 아이콘 X
iconData = cur[key];
}
$col.innerHTML = `
${iconData}
`;
$col.style = "text-align:left";
}
else if( ["priority"].includes(key)){
const iconData = this.mappingPriorityIcon(cur[key]);
$col.innerHTML = `
${iconData}
`;
$col.style = "text-align:left";
}
else if( ["difficulty"].includes(key)){
const iconData = this.mappingDifficultyIcon(cur[key]);
$col.innerHTML = `
${iconData}
`;
$col.style = "text-align:left";
}
else if(['content'].includes($col.className)){
$col.prepend(this.makeEditableButton(cur, key));
}else if(['assignee'].includes($col.className) ){
let 전체_담당자 = cur[key];
전체_담당자 = 전체_담당자.split(', ').sort();
전체_담당자 = Array.from(new Set(전체_담당자));
$col.innerHTML = 전체_담당자;
$col.style = "text-align:left";
}else {
$col.innerHTML = cur[key];
}
} else {
$col.innerHTML = cur[key];
}
// 담당자 미지정일 경우 빨간색으로 표시
if (['assignee'].includes($col.className) && $col.innerHTML === "담당자 미배정") {
$col.style.color = "red";
}
$tr.appendChild($col);
});
return [...acc, $tr];
}, []);
}
mappingPriorityIcon(key){
if(key ==="매우 높음"){
return ' 매우 높음';
}else if(key ==="높음"){
return ' 높음';
}else if(key ==="중간"){
return ' 중간';
}else if(key ==="낮음"){
return ' 낮음';
}else if(key ==="매우 낮음"){
return ' 매우 낮음';
}
}
mappingDifficultyIcon(key){
if(key ==="매우 어려움"){
return ' 매우 어려움';
}else if(key ==="어려움"){
return ' 어려움';
}else if(key ==="보통"){
return ' 보통';
}else if(key ==="쉬움"){
return ' 쉬움';
}else if(key ==="매우 쉬움"){
return ' 매우 쉬움';
}
}
updateData(reqId, editContents) {
// 초기 데이터 객체 생성
let dataToSend = {
c_id: reqId,
c_req_update_date: new Date()
};
// 조건 검사 후 조건을 만족하는 경우에만 프로퍼티 추가
if(editContents.statusId) {
const reqData = res.find(item => item.c_id == reqId);
const c_req_pdservice_versionset_link = reqData ? reqData.c_req_pdservice_versionset_link : null;
dataToSend.c_req_state_link = editContents.statusId;
dataToSend.c_req_pdservice_versionset_link = c_req_pdservice_versionset_link;
dataToSend.c_title = reqData.c_title;
}
if(editContents.priorityId) dataToSend.c_req_priority_link = editContents.priorityId;
if(editContents.difficultyId) dataToSend.c_req_difficulty_link = editContents.difficultyId;
if(editContents.content) dataToSend.c_title = editContents.content;
// startDate와 endDate 값 검증 후 객체에 추가
if(editContents.startDate && !isNaN(new Date(editContents.startDate).getTime())) {
dataToSend.c_req_start_date = new Date(editContents.startDate);
}
if(editContents.endDate && !isNaN(new Date(editContents.endDate).getTime())) {
dataToSend.c_req_end_date = new Date(editContents.endDate);
}
if(dataToSend.c_req_state_link || dataToSend.c_req_priority_link || dataToSend.c_req_difficulty_link || dataToSend.c_req_start_date || dataToSend.c_req_end_date) {
// DB까지만 변경 필요한 경우
tableOptions.onDBUpdate(tableOptions.id, dataToSend);
}
else {
const versionSetLink = res.find(item => item.c_id === reqId);
tableOptions.onUpdate(tableOptions.id, {
c_id: reqId,
c_title: editContents.content ?? null,
c_req_pdservice_versionset_link : versionSetLink ? versionSetLink.c_req_pdservice_versionset_link : null
});
}
}
addInput(node, updateKey, type = text) {
const uuid = createUUID();
const text = node.textContent;
const $input = this.makeElement("input");
$input.setAttribute("type", type);
$input.id = uuid;
$input.addEventListener("blur", () => {
this.updateData(this.getElement(node, "TR").dataset.id, updateKey, $input.value);
node.textContent = $input.value;
});
node.textContent = "";
node.appendChild($input);
document.getElementById(uuid).value = text;
document.getElementById(uuid).focus();
}
addDateInput(node, updateKey) {
const uuid = createUUID();
const text = node.textContent;
const version = this.getElement(node, "TR").dataset.version
const versionData = versionList.find(item => item.c_id === Number(version));
const versionStartDate = getDate(versionData.c_pds_version_start_date);
const versionEndDate = getDate(versionData.c_pds_version_end_date);
const $input = this.makeElement("input");
$input.setAttribute("type", "date");
$input.setAttribute("min",versionStartDate);
$input.setAttribute("max",versionEndDate);
$input.id = uuid;
$input.addEventListener("blur", () => {
if(updateKey === "startDate"){
editContents.startDate = $input.value; // 요구사항 시작일
}else if(updateKey === "endDate"){
editContents.endDate = $input.value;
}
this.updateData(this.getElement(node, "TR").dataset.id, editContents);
node.textContent = $input.value;
});
node.textContent = "";
node.appendChild($input);
document.getElementById(uuid).value = text;
document.getElementById(uuid).focus();
}
setOptions(name, text, uuid) {
let options;
let keyname;
switch (name) {
case "difficulty":
options = ReqDifficulty;
keyname = "_difficulty";
break;
case "priority":
options = ReqPriority;
keyname = "_priority";
break;
case "status":
default:
options = ReqStatus;
keyname = "_status";
break;
}
return Object.entries(options).reduce((acc, [name, value]) => {
const $li = this.makeElement("li");
const label = name.replace("_", " ");
let dropdownIconData ="";
$li.className = text.trim() === label ? "active" : "";
$li.innerHTML = `${label}`;
$li.addEventListener("click", (e) => {
if (keyname === "_status") {
editContents.statusId = value;
if (req_state_map[value].reqStateCategoryEntity) {
dropdownIconData = req_state_map[value].reqStateCategoryEntity.c_category_icon + " " + e.target.textContent;
}
else {
dropdownIconData = e.target.textContent;
}
}
else if (keyname === "_priority") {
editContents.priorityId = value;
dropdownIconData = this.mappingPriorityIcon(e.target.textContent);
}
else if (keyname === "_difficulty") {
editContents.difficultyId = value;
dropdownIconData = this.mappingDifficultyIcon(e.target.textContent);
}
this.updateData($li.parentElement.parentElement.parentElement.dataset.id,editContents);
$li.parentElement.previousElementSibling.innerHTML = `${dropdownIconData} `;
$("#" + uuid).empty();
});
return [...acc, $li];
}, []);
}
addSelect(node) {
const uuid = createUUID();
const $ul = this.makeElement("ul");
$ul.id = uuid;
$ul.className = "dropdown-menu req-select";
this.setOptions(node.parentElement.className, node.textContent, uuid).forEach((item) => $ul.append(item));
node.parentElement.appendChild($ul);
}
bindHeadEvent($el) {
$el.addEventListener("click", (e) => {
!["assignee", "depth1", "depth2", "depth3", "content"].includes(e.target.className) &&
this.handleSorting(e, e.target.classList.item(0));
});
}
bindBodyEvent($el) {
$el.addEventListener("click", (e) => {
const { tagName, classList, parentElement } = e.target;
const $assignee = this.getElement(e.target, "TD", "assignee");
const $content = this.getElement(e.target, "TD", "content");
const $progress = this.getElement(e.target, "TD", "progress");
const $createDate = this.getElement(e.target, "TD", "createDate");
const $startDate = this.getElement(e.target, "TD", "startDate");
const $endDate = this.getElement(e.target, "TD", "endDate");
const tdElement = e.target.closest("td");
const trElement = e.target.closest("tr");
if ($startDate) {
this.addDateInput($startDate, "startDate");
return;
}
if ($endDate) {
console.log($endDate);
this.addDateInput($endDate, "endDate");
return;
}
if (e.target.type === "radio") {
editContents.statusId = ReqStatus[tdElement.getAttribute("data-title")];
this.updateData(trElement.dataset.id, editContents);
}
// select a 태그(상태, 난이도, 우선순위 명칭)
if (["A"].includes(tagName)) {
const dropdown_menu = $(e.target).nextAll('.dropdown-menu');
dropdown_menu.remove();
classList.contains("dropdown-toggle") && this.addSelect(e.target);
tagName === "I" && this.addSelect(parentElement);
}
// select i 태그(상태, 난이도, 우선순위 우측 드롭다운 아이콘)
else if (["I"].includes(tagName)) {
const dropdown_menu = $(parentElement).nextAll('.dropdown-menu');
dropdown_menu.remove();
classList.contains("dropdown-toggle") && this.addSelect(e.target);
tagName === "I" && this.addSelect(parentElement);
}
});
}
makeSection(rowData, name, col) {
const $el = this.makeElement(name);
$el.id = `req_${name}`;
$el.className = name;
if (pivotType === "normal") this.makeRow(rowData, col).forEach((r) => $el.append(r));
else this.makePivotRow(rowData, col).forEach((r) => $el.append(r));
if ("thead" === name) this.bindHeadEvent($el);
else this.bindBodyEvent($el);
return $el;
}
makeTable() {
this.$table.className = `reqTable ${pivotType !== "normal" ? "pivotTable" : ""}`;
this.$table.appendChild(this.makeSection([ContentType[pivotType]], "thead", "th"));
this.$table.appendChild(this.makeSection(this.$data, "tbody", "td"));
return this.$table;
}
rendering() {
const $wrapper = document.getElementById(tableOptions.wrapper);
$wrapper.innerHTML = null;
$wrapper.appendChild(this.makeTable());
}
unmergeRowsByClassName(className,element) {
const tables = document.querySelectorAll('.reqTable');
const dataVersion = element.getAttribute('data-version');
const dataAssignee = element.getAttribute('data-assignee');
tables.forEach((table) => {
Array.from(table.querySelectorAll('tbody tr')).forEach((row) => {
const rowDataVersion = row.getAttribute('data-version');
const rowDataAssignee = row.getAttribute('data-assignee');
const cell = row.querySelector(`.${className}`);
if (rowDataVersion === dataVersion && rowDataAssignee === dataAssignee) {
const cell = row.querySelector(`.${className}`);
if (cell) {
// 숨겼던 셀을 다시 보이게 함
cell.style.display = '';
// 적용했던 배경색 제거
cell.style.backgroundColor = '';
// rowSpan 값을 기본값으로 재설정
cell.rowSpan = 1;
}
}
});
});
}
unmergeRoot(className,element) {
console.log(className);console.log(element);
const tables = document.querySelectorAll('.reqTable');
const dataVersion = element.getAttribute('data-version');
const dataAssignee = element.getAttribute('data-assignee');
tables.forEach((table) => {
Array.from(table.querySelectorAll('tbody tr')).forEach((row) => {
const rowDataVersion = row.getAttribute('data-version');
const rowDataAssignee = row.getAttribute('data-assignee');
const cell = row.querySelector(`.${className}`);
if (rowDataVersion === dataVersion || rowDataAssignee === dataAssignee) {
const cell = row.querySelector(`.${className}`);
if (cell) {
cell.style.display = '';
cell.style.backgroundColor = '';
cell.rowSpan = 1;
}
}
});
});
}
mergeRowsByClassName(className) {
const tables = document.querySelectorAll('.reqTable');
tables.forEach((table) => {
let lastValue = null;
let lastRow = null;
let lastDataVersion = null;
let lastDataAssignee = null;
let rowspan = 1;
let groupIndex = 0; // 그룹 인덱스 추가
Array.from(table.querySelectorAll('tbody tr')).forEach((row) => {
const cell = row.querySelector(`.${className}`);
const value = cell ? cell.textContent : '';
const dataVersion = row.getAttribute('data-version');
const dataAssignee = row.getAttribute('data-assignee');
if (cell) {
if (value === lastValue && dataVersion === lastDataVersion && dataAssignee === lastDataAssignee) {
cell.style.display = 'none'; // 셀 병합 시 숨김 처리
if (lastRow) {
const cellInLastRow = lastRow.querySelector(`.${className}`);
if (cellInLastRow) {
cellInLastRow.rowSpan = rowspan + 1;
// 병합되는 첫 번째 셀에만 배경색 적용
if (groupIndex % 2 === 0) { // 짝수 그룹에 대해 배경색 적용
cellInLastRow.style.backgroundColor = 'rgba(51, 51, 51, 0.325)';
}
}
}
rowspan++;
} else {
rowspan = 1;
lastValue = value;
lastDataVersion = dataVersion;
lastDataAssignee = dataAssignee;
lastRow = row;
groupIndex++; // 새 그룹이 시작될 때마다 그룹 인덱스 증가
}
}
});
});
}
rerenderTable() {
this.$data = this.setTableData();
this.$table.innerHTML = "";
this.makeTable();
}
}
const makeReqTable = async (options) => {
// 선택옵션
tableOptions = options;
// 버전 정보
const data = await tableOptions.onGetVersion(tableOptions.id);
// 요구사항 담당자 정보
const assigneeData =await tableOptions.onGetReqAssignee(tableOptions.id);
const assigneeResponse = assigneeData.response;
// 요구사항 정보
res = await tableOptions.onGetData(tableOptions.id);
// 담당자 정보 + 요구사항 정보
res = mergeData(res,assigneeResponse);
versionList = data.response.sort((a, b) => a.c_id - b.c_id);
// 최대 깊이 조회
const maxLevel = Math.max(...res.map(task => task.c_level));
maxDepth = maxLevel - 2;
// 테이블 데이터 생성
tableData = mapperTableData([...res]);
pivotTableData = mapperPivotTableData([...res]);
TableInstance = new Table();
TableInstance.rendering();
TableInstance.mergeRowsByClassName('version');
const keys = Object.keys(folderDepth);
keys.forEach(key => {
TableInstance.mergeRowsByClassName(key);
});
};
const changeTableType = (type) => {
pivotType = type;
TableInstance.rerenderTable();
tableSelect(tableOptions.id);
TableInstance.mergeRowsByClassName('version');
const keys = Object.keys(folderDepth);
keys.forEach(key => {
TableInstance.mergeRowsByClassName(key);
});
};
const mergeData = (res, assigneeResponse) =>{
return res.map(res => {
const matchedAssignees = assigneeResponse.filter(assignee => assignee.요구사항_아이디 == res.c_id);
return { ...res, assignee: matchedAssignees };
});
}