|
|
|
@@ -152,12 +152,55 @@ const EstSchedule = (() => {
|
|
|
|
|
function clearSchedule(project) {
|
|
|
|
|
project.schedule = [];
|
|
|
|
|
}
|
|
|
|
|
// 원본 예정공정표 눈금(mnu1Day·mnu5Day·mnu10Day·mnu15Day·mnu20Day·mnu1Week·mnu1Month).
|
|
|
|
|
const scales = [
|
|
|
|
|
["1d", "1일", 1],
|
|
|
|
|
["5d", "5일", 5],
|
|
|
|
|
["10d", "10일", 10],
|
|
|
|
|
["15d", "15일", 15],
|
|
|
|
|
["20d", "20일", 20],
|
|
|
|
|
["1w", "1주", 7],
|
|
|
|
|
["1m", "1개월", 0],
|
|
|
|
|
];
|
|
|
|
|
function ticks(start, end, scale, limit = 60) {
|
|
|
|
|
if (!start || !end) return { ticks: [], cut: false };
|
|
|
|
|
const first = day(start),
|
|
|
|
|
span = day(end) - first + 1,
|
|
|
|
|
step = scales.find(([key]) => key === scale)?.[2];
|
|
|
|
|
if (step == null) throw Error("눈금 단위를 고르세요.");
|
|
|
|
|
const out = [];
|
|
|
|
|
if (step) for (let d = first; d < first + span; d += step) out.push(d);
|
|
|
|
|
else {
|
|
|
|
|
const date = new Date(first * 86400000);
|
|
|
|
|
for (let y = date.getUTCFullYear(), m = date.getUTCMonth(); ; m++) {
|
|
|
|
|
const t = Date.UTC(y, m, 1) / 86400000;
|
|
|
|
|
if (t >= first + span) break;
|
|
|
|
|
out.push(Math.max(t, first));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const cut = out.length > limit;
|
|
|
|
|
return {
|
|
|
|
|
cut,
|
|
|
|
|
ticks: out.slice(0, limit).map((d) => ({
|
|
|
|
|
left: ((d - first) / span) * 100,
|
|
|
|
|
label: new Date(d * 86400000).toISOString().slice(step ? 5 : 0, step ? 10 : 7),
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
function render() {
|
|
|
|
|
const { esc, number, state } = EstApp,
|
|
|
|
|
output = calculate(state.project);
|
|
|
|
|
state.scheduleScale ||= "1m";
|
|
|
|
|
const grid = ticks(output.start, output.end, state.scheduleScale),
|
|
|
|
|
lines = grid.ticks
|
|
|
|
|
.map((t) => `linear-gradient(#999,#999) ${t.left}% 0/1px 100% no-repeat`)
|
|
|
|
|
.join(","),
|
|
|
|
|
width = Math.max(180, grid.ticks.length * 44),
|
|
|
|
|
header = `<div style="position:relative;height:14px;font-size:10px;min-width:${width}px">${grid.ticks.map((t) => `<span style="position:absolute;left:${t.left}%">${esc(t.label)}</span>`).join("")}</div>`,
|
|
|
|
|
scaleSelect = `<label>눈금 <select data-schedule-scale>${scales.map(([key, label]) => `<option value="${key}" ${key === state.scheduleScale ? "selected" : ""}>${label}</option>`).join("")}</select></label>${grid.cut ? "<span>눈금이 많아 앞 60개만 표시</span>" : ""}`;
|
|
|
|
|
const rowsCount = output.rows.length;
|
|
|
|
|
EstApp.byId("work-content").innerHTML =
|
|
|
|
|
`<div class="business-tools"><button data-action="schedule-add">공정 추가</button><button data-action="schedule-import">내역 일괄 추가</button><button data-action="schedule-weights">내역 금액으로 가중치 갱신</button><button data-action="schedule-progress">최근 확정 기성으로 진도 갱신</button><button data-action="schedule-clear" ${rowsCount === 0 ? "disabled" : ""}>공정 전체 비우기</button><span>시작 ${esc(output.start)} · 종료 ${esc(output.end)} · 가중치 합계 ${number(output.weight)} · 입력 진도 가중평균 ${number(output.progress)}%</span></div><p>기간과 진도는 직접 입력합니다. 가중치는 공종 금액 또는 사용자가 정한 값을 사용합니다.</p><table class="sheet"><thead><tr>${["코드", "공정명", "시작일", "종료일", "가중치", "진도(%)", "기간", "작업"].map((h) => `<th>${h}</th>`).join("")}</tr></thead><tbody>${output.rows.map((r, i) => `<tr>${["Code", "Name", "start", "end", "weight", "progress"].map((k) => `<td><input data-schedule="${k}" data-index="${i}" type="${["start", "end"].includes(k) ? "date" : ["weight", "progress"].includes(k) ? "number" : "text"}" value="${esc(r[k])}" step="any"></td>`).join("")}<td style="min-width:180px"><div style="height:12px;background:#ddd"><div style="margin-left:${r.left}%;width:${r.width}%;height:12px;background:#316ea5"></div></div></td><td><button data-action="schedule-move" data-index="${i}" data-direction="-1" ${i === 0 ? "disabled" : ""}>↑</button><button data-action="schedule-move" data-index="${i}" data-direction="1" ${i === rowsCount - 1 ? "disabled" : ""}>↓</button><button data-action="schedule-delete" data-index="${i}">삭제</button></td></tr>`).join("")}</tbody></table>`;
|
|
|
|
|
`<div class="business-tools"><button data-action="schedule-add">공정 추가</button><button data-action="schedule-import">내역 일괄 추가</button><button data-action="schedule-weights">내역 금액으로 가중치 갱신</button><button data-action="schedule-progress">최근 확정 기성으로 진도 갱신</button><button data-action="schedule-clear" ${rowsCount === 0 ? "disabled" : ""}>공정 전체 비우기</button>${scaleSelect}<span>시작 ${esc(output.start)} · 종료 ${esc(output.end)} · 가중치 합계 ${number(output.weight)} · 입력 진도 가중평균 ${number(output.progress)}%</span></div><p>기간과 진도는 직접 입력합니다. 가중치는 공종 금액 또는 사용자가 정한 값을 사용합니다.</p><table class="sheet"><thead><tr>${["코드", "공정명", "시작일", "종료일", "가중치", "진도(%)", "기간", "작업"].map((h) => `<th>${h === "기간" ? `기간${header}` : h}</th>`).join("")}</tr></thead><tbody>${output.rows.map((r, i) => `<tr>${["Code", "Name", "start", "end", "weight", "progress"].map((k) => `<td><input data-schedule="${k}" data-index="${i}" type="${["start", "end"].includes(k) ? "date" : ["weight", "progress"].includes(k) ? "number" : "text"}" value="${esc(r[k])}" step="any"></td>`).join("")}<td style="min-width:${width}px"><div style="height:12px;background:${lines ? `${lines},` : ""}#ddd"><div style="margin-left:${r.left}%;width:${r.width}%;height:12px;background:#316ea5"></div></div></td><td><button data-action="schedule-move" data-index="${i}" data-direction="-1" ${i === 0 ? "disabled" : ""}>↑</button><button data-action="schedule-move" data-index="${i}" data-direction="1" ${i === rowsCount - 1 ? "disabled" : ""}>↓</button><button data-action="schedule-delete" data-index="${i}">삭제</button></td></tr>`).join("")}</tbody></table>`;
|
|
|
|
|
}
|
|
|
|
|
function install() {
|
|
|
|
|
document.addEventListener("click", (event) => {
|
|
|
|
@@ -213,6 +256,11 @@ const EstSchedule = (() => {
|
|
|
|
|
});
|
|
|
|
|
document.addEventListener("change", (event) => {
|
|
|
|
|
const node = event.target;
|
|
|
|
|
if (node.dataset.scheduleScale !== undefined) {
|
|
|
|
|
EstApp.state.scheduleScale = node.value;
|
|
|
|
|
EstApp.render();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!node.dataset.schedule) return;
|
|
|
|
|
const p = structuredClone(EstApp.state.project),
|
|
|
|
|
row = p.schedule[Number(node.dataset.index)],
|
|
|
|
@@ -231,6 +279,7 @@ const EstSchedule = (() => {
|
|
|
|
|
return {
|
|
|
|
|
validate,
|
|
|
|
|
calculate,
|
|
|
|
|
ticks,
|
|
|
|
|
fromEstimate,
|
|
|
|
|
refreshWeights,
|
|
|
|
|
refreshProgress,
|
|
|
|
|