diff --git a/incoming-files/mh.html b/incoming-files/mh.html index 839814f..8440e78 100644 --- a/incoming-files/mh.html +++ b/incoming-files/mh.html @@ -1481,7 +1481,8 @@ const RANK_WEIGHTS = { '수석': 1, '책임': 2, '선임': 3, '연구원': 4 }; - let teamData = []; + let teamData = []; + const ALL_TEAM_VALUE = '__ALL__'; let allTeams = []; let allPeopleData = []; let searchTeams = []; @@ -1637,16 +1638,20 @@ const buildScopedPeopleData = (rows) => { const seen = new Set(); return rows.map(r => ({ - name: String(r[columnMap.name] || '').trim(), - team: String(r[columnMap.team] || '').trim() - })).filter(p => { - if (!p.name || !p.team) return false; - const key = `${p.name}-${p.team}`; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - }; + name: String(r[columnMap.name] || '').trim(), + team: String(r[columnMap.team] || '').trim() + })).filter(p => { + if (!p.name || !p.team) return false; + const key = `${p.name}-${p.team}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }).map(p => ({ + ...p, + value: `${p.name}|${p.team}`, + label: `${p.name} (${p.team})` + })); + }; const findBizIndexByProject = (normalizedHeaders, projIdx) => { const bizKey = normalizeHeader('사업 종류'); for (let i = projIdx - 1; i >= 0; i--) { @@ -2465,23 +2470,23 @@ searchTeams = [...new Set(allRows.map(r => String(r[columnMap.team] || '').trim()))].filter(Boolean).sort(); searchPeopleData = buildScopedPeopleData(allRows); - teamSelect.innerHTML = '' + allTeams.map(t => ``).join(''); - - if (currentTeam && allTeams.includes(currentTeam)) { - teamSelect.value = currentTeam; - } else if (allTeams.length > 0) { - teamSelect.value = allTeams[0]; - } else { - teamSelect.value = ''; - } + teamSelect.innerHTML = `` + allTeams.map(t => ``).join(''); + + if (currentTeam === ALL_TEAM_VALUE) { + teamSelect.value = ALL_TEAM_VALUE; + } else if (currentTeam && allTeams.includes(currentTeam)) { + teamSelect.value = currentTeam; + } else { + teamSelect.value = ALL_TEAM_VALUE; + } updateFilters(); - if (teamSelect.value && currentPerson && allPeopleData.some(p => p.team === teamSelect.value && p.name === currentPerson)) { - personSelect.value = currentPerson; - } else { - personSelect.value = ''; - } + if (currentPerson && [...personSelect.options].some(option => option.value === currentPerson)) { + personSelect.value = currentPerson; + } else { + personSelect.value = ''; + } } function setScope(scope, preserveSelection = true) { @@ -2621,7 +2626,10 @@ setScope(getTeamScope(item.dataset.team), false); teamSelect.value = item.dataset.team; updateFilters(); - personSelect.value = item.dataset.name; + const targetValue = `${item.dataset.name}|${item.dataset.team}`; + personSelect.value = [...personSelect.options].some(option => option.value === targetValue) + ? targetValue + : item.dataset.name; } mainSearchInput.value = ""; @@ -2646,19 +2654,25 @@ - function updateFilters() { - if (teamData.length === 0) return; - const team = document.getElementById('team-select').value; - const personSelect = document.getElementById('person-select'); - if (!team) { + function updateFilters() { + if (teamData.length === 0) return; + const team = document.getElementById('team-select').value; + const personSelect = document.getElementById('person-select'); + if (!team) { document.getElementById('main-title').innerText = ''; - personSelect.innerHTML = ''; - return; - } - document.getElementById('main-title').innerText = team; - const teamPeople = [...new Set(getScopedRows().filter(r => String(r[columnMap.team] || '').trim() === team).map(r => String(r[columnMap.name] || '').trim()))].filter(Boolean).sort(); - personSelect.innerHTML = '' + teamPeople.map(p => ``).join(''); - } + personSelect.innerHTML = ''; + return; + } + if (team === ALL_TEAM_VALUE) { + document.getElementById('main-title').innerText = '전체'; + const allPeople = buildScopedPeopleData(getScopedRows()); + personSelect.innerHTML = '' + allPeople.map(p => ``).join(''); + return; + } + document.getElementById('main-title').innerText = team; + const teamPeople = [...new Set(getScopedRows().filter(r => String(r[columnMap.team] || '').trim() === team).map(r => String(r[columnMap.name] || '').trim()))].filter(Boolean).sort(); + personSelect.innerHTML = '' + teamPeople.map(p => ``).join(''); + } function resetMhView() { if (teamData.length < 2) return; @@ -2759,14 +2773,17 @@ const latePeople = new Set(); const latePeopleMap = {}; - teamData.slice(1).forEach(row => { - const d = dStr(row[columnMap.date]); - if (!d || d < startStr || d > endStr || !isRowInScope(row) || String(row[columnMap.team] || '').trim() !== teamF) return; + teamData.slice(1).forEach(row => { + const d = dStr(row[columnMap.date]); + const rowTeam = String(row[columnMap.team] || '').trim(); + if (!d || d < startStr || d > endStr || !isRowInScope(row) || (teamF !== ALL_TEAM_VALUE && rowTeam !== teamF)) return; const lateFlagText = String(row[columnMap.lateFlag] || '').trim(); const userStateText = String(row[columnMap.userState] || '').trim(); const isWeekend = lateFlagText.includes("\uC8FC\uB9D0"); const name = String(row[columnMap.name] || '').trim(); const rank = String(row[columnMap.rank] || '연구원').trim(); + const personKey = teamF === ALL_TEAM_VALUE ? `${name}|${rowTeam}` : name; + const personLabel = teamF === ALL_TEAM_VALUE ? `${name} (${rowTeam})` : name; const hasSourceCostCols = columnMap.normalCost >= 0 || columnMap.extraCost >= 0; const sourceNormalLaborCost = columnMap.normalCost >= 0 ? parseNumber(row[columnMap.normalCost]) : 0; const sourceExtraLaborCost = columnMap.extraCost >= 0 ? parseNumber(row[columnMap.extraCost]) : 0; @@ -2783,24 +2800,24 @@ stats.normalLaborCost += normalLaborCost; stats.extraLaborCost += extraLaborCost; stats.laborCost += normalLaborCost + extraLaborCost; - if (!pMap[name]) pMap[name] = { name, rank, normal: 0, extra: 0, holiday: 0, extraCount: 0, holidayCount: 0, total: 0, projects: {} }; + if (!pMap[personKey]) pMap[personKey] = { name, team: rowTeam, displayName: personLabel, personKey, rank, normal: 0, extra: 0, holiday: 0, extraCount: 0, holidayCount: 0, total: 0, projects: {} }; slots.forEach(s => { const val = parseNumber(row[s.time]); const projectCode = (s.code >= 0 ? String(row[s.code] || '') : '').trim(); const pName = String(row[s.proj] || '').trim(); const biz = (s.biz >= 0 ? String(row[s.biz] || '') : '').trim() || '공통'; if (val > 0 && pName) { - const ownershipInfo = getProjectOwnership(teamF, projectCode); - stats.totalMH += val; pMap[name].total += val; - let type = 'normal'; - if (isWeekend) { type = 'holiday'; pMap[name].holiday += val; pMap[name].holidayCount += 1; stats.extraHolMH += val; } + const ownershipInfo = getProjectOwnership(teamF === ALL_TEAM_VALUE ? rowTeam : teamF, projectCode); + stats.totalMH += val; pMap[personKey].total += val; + let type = 'normal'; + if (isWeekend) { type = 'holiday'; pMap[personKey].holiday += val; pMap[personKey].holidayCount += 1; stats.extraHolMH += val; } - else if (s.extra) { type = 'extra'; pMap[name].extra += val; pMap[name].extraCount += 1; stats.extraHolMH += val; } - - else { pMap[name].normal += val; stats.normalMH += val; } - - if (!pMap[name].projects[pName]) { - pMap[name].projects[pName] = { + else if (s.extra) { type = 'extra'; pMap[personKey].extra += val; pMap[personKey].extraCount += 1; stats.extraHolMH += val; } + + else { pMap[personKey].normal += val; stats.normalMH += val; } + + if (!pMap[personKey].projects[pName]) { + pMap[personKey].projects[pName] = { normal: 0, extra: 0, holiday: 0, @@ -2812,11 +2829,11 @@ }; } - pMap[name].projects[pName].total += val; pMap[name].projects[pName][type] += val; - if (type === 'extra') pMap[name].projects[pName].extraCount += 1; - if (type === 'holiday') pMap[name].projects[pName].holidayCount += 1; - if (!pMap[name].projects[pName].firstDate || d < pMap[name].projects[pName].firstDate) pMap[name].projects[pName].firstDate = d; - if (!pMap[name].projects[pName].lastDate || d > pMap[name].projects[pName].lastDate) pMap[name].projects[pName].lastDate = d; + pMap[personKey].projects[pName].total += val; pMap[personKey].projects[pName][type] += val; + if (type === 'extra') pMap[personKey].projects[pName].extraCount += 1; + if (type === 'holiday') pMap[personKey].projects[pName].holidayCount += 1; + if (!pMap[personKey].projects[pName].firstDate || d < pMap[personKey].projects[pName].firstDate) pMap[personKey].projects[pName].firstDate = d; + if (!pMap[personKey].projects[pName].lastDate || d > pMap[personKey].projects[pName].lastDate) pMap[personKey].projects[pName].lastDate = d; if (!mMap[biz]) mMap[biz] = {}; @@ -2832,7 +2849,7 @@ if (!mMap[biz][pName].ownership) mMap[biz][pName].ownership = ownershipInfo.ownership; else if (ownershipInfo.ownership === '\uC8FC\uAD00') mMap[biz][pName].ownership = ownershipInfo.ownership; - mMap[biz][pName].total += val; mMap[biz][pName].ps.add(name); + mMap[biz][pName].total += val; mMap[biz][pName].ps.add(personLabel); const rKey = rank.includes("수석") ? "수석" : rank.includes("책임") ? "책임" : rank.includes("선임") ? "선임" : "연구원"; @@ -2852,17 +2869,17 @@ const wb = RANK_WEIGHTS[b.rank.includes('수석') ? '수석' : b.rank.includes('책임') ? '책임' : b.rank.includes('선임') ? '선임' : '연구원'] || 99; - return (wa !== wb) ? wa - wb : a.name.localeCompare(b.name, 'ko'); + return (wa !== wb) ? wa - wb : (a.displayName || a.name).localeCompare((b.displayName || b.name), 'ko'); }); stats.personCount = teamList.length; - stats.memberNames = teamList.map(p => p.name); - stats.lateNames = teamList.map(p => p.name).filter(name => latePeople.has(name)); - stats.lateDetails = stats.lateNames.map(name => ({ - name, - label: `${name}(${latePeopleMap[name]?.count || 0})`, - firstDate: latePeopleMap[name]?.firstDate || '' + stats.memberNames = teamList.map(p => ({ name: p.name, label: p.displayName || p.name })); + stats.lateNames = teamList.filter(p => latePeople.has(p.name)).map(p => p.displayName || p.name); + stats.lateDetails = teamList.filter(p => latePeople.has(p.name)).map(p => ({ + name: p.name, + label: `${p.displayName || p.name}(${latePeopleMap[p.name]?.count || 0})`, + firstDate: latePeopleMap[p.name]?.firstDate || '' })); stats.lateCount = stats.lateNames.length; @@ -2870,8 +2887,11 @@ - const personRows = teamList.map(p => ({ + const personRows = teamList.map(p => ({ name: p.name, + team: p.team, + displayName: p.displayName || p.name, + personKey: p.personKey || p.name, rank: p.rank, normal: p.normal, extra: p.extra, @@ -3218,7 +3238,7 @@ targetBadge.classList.add('hidden'); iconEl.setAttribute('data-lucide', 'layout-grid'); - const rows = (personF ? personRows.filter(p => p.name === personF) : personRows) || []; + const rows = (personF ? personRows.filter(p => p.personKey === personF || p.name === personF) : personRows) || []; if (!rows.length) { container.innerHTML = '
표시할 데이터가 없습니다.
'; return; @@ -3278,7 +3298,7 @@ const over = total > summaryTargetH; const overHour = Math.max(0, total - summaryTargetH); const projects = person.projects || []; - const personKey = encodeURIComponent(`${person.name}|${person.rank}`); + const personKey = encodeURIComponent(`${person.personKey || person.name}|${person.rank}`); const isOpen = personF ? true : personChartOpenState[personKey] === true; return `
@@ -3286,7 +3306,7 @@
- ${person.name} ${person.rank}${over ? `초과 ${formatHour(overHour)}h` : ''} + ${person.displayName || person.name} ${person.rank}${over ? `초과 ${formatHour(overHour)}h` : ''}
diff --git a/legacy/static/organization.css b/legacy/static/organization.css index 6c26c97..cd7a8fd 100644 --- a/legacy/static/organization.css +++ b/legacy/static/organization.css @@ -817,6 +817,20 @@ body { flex-wrap: wrap; } +.list-toolbar-group { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.list-toolbar-divider { + width: 1px; + align-self: stretch; + min-height: 36px; + background: #dbe2ea; +} + .list-mode-btn { border: 1px solid #c7d2fe; background: #eef2ff; diff --git a/legacy/static/organization.js b/legacy/static/organization.js index 2cafb9b..548482b 100644 --- a/legacy/static/organization.js +++ b/legacy/static/organization.js @@ -1393,12 +1393,16 @@ function openListViewModal(event) { fieldsArea.innerHTML = `
- -
+
+ +
+ +
-
+ +
~