Fix project comparison consistency and labor input handling

This commit is contained in:
b17301
2026-04-29 20:30:33 +09:00
parent 3bf48c42db
commit 7afd2680d3
5 changed files with 2157 additions and 188 deletions
+120 -16
View File
@@ -333,6 +333,10 @@
min-width: 0;
}
.pc-selected-card[hidden] {
display: none;
}
.pc-selected-head {
display: flex;
align-items: center;
@@ -452,6 +456,16 @@
min-height: 32px;
}
.pc-chip-link {
color: inherit;
text-decoration: none;
font-weight: 700;
}
.pc-chip-link:hover {
text-decoration: underline;
}
.pc-chip button {
border: none;
background: transparent;
@@ -463,6 +477,33 @@
cursor: pointer;
}
.pc-related-include {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
min-width: 18px;
height: 18px;
border-radius: 999px;
border: 1px solid rgba(217, 221, 227, 0.9);
background: #fff;
color: var(--muted);
font-size: 12px;
font-weight: 800;
padding: 0;
line-height: 1;
}
.pc-related-include.is-active {
color: #1f6f3d;
background: #eefbf3;
}
.pc-related-include:disabled {
opacity: 0.42;
cursor: default;
}
.pc-related-form {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
@@ -686,12 +727,12 @@
<div class="pc-toolbar">
<div class="pc-tab-row">
<div class="pc-source-tabs">
<a href="/process-cost?source=hanmac{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}{% if process_cost_include_related %}&include_related=1{% endif %}" class="{% if process_cost_source == 'hanmac' %}active{% endif %}">Hanmac ERP</a>
<a href="/process-cost?source=wehago{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}{% if process_cost_include_related %}&include_related=1{% endif %}" class="{% if process_cost_source == 'wehago' %}active{% endif %}">WEHAGO</a>
<a href="/process-cost?source=hanmac{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}{% if process_cost_include_related %}&include_related=1&active_related={% if process_cost_active_related_codes %}{{ process_cost_active_related_codes|join(',') }}{% elif process_cost_related_codes %}{{ process_cost_related_codes|join(',') }}{% else %}-{% endif %}{% endif %}" class="{% if process_cost_source == 'hanmac' %}active{% endif %}">Hanmac ERP</a>
<a href="/process-cost?source=wehago{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}{% if process_cost_include_related %}&include_related=1&active_related={% if process_cost_active_related_codes %}{{ process_cost_active_related_codes|join(',') }}{% elif process_cost_related_codes %}{{ process_cost_related_codes|join(',') }}{% else %}-{% endif %}{% endif %}" class="{% if process_cost_source == 'wehago' %}active{% endif %}">WEHAGO</a>
</div>
<div class="pc-source-tabs">
<a href="/process-cost?source={{ process_cost_source }}{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}" class="{% if not process_cost_include_related %}active{% endif %}">연계 미반영</a>
<a href="/process-cost?source={{ process_cost_source }}{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}&include_related=1" class="{% if process_cost_include_related %}active{% endif %}">연계 반영</a>
<a href="/process-cost?source={{ process_cost_source }}{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}{% if process_cost_selected_code %}&code={{ process_cost_selected_code }}{% endif %}&include_related=1&active_related={% if process_cost_active_related_codes %}{{ process_cost_active_related_codes|join(',') }}{% elif process_cost_related_codes %}{{ process_cost_related_codes|join(',') }}{% else %}-{% endif %}" class="{% if process_cost_include_related %}active{% endif %}">연계 반영</a>
</div>
</div>
<form method="get" action="/process-cost" class="pc-filter">
@@ -699,6 +740,7 @@
<input type="hidden" name="code" value="{{ process_cost_selected_code }}">
{% if process_cost_include_related %}
<input type="hidden" name="include_related" value="1">
<input type="hidden" name="active_related" value="{% if process_cost_active_related_codes %}{{ process_cost_active_related_codes|join(',') }}{% else %}-{% endif %}">
{% endif %}
<select name="start_year" aria-label="시작 연도 선택" id="processCostStartYear">
{% if not process_cost_years %}
@@ -758,7 +800,7 @@
<div class="pc-project-dropdown" id="projectList" hidden>
{% for item in process_cost_projects %}
<a
href="/process-cost?source={{ process_cost_source }}{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}&code={{ item.support_dept_code }}{% if process_cost_include_related %}&include_related=1{% endif %}"
href="/process-cost?source={{ process_cost_source }}{% if process_cost_selected_start_year %}&start_year={{ process_cost_selected_start_year }}{% endif %}{% if process_cost_selected_end_year %}&end_year={{ process_cost_selected_end_year }}{% endif %}&code={{ item.support_dept_code }}{% if process_cost_include_related %}&include_related=1&active_related={% if process_cost_active_related_codes %}{{ process_cost_active_related_codes|join(',') }}{% else %}-{% endif %}{% endif %}"
class="pc-project-item {% if process_cost_selected_code == item.support_dept_code %}active{% endif %}"
data-project-search="{{ (item.support_dept_code ~ ' ' ~ item.support_dept_name ~ ' ' ~ (item.client_name or ''))|lower }}"
data-project-kind="{{ item.project_kind_code|lower }}"
@@ -777,8 +819,8 @@
<div class="pc-note" id="projectListEmpty" hidden style="padding: 12px;">검색 결과가 없습니다.</div>
</div>
</div>
{% if selected_project %}
<div class="pc-selected-card">
{% if selected_project and (selected_project.support_dept_code not in (process_cost_quick_link_codes or [])) %}
<div class="pc-selected-card" id="selectedProjectCard">
<div class="pc-selected-head">
<div class="pc-selected-title">{{ selected_project.support_dept_code }}</div>
<button
@@ -835,7 +877,7 @@
<span class="meta">목표원가 {{ "{:,.0f}".format(overview.target_cost_amount|default(0)) }}원</span>
</article>
<article class="pc-card">
<span class="label">예상 영업수지</span>
<span class="label">영업수지</span>
<strong class="value">{{ "{:,.0f}".format(overview.profit_amount|default(0)) }}원</strong>
<span class="meta">마진율 {{ "{:.1f}".format(overview.profit_rate|default(0)) }}%</span>
</article>
@@ -872,14 +914,14 @@
</section>
<section class="pc-block">
<h3>설계 프로세스별 원가 현황</h3>
<h3>지출 내역</h3>
<div class="pc-table-wrap">
<table class="pc-table">
<thead>
<tr>
<th>구분</th>
<th>목표 원가</th>
<th>제 집행</th>
<th>지출</th>
<th>차이(목표-실제)</th>
<th>집행률</th>
</tr>
@@ -905,13 +947,13 @@
</section>
<section class="pc-block">
<h3>계정별 집행 상위 항목</h3>
<h3>지출 상위 항목</h3>
<div class="pc-table-wrap">
<table class="pc-table">
<thead>
<tr>
<th>계정</th>
<th>집행금액</th>
<th>실지출</th>
<th>비중</th>
<th>전표건수</th>
<th>최근 증빙일</th>
@@ -1007,6 +1049,7 @@
const selectedCode = {{ process_cost_selected_code | tojson }};
const selectedProject = {{ (process_cost_selected_project or {}) | tojson }};
const initialRelatedCodes = {{ (process_cost_related_codes or []) | tojson }};
const initialActiveRelatedCodes = {{ (process_cost_active_related_codes or []) | tojson }};
const initialQuickLinkCodes = {{ (process_cost_quick_link_codes or []) | tojson }};
const searchInput = document.getElementById("projectSearchInput");
const list = document.getElementById("projectList");
@@ -1020,9 +1063,11 @@
const processModalNote = document.getElementById("processFlowModalNote");
const processModalClose = document.getElementById("processFlowModalClose");
const processButtons = document.querySelectorAll("[data-process-modal]");
const includeRelatedEnabled = {{ 1 if process_cost_include_related else 0 }};
let bookmarkedCodes = Array.isArray(initialQuickLinkCodes) ? [...initialQuickLinkCodes] : [];
let activeRelatedCodes = Array.isArray(initialActiveRelatedCodes) ? [...initialActiveRelatedCodes] : [];
function buildProcessCostUrl(code) {
function buildProcessCostUrl(code, options = {}) {
const params = new URLSearchParams();
params.set("source", {{ process_cost_source | tojson }});
if ({{ process_cost_selected_start_year | tojson }}) {
@@ -1034,8 +1079,15 @@
if (code) {
params.set("code", code);
}
if ({{ 1 if process_cost_include_related else 0 }}) {
const includeRelated = Object.prototype.hasOwnProperty.call(options, "includeRelated")
? Boolean(options.includeRelated)
: Boolean(includeRelatedEnabled);
const relatedCodes = Array.isArray(options.activeRelatedCodes)
? options.activeRelatedCodes
: activeRelatedCodes;
if (includeRelated) {
params.set("include_related", "1");
params.set("active_related", relatedCodes.length ? relatedCodes.join(",") : "-");
}
return `/process-cost?${params.toString()}`;
}
@@ -1072,11 +1124,20 @@
selectedProjectBookmark.setAttribute("title", active ? "바로가기 해제" : "바로가기 저장");
}
function syncSelectedProjectCard() {
const selectedProjectCard = document.getElementById("selectedProjectCard");
if (!selectedProjectCard || !selectedProject || !selectedProject.support_dept_code) {
return;
}
selectedProjectCard.hidden = isBookmarked(selectedProject.support_dept_code);
}
function renderQuickLinks() {
if (!quickLinksContainer) return;
const items = getBookmarkedProjects();
if (!items.length) {
quickLinksContainer.innerHTML = "";
syncSelectedProjectCard();
return;
}
quickLinksContainer.innerHTML = items.map((item) => `
@@ -1102,6 +1163,7 @@
.then(() => {
renderQuickLinks();
syncBookmarkButton();
syncSelectedProjectCard();
closeProjectList();
filterProjectList();
})
@@ -1110,6 +1172,7 @@
});
});
});
syncSelectedProjectCard();
}
function getVisibleProjectItems() {
@@ -1228,6 +1291,7 @@
renderQuickLinks();
syncBookmarkButton();
syncSelectedProjectCard();
filterProjectList();
if (selectedProjectBookmark) {
@@ -1242,6 +1306,7 @@
await persistBookmarkedCodes(nextCodes);
renderQuickLinks();
syncBookmarkButton();
syncSelectedProjectCard();
closeProjectList();
filterProjectList();
} catch (error) {
@@ -1266,19 +1331,57 @@
function renderRelatedChips() {
if (!relatedChipRow) return;
const fixedHead = selectedCode ? `<span class="pc-chip">${selectedCode} 기준</span>` : "";
const chips = [...relatedSet].sort().map((code) => `
const chips = [...relatedSet].sort().map((code) => {
const isActive = activeRelatedCodes.includes(code);
const nextActiveCodes = isActive
? activeRelatedCodes.filter((item) => item !== code)
: [...activeRelatedCodes, code];
const openActiveCodes = [...new Set(
[selectedCode, ...activeRelatedCodes]
.filter((item) => item && item !== code)
)];
return `
<span class="pc-chip" data-related-code="${code}">
<span>${code}</span>
<a
href="${buildProcessCostUrl(code, { activeRelatedCodes: openActiveCodes, includeRelated: includeRelatedEnabled })}"
class="pc-chip-link"
>${code}</a>
<button
type="button"
class="pc-related-include ${isActive ? "is-active" : ""}"
data-related-toggle="${code}"
data-related-active="${isActive ? "1" : "0"}"
aria-label="${isActive ? "합산 제외" : "합산 포함"}"
title="${isActive ? "합산 제외" : "합산 포함"}"
${includeRelatedEnabled ? "" : "disabled"}
>${isActive ? "" : "+"}</button>
<button type="button" class="pc-related-remove" data-related-remove="${code}">x</button>
</span>
`).join("");
`;
}).join("");
const empty = "";
relatedChipRow.innerHTML = fixedHead + chips + empty;
relatedChipRow.querySelectorAll("[data-related-toggle]").forEach((button) => {
button.addEventListener("click", () => {
if (!includeRelatedEnabled) return;
const code = button.getAttribute("data-related-toggle") || "";
if (!code) return;
const currentlyActive = button.getAttribute("data-related-active") === "1";
const nextActiveCodes = currentlyActive
? activeRelatedCodes.filter((item) => item !== code)
: [...activeRelatedCodes, code];
window.location.href = buildProcessCostUrl(selectedCode, {
includeRelated: true,
activeRelatedCodes: [...new Set(nextActiveCodes)],
});
});
});
relatedChipRow.querySelectorAll("[data-related-remove]").forEach((button) => {
button.addEventListener("click", () => {
const code = button.dataset.relatedRemove || "";
if (code) {
relatedSet.delete(code);
activeRelatedCodes = activeRelatedCodes.filter((item) => item !== code);
renderRelatedChips();
}
});
@@ -1338,6 +1441,7 @@
for (const code of initialRelatedCodes || []) {
relatedSet.add(code);
}
activeRelatedCodes = Array.isArray(initialActiveRelatedCodes) ? [...initialActiveRelatedCodes] : [];
if (relatedSearchInput) relatedSearchInput.value = "";
if (relatedSuggestList) {
relatedSuggestList.classList.remove("active");