Update remicon_cost_app.html with advanced specification selection features
This commit is contained in:
@@ -364,7 +364,11 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
<label for="mSpec">규격명</label>
|
<label for="mSpec">규격명</label>
|
||||||
<input id="mSpec" placeholder="예: 25-24-150" />
|
<select id="mSpec"></select>
|
||||||
|
<div class="row" style="margin-top:6px;">
|
||||||
|
<input id="mNewSpec" placeholder="신규 규격 입력 (예: 20-55-600)" />
|
||||||
|
<button type="button" class="sub" id="addSpecBtn">규격 추가</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
<label for="mPriceSetId">적용 단가</label>
|
<label for="mPriceSetId">적용 단가</label>
|
||||||
@@ -527,6 +531,16 @@
|
|||||||
const SELECTED_PRICE_SET_KEY = "remicon_selected_price_set_v1";
|
const SELECTED_PRICE_SET_KEY = "remicon_selected_price_set_v1";
|
||||||
const MIX_DRAFT_KEY = "remicon_mix_draft_v1";
|
const MIX_DRAFT_KEY = "remicon_mix_draft_v1";
|
||||||
const PRICE_DRAFT_KEY = "remicon_price_draft_v1";
|
const PRICE_DRAFT_KEY = "remicon_price_draft_v1";
|
||||||
|
const CUSTOM_SPECS_KEY = "remicon_custom_specs_v1";
|
||||||
|
const PRESET_SPECS = [
|
||||||
|
"25-27-600",
|
||||||
|
"25-30-600",
|
||||||
|
"25-35-600",
|
||||||
|
"20-40-600",
|
||||||
|
"20-45-600",
|
||||||
|
"20-50-600",
|
||||||
|
"20-60-600"
|
||||||
|
];
|
||||||
|
|
||||||
const DEFAULT_PRICE_SETS = [
|
const DEFAULT_PRICE_SETS = [
|
||||||
{
|
{
|
||||||
@@ -690,6 +704,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustomSpecs() {
|
||||||
|
return loadJsonArray(CUSTOM_SPECS_KEY)
|
||||||
|
.map((x) => String(x || "").trim())
|
||||||
|
.filter((x) => x.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCustomSpecs(specs) {
|
||||||
|
localStorage.setItem(CUSTOM_SPECS_KEY, JSON.stringify(specs));
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateTime(iso) {
|
function formatDateTime(iso) {
|
||||||
if (!iso) return "-";
|
if (!iso) return "-";
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
@@ -1032,6 +1056,58 @@
|
|||||||
.sort((a, b) => a.spec.localeCompare(b.spec, "ko-KR", { numeric: true, sensitivity: "base" }));
|
.sort((a, b) => a.spec.localeCompare(b.spec, "ko-KR", { numeric: true, sensitivity: "base" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSpecDataList(selectedValue = "") {
|
||||||
|
const specSelect = document.getElementById("mSpec");
|
||||||
|
if (!specSelect) return;
|
||||||
|
specSelect.innerHTML = "";
|
||||||
|
const dynamicSpecs = uniqueLatestSpecs().map(({ spec }) => spec);
|
||||||
|
const customSpecs = getCustomSpecs();
|
||||||
|
const mergedSpecs = [...new Set([...PRESET_SPECS, ...customSpecs, ...dynamicSpecs])]
|
||||||
|
.sort((a, b) => a.localeCompare(b, "ko-KR", { numeric: true, sensitivity: "base" }));
|
||||||
|
const placeholder = document.createElement("option");
|
||||||
|
placeholder.value = "";
|
||||||
|
placeholder.textContent = "규격 선택";
|
||||||
|
specSelect.appendChild(placeholder);
|
||||||
|
mergedSpecs.forEach((spec) => {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = spec;
|
||||||
|
opt.textContent = spec;
|
||||||
|
specSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
const target = selectedValue || "";
|
||||||
|
if (target && !mergedSpecs.includes(target)) {
|
||||||
|
const custom = document.createElement("option");
|
||||||
|
custom.value = target;
|
||||||
|
custom.textContent = target;
|
||||||
|
specSelect.appendChild(custom);
|
||||||
|
}
|
||||||
|
specSelect.value = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCustomSpec() {
|
||||||
|
const input = document.getElementById("mNewSpec");
|
||||||
|
if (!input) return;
|
||||||
|
const next = String(input.value || "").trim();
|
||||||
|
if (!next) {
|
||||||
|
alert("추가할 규격을 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const all = [...new Set([...PRESET_SPECS, ...getCustomSpecs(), ...uniqueLatestSpecs().map((x) => x.spec)])];
|
||||||
|
if (all.includes(next)) {
|
||||||
|
renderSpecDataList(next);
|
||||||
|
input.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updated = [...getCustomSpecs(), next]
|
||||||
|
.filter((x) => x)
|
||||||
|
.sort((a, b) => a.localeCompare(b, "ko-KR", { numeric: true, sensitivity: "base" }));
|
||||||
|
saveCustomSpecs(updated);
|
||||||
|
renderSpecDataList(next);
|
||||||
|
document.getElementById("mSpec").value = next;
|
||||||
|
saveDraft(MIX_DRAFT_KEY, getMixDraftPayload());
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
function renderSpecList() {
|
function renderSpecList() {
|
||||||
const area = document.getElementById("specListArea");
|
const area = document.getElementById("specListArea");
|
||||||
const items = uniqueLatestSpecs();
|
const items = uniqueLatestSpecs();
|
||||||
@@ -1379,6 +1455,7 @@
|
|||||||
mixModalTitle.textContent = "배합표 수정";
|
mixModalTitle.textContent = "배합표 수정";
|
||||||
saveMixBtn.textContent = "수정 저장";
|
saveMixBtn.textContent = "수정 저장";
|
||||||
|
|
||||||
|
renderSpecDataList(entry.spec || "");
|
||||||
document.getElementById("mSpec").value = entry.spec || "";
|
document.getElementById("mSpec").value = entry.spec || "";
|
||||||
document.getElementById("mWaterCementRatio").value = toNum(entry.mixMeta?.waterCementRatio);
|
document.getElementById("mWaterCementRatio").value = toNum(entry.mixMeta?.waterCementRatio);
|
||||||
document.getElementById("mFineAggRatio").value = toNum(entry.mixMeta?.fineAggRatio);
|
document.getElementById("mFineAggRatio").value = toNum(entry.mixMeta?.fineAggRatio);
|
||||||
@@ -1409,6 +1486,7 @@
|
|||||||
mixModalTitle.textContent = "배합표 등록";
|
mixModalTitle.textContent = "배합표 등록";
|
||||||
saveMixBtn.textContent = "저장";
|
saveMixBtn.textContent = "저장";
|
||||||
clearModalFields();
|
clearModalFields();
|
||||||
|
renderSpecDataList(selectedSpec || "");
|
||||||
document.getElementById("mSpec").value = selectedSpec || "";
|
document.getElementById("mSpec").value = selectedSpec || "";
|
||||||
renderMixPriceSetSelect(localStorage.getItem(SELECTED_PRICE_SET_KEY) || "");
|
renderMixPriceSetSelect(localStorage.getItem(SELECTED_PRICE_SET_KEY) || "");
|
||||||
applyMixDraft();
|
applyMixDraft();
|
||||||
@@ -1498,6 +1576,16 @@
|
|||||||
function applyMixDraft() {
|
function applyMixDraft() {
|
||||||
const draft = loadDraft(MIX_DRAFT_KEY);
|
const draft = loadDraft(MIX_DRAFT_KEY);
|
||||||
if (!draft) return;
|
if (!draft) return;
|
||||||
|
const draftSpec = String(draft.mSpec || "").trim();
|
||||||
|
if (draftSpec) {
|
||||||
|
const specSelect = document.getElementById("mSpec");
|
||||||
|
if (specSelect && ![...specSelect.options].some((o) => o.value === draftSpec)) {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = draftSpec;
|
||||||
|
opt.textContent = draftSpec;
|
||||||
|
specSelect.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
Object.entries(draft).forEach(([id, val]) => {
|
Object.entries(draft).forEach(([id, val]) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.value = val ?? "";
|
if (el) el.value = val ?? "";
|
||||||
@@ -2234,6 +2322,13 @@
|
|||||||
document.getElementById("goPriceBtn").addEventListener("click", () => switchPage("price"));
|
document.getElementById("goPriceBtn").addEventListener("click", () => switchPage("price"));
|
||||||
document.getElementById("refreshBtn").addEventListener("click", renderMain);
|
document.getElementById("refreshBtn").addEventListener("click", renderMain);
|
||||||
document.getElementById("newMixBtn").addEventListener("click", openModal);
|
document.getElementById("newMixBtn").addEventListener("click", openModal);
|
||||||
|
document.getElementById("addSpecBtn").addEventListener("click", addCustomSpec);
|
||||||
|
document.getElementById("mNewSpec").addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
addCustomSpec();
|
||||||
|
}
|
||||||
|
});
|
||||||
document.getElementById("closeModalBtn").addEventListener("click", closeModal);
|
document.getElementById("closeModalBtn").addEventListener("click", closeModal);
|
||||||
document.getElementById("cancelBtn").addEventListener("click", closeModal);
|
document.getElementById("cancelBtn").addEventListener("click", closeModal);
|
||||||
document.getElementById("saveMixBtn").addEventListener("click", saveMixEntry);
|
document.getElementById("saveMixBtn").addEventListener("click", saveMixEntry);
|
||||||
@@ -2256,12 +2351,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
"mSpec", "mPriceSetId", "mWaterCementRatio", "mFineAggRatio", "mWater",
|
"mPriceSetId", "mWaterCementRatio", "mFineAggRatio", "mWater",
|
||||||
"mCement", "mSlag", "mWashedSand", "mCrushedSand", "mMm20", "mMm25", "mAdmixture", "mNote"
|
"mCement", "mSlag", "mWashedSand", "mCrushedSand", "mMm20", "mMm25", "mAdmixture", "mNote"
|
||||||
].forEach((id) => {
|
].forEach((id) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.addEventListener("input", () => saveDraft(MIX_DRAFT_KEY, getMixDraftPayload()));
|
if (el) el.addEventListener("input", () => saveDraft(MIX_DRAFT_KEY, getMixDraftPayload()));
|
||||||
});
|
});
|
||||||
|
document.getElementById("mSpec").addEventListener("change", () => saveDraft(MIX_DRAFT_KEY, getMixDraftPayload()));
|
||||||
|
|
||||||
[
|
[
|
||||||
"pYear", "pMonth", "pCementPrice", "pSlagPrice", "pWashedSandPrice", "pCrushedSandPrice",
|
"pYear", "pMonth", "pCementPrice", "pSlagPrice", "pWashedSandPrice", "pCrushedSandPrice",
|
||||||
@@ -2295,4 +2391,4 @@
|
|||||||
init();
|
init();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user