release: v0.10.0
This commit is contained in:
parent
2b7fa32b5b
commit
6930906583
61 changed files with 15165 additions and 82 deletions
300
apps/site/public/work/griddle-club/scripts/franchise.js
Normal file
300
apps/site/public/work/griddle-club/scripts/franchise.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
(() => {
|
||||
"use strict";
|
||||
|
||||
const fixture = new URLSearchParams(window.location.search).get("fixture") || "";
|
||||
const form = document.querySelector("#franchise-form");
|
||||
const formSteps = [...document.querySelectorAll("[data-step]")];
|
||||
const indicators = [...document.querySelectorAll("[data-step-indicator]")];
|
||||
const indicatorList = document.querySelector(".step-indicator");
|
||||
const areaField = document.querySelector("#area-field");
|
||||
const areaInput = document.querySelector("#exclusive-area");
|
||||
const areaOutput = document.querySelector("#area-conversion");
|
||||
const successPanel = document.querySelector("#inquiry-success");
|
||||
const submitState = document.querySelector("#submit-state");
|
||||
const submitButton = document.querySelector("#submit-inquiry");
|
||||
let currentStep = 1;
|
||||
|
||||
const markBrokenImage = (image) => {
|
||||
const shell = image.closest(".image-shell");
|
||||
if (shell) shell.classList.add("is-broken");
|
||||
};
|
||||
|
||||
document.querySelectorAll(".image-shell img").forEach((image) => {
|
||||
image.addEventListener("error", () => markBrokenImage(image));
|
||||
if (image.complete && image.naturalWidth === 0) markBrokenImage(image);
|
||||
});
|
||||
|
||||
if (!form) return;
|
||||
|
||||
const getRadioValue = (name) => form.querySelector(`input[name="${name}"]:checked`)?.value || "";
|
||||
const getSelectValue = (name) => {
|
||||
const field = form.elements.namedItem(name);
|
||||
return field instanceof HTMLSelectElement ? field.value : "";
|
||||
};
|
||||
const getInputValue = (name) => {
|
||||
const field = form.elements.namedItem(name);
|
||||
return field instanceof HTMLInputElement ? field.value.trim() : "";
|
||||
};
|
||||
|
||||
const setStep = (step) => {
|
||||
currentStep = step;
|
||||
formSteps.forEach((section) => section.classList.toggle("is-current", Number(section.dataset.step) === step));
|
||||
indicators.forEach((item) => {
|
||||
const active = Number(item.dataset.stepIndicator) === step;
|
||||
item.classList.toggle("is-current", active);
|
||||
if (active) item.setAttribute("aria-current", "step");
|
||||
else item.removeAttribute("aria-current");
|
||||
});
|
||||
const heading = form.querySelector(`[data-step="${step}"] h3`);
|
||||
if (heading instanceof HTMLElement) {
|
||||
heading.setAttribute("tabindex", "-1");
|
||||
heading.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
|
||||
const clearFieldError = (fieldId, errorId) => {
|
||||
const field = document.querySelector(`#${fieldId}`);
|
||||
const error = document.querySelector(`#${errorId}`);
|
||||
const wrapper = field?.closest(".form-field");
|
||||
wrapper?.classList.remove("has-error");
|
||||
field?.removeAttribute("aria-invalid");
|
||||
field?.removeAttribute("aria-describedby");
|
||||
if (error) error.textContent = "";
|
||||
};
|
||||
|
||||
const setFieldError = (fieldId, errorId, message) => {
|
||||
const field = document.querySelector(`#${fieldId}`);
|
||||
const error = document.querySelector(`#${errorId}`);
|
||||
const wrapper = field?.closest(".form-field");
|
||||
wrapper?.classList.add("has-error");
|
||||
field?.setAttribute("aria-invalid", "true");
|
||||
field?.setAttribute("aria-describedby", errorId);
|
||||
if (error) error.textContent = message;
|
||||
return field;
|
||||
};
|
||||
|
||||
const clearRadioError = (name, errorId) => {
|
||||
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
|
||||
const wrapper = radios[0]?.closest(".form-field");
|
||||
wrapper?.classList.remove("has-error");
|
||||
radios.forEach((radio) => {
|
||||
radio.removeAttribute("aria-invalid");
|
||||
radio.removeAttribute("aria-describedby");
|
||||
});
|
||||
const error = document.querySelector(`#${errorId}`);
|
||||
if (error) error.textContent = "";
|
||||
};
|
||||
|
||||
const setRadioError = (name, errorId, message) => {
|
||||
const radios = [...form.querySelectorAll(`input[name="${name}"]`)];
|
||||
const wrapper = radios[0]?.closest(".form-field");
|
||||
wrapper?.classList.add("has-error");
|
||||
radios.forEach((radio) => {
|
||||
radio.setAttribute("aria-invalid", "true");
|
||||
radio.setAttribute("aria-describedby", errorId);
|
||||
});
|
||||
const error = document.querySelector(`#${errorId}`);
|
||||
if (error) error.textContent = message;
|
||||
return radios[0];
|
||||
};
|
||||
|
||||
const validateStepOne = () => {
|
||||
const invalid = [];
|
||||
clearFieldError("demo-name", "demo-name-error");
|
||||
clearFieldError("region", "region-error");
|
||||
clearRadioError("areaKind", "area-kind-error");
|
||||
|
||||
if (getInputValue("demoName").length < 2) {
|
||||
invalid.push(setFieldError("demo-name", "demo-name-error", "예시 호칭을 두 글자 이상 입력해 주세요."));
|
||||
}
|
||||
if (!getSelectValue("region")) {
|
||||
invalid.push(setFieldError("region", "region-error", "희망 지역을 골라 주세요."));
|
||||
}
|
||||
if (!getRadioValue("areaKind")) {
|
||||
invalid.push(setRadioError("areaKind", "area-kind-error", "희망 상권을 골라 주세요."));
|
||||
}
|
||||
invalid[0]?.focus();
|
||||
return invalid.length === 0;
|
||||
};
|
||||
|
||||
const requiresArea = () => ["협의 중", "보유함"].includes(getRadioValue("storePossession"));
|
||||
|
||||
const updateAreaField = () => {
|
||||
const required = requiresArea();
|
||||
if (areaField) areaField.hidden = !required;
|
||||
if (areaInput instanceof HTMLInputElement) {
|
||||
areaInput.required = required;
|
||||
if (!required) {
|
||||
areaInput.value = "";
|
||||
clearFieldError("exclusive-area", "exclusive-area-error");
|
||||
if (areaOutput) areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateAreaConversion = () => {
|
||||
if (!(areaInput instanceof HTMLInputElement) || !areaOutput) return;
|
||||
const squareMeters = Number(areaInput.value);
|
||||
if (!Number.isFinite(squareMeters) || squareMeters <= 0) {
|
||||
areaOutput.textContent = "면적을 입력하면 평으로도 보여 드립니다.";
|
||||
return;
|
||||
}
|
||||
const pyeong = Math.round((squareMeters / 3.3058) * 10) / 10;
|
||||
areaOutput.textContent = `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${pyeong.toLocaleString("ko-KR")}평`;
|
||||
};
|
||||
|
||||
const validateStepTwo = () => {
|
||||
const invalid = [];
|
||||
clearRadioError("storePossession", "store-possession-error");
|
||||
clearFieldError("exclusive-area", "exclusive-area-error");
|
||||
clearFieldError("opening-window", "opening-window-error");
|
||||
clearRadioError("experience", "experience-error");
|
||||
|
||||
if (!getRadioValue("storePossession")) {
|
||||
invalid.push(setRadioError("storePossession", "store-possession-error", "점포 상태를 골라 주세요."));
|
||||
}
|
||||
if (requiresArea()) {
|
||||
const value = Number(getInputValue("exclusiveArea"));
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
invalid.push(setFieldError("exclusive-area", "exclusive-area-error", "면적은 0보다 큰 숫자로 입력해 주세요."));
|
||||
}
|
||||
}
|
||||
if (!getSelectValue("openingWindow")) {
|
||||
invalid.push(setFieldError("opening-window", "opening-window-error", "희망 시작 시기를 골라 주세요."));
|
||||
}
|
||||
if (!getRadioValue("experience")) {
|
||||
invalid.push(setRadioError("experience", "experience-error", "운영 경험 여부를 골라 주세요."));
|
||||
}
|
||||
invalid[0]?.focus();
|
||||
return invalid.length === 0;
|
||||
};
|
||||
|
||||
const reviewValues = () => {
|
||||
const squareMeters = Number(getInputValue("exclusiveArea"));
|
||||
const areaLabel = requiresArea() && squareMeters > 0
|
||||
? `${squareMeters.toLocaleString("ko-KR")}㎡ · 약 ${(Math.round((squareMeters / 3.3058) * 10) / 10).toLocaleString("ko-KR")}평`
|
||||
: "해당 없음";
|
||||
const values = {
|
||||
demoName: getInputValue("demoName"),
|
||||
region: getSelectValue("region"),
|
||||
areaKind: getRadioValue("areaKind"),
|
||||
storePossession: getRadioValue("storePossession"),
|
||||
exclusiveArea: areaLabel,
|
||||
openingWindow: getSelectValue("openingWindow"),
|
||||
experience: getRadioValue("experience"),
|
||||
};
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
const target = document.querySelector(`[data-review="${key}"]`);
|
||||
if (target) target.textContent = value || "—";
|
||||
});
|
||||
};
|
||||
|
||||
form.querySelectorAll("[data-next]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
if (currentStep === 1 && validateStepOne()) setStep(2);
|
||||
else if (currentStep === 2 && validateStepTwo()) {
|
||||
reviewValues();
|
||||
setStep(3);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
form.querySelectorAll("[data-back]").forEach((button) => {
|
||||
button.addEventListener("click", () => setStep(Math.max(1, currentStep - 1)));
|
||||
});
|
||||
|
||||
form.querySelectorAll("input[name='storePossession']").forEach((input) => {
|
||||
input.addEventListener("change", updateAreaField);
|
||||
});
|
||||
areaInput?.addEventListener("input", updateAreaConversion);
|
||||
|
||||
form.addEventListener("input", (event) => {
|
||||
const field = event.target;
|
||||
if (!(field instanceof HTMLElement) || !field.id) return;
|
||||
const errorId = `${field.id}-error`;
|
||||
if (document.querySelector(`#${errorId}`)) clearFieldError(field.id, errorId);
|
||||
});
|
||||
|
||||
form.addEventListener("change", (event) => {
|
||||
const field = event.target;
|
||||
if (!(field instanceof HTMLInputElement)) return;
|
||||
if (field.type === "radio") {
|
||||
const names = {
|
||||
areaKind: "area-kind-error",
|
||||
storePossession: "store-possession-error",
|
||||
experience: "experience-error",
|
||||
};
|
||||
if (names[field.name]) clearRadioError(field.name, names[field.name]);
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const consent = document.querySelector("#demo-consent");
|
||||
const consentError = document.querySelector("#demo-consent-error");
|
||||
if (consent instanceof HTMLInputElement) {
|
||||
consent.removeAttribute("aria-invalid");
|
||||
consent.removeAttribute("aria-describedby");
|
||||
}
|
||||
if (consentError) consentError.textContent = "";
|
||||
if (!(consent instanceof HTMLInputElement) || !consent.checked) {
|
||||
consent?.setAttribute("aria-invalid", "true");
|
||||
consent?.setAttribute("aria-describedby", "demo-consent-error");
|
||||
if (consentError) consentError.textContent = "문의 체험 안내를 확인해 주세요.";
|
||||
consent?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (submitButton instanceof HTMLButtonElement) {
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = "화면 상태 확인 중";
|
||||
}
|
||||
if (submitState) submitState.textContent = "";
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (fixture === "inquiry-fail") {
|
||||
if (submitButton instanceof HTMLButtonElement) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = "다시 시도하기";
|
||||
}
|
||||
if (submitState) {
|
||||
submitState.textContent = "지금은 문의 준비 상태를 바꾸지 못했어요. 입력 내용은 화면에 그대로 두었습니다.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
form.hidden = true;
|
||||
if (indicatorList) indicatorList.hidden = true;
|
||||
if (successPanel) {
|
||||
successPanel.hidden = false;
|
||||
successPanel.focus();
|
||||
}
|
||||
if (submitButton instanceof HTMLButtonElement) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = "문의 준비 마치기";
|
||||
}
|
||||
}, 450);
|
||||
});
|
||||
|
||||
document.querySelector("#review-again")?.addEventListener("click", () => {
|
||||
if (successPanel) successPanel.hidden = true;
|
||||
form.hidden = false;
|
||||
if (indicatorList) indicatorList.hidden = false;
|
||||
setStep(3);
|
||||
});
|
||||
|
||||
document.querySelector("#restart-form")?.addEventListener("click", () => {
|
||||
form.reset();
|
||||
form.hidden = false;
|
||||
if (successPanel) successPanel.hidden = true;
|
||||
if (indicatorList) indicatorList.hidden = false;
|
||||
if (submitState) submitState.textContent = "";
|
||||
document.querySelectorAll(".field-error").forEach((error) => { error.textContent = ""; });
|
||||
document.querySelectorAll(".has-error").forEach((field) => field.classList.remove("has-error"));
|
||||
updateAreaField();
|
||||
setStep(1);
|
||||
});
|
||||
|
||||
updateAreaField();
|
||||
setStep(1);
|
||||
})();
|
||||
261
apps/site/public/work/griddle-club/scripts/site.js
Normal file
261
apps/site/public/work/griddle-club/scripts/site.js
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
(() => {
|
||||
"use strict";
|
||||
|
||||
const fixture = new URLSearchParams(window.location.search).get("fixture") || "";
|
||||
const formatWon = (value) => `${new Intl.NumberFormat("ko-KR").format(value)}원`;
|
||||
|
||||
const markBrokenImage = (image) => {
|
||||
const shell = image.closest(".image-shell");
|
||||
if (shell) shell.classList.add("is-broken");
|
||||
};
|
||||
|
||||
document.querySelectorAll(".image-shell img").forEach((image) => {
|
||||
image.addEventListener("error", () => markBrokenImage(image));
|
||||
if (image.complete && image.naturalWidth === 0) markBrokenImage(image);
|
||||
});
|
||||
|
||||
const menuButtons = [...document.querySelectorAll("[data-menu-filter]")];
|
||||
const menuItems = [...document.querySelectorAll("[data-menu-category]")];
|
||||
const menuEmpty = document.querySelector("#menu-empty");
|
||||
|
||||
const setMenuFilter = (filter) => {
|
||||
const forceMenuEmpty = new URLSearchParams(window.location.search).get("fixture") === "menu-empty";
|
||||
let visibleCount = 0;
|
||||
menuButtons.forEach((button) => {
|
||||
const active = button.dataset.menuFilter === filter;
|
||||
button.classList.toggle("is-active", active);
|
||||
button.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
const visible = !forceMenuEmpty && (filter === "all" || item.dataset.menuCategory === filter);
|
||||
item.hidden = !visible;
|
||||
if (visible) visibleCount += 1;
|
||||
});
|
||||
|
||||
if (menuEmpty) menuEmpty.hidden = visibleCount > 0;
|
||||
};
|
||||
|
||||
menuButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => setMenuFilter(button.dataset.menuFilter || "all"));
|
||||
});
|
||||
|
||||
document.querySelector("[data-show-all-menu]")?.addEventListener("click", () => {
|
||||
if (fixture === "menu-empty") {
|
||||
const nextUrl = new URL(window.location.href);
|
||||
nextUrl.searchParams.delete("fixture");
|
||||
window.history.replaceState({}, "", nextUrl);
|
||||
}
|
||||
setMenuFilter("all");
|
||||
});
|
||||
|
||||
if (fixture === "menu-empty") setMenuFilter("all");
|
||||
|
||||
const builder = document.querySelector("#burger-builder");
|
||||
const builderSection = document.querySelector("#builder");
|
||||
const setOptions = document.querySelector("#set-options");
|
||||
const summaryHeading = document.querySelector("#summary-heading");
|
||||
const summaryLines = document.querySelector("#summary-lines");
|
||||
const summaryServing = document.querySelector("#summary-serving");
|
||||
const summaryStore = document.querySelector("#summary-store");
|
||||
const summaryStoreState = document.querySelector("#summary-store-state");
|
||||
const summaryAllergens = document.querySelector("#summary-allergens");
|
||||
const summaryTotal = document.querySelector("#summary-total");
|
||||
const saveButton = document.querySelector("#save-builder");
|
||||
const saveStatus = document.querySelector("#save-status");
|
||||
|
||||
const stores = {
|
||||
seongsu: { label: "성수 예시점", state: "영업 중 예시", available: true },
|
||||
yeonnam: { label: "연남 예시점", state: "오늘 영업 종료 예시", available: false },
|
||||
mullae: { label: "문래 예시점", state: "오늘은 쉬어요", available: false },
|
||||
};
|
||||
|
||||
const fieldValue = (name) => {
|
||||
const field = builder?.elements.namedItem(name);
|
||||
if (!field) return "";
|
||||
if (field instanceof RadioNodeList) return field.value;
|
||||
return field.value;
|
||||
};
|
||||
|
||||
const setRadio = (name, value) => {
|
||||
const radio = builder?.querySelector(`input[name="${name}"][value="${value}"]`);
|
||||
if (radio instanceof HTMLInputElement) radio.checked = true;
|
||||
};
|
||||
|
||||
const builderPrice = ({ flavor, patty, cheese, serving }) => {
|
||||
const flavorBase = flavor === "spicy" ? 7400 : 6900;
|
||||
const pattyAdd = Number(patty) === 2 ? 2000 : 0;
|
||||
const cheeseAdd = Number(cheese) === 0 ? -400 : Number(cheese) === 2 ? 400 : 0;
|
||||
const setAdd = serving === "set" ? 4100 : 0;
|
||||
return flavorBase + pattyAdd + cheeseAdd + setAdd;
|
||||
};
|
||||
|
||||
const updateBuilder = () => {
|
||||
if (!builder) return;
|
||||
const flavor = fieldValue("flavor");
|
||||
const patty = fieldValue("patty");
|
||||
const cheese = fieldValue("cheese");
|
||||
const onion = fieldValue("onion");
|
||||
const pickle = fieldValue("pickle");
|
||||
const sauce = fieldValue("sauce");
|
||||
const serving = fieldValue("serving");
|
||||
const storeKey = fieldValue("store");
|
||||
const store = stores[storeKey] || stores.seongsu;
|
||||
const soldOut = fixture === "sold-out";
|
||||
|
||||
const setEnabled = serving === "set";
|
||||
builder.querySelectorAll("select[name='side'], select[name='drink']").forEach((select) => {
|
||||
select.disabled = !setEnabled;
|
||||
});
|
||||
setOptions?.classList.toggle("is-disabled", !setEnabled);
|
||||
|
||||
const names = {
|
||||
onion: { none: "양파 없음", regular: "양파 기본", extra: "양파 많이" },
|
||||
pickle: { none: "피클 없음", regular: "피클 기본", extra: "피클 많이" },
|
||||
sauce: { none: "소스 없음", regular: "소스 기본", side: "소스 따로" },
|
||||
};
|
||||
const pattyLabel = Number(patty) === 2 ? "더블" : "싱글";
|
||||
const flavorLabel = flavor === "spicy" ? "고추 피클" : "철판";
|
||||
const cheeseLabel = Number(cheese) === 0 ? "치즈 없음" : `치즈 ${cheese === "2" ? "두" : "한"} 장`;
|
||||
const productName = `${flavorLabel} ${pattyLabel}`;
|
||||
const servingLabel = serving === "set" ? "세트 · 얇은 감자와 음료" : "단품";
|
||||
|
||||
if (summaryHeading) summaryHeading.textContent = productName;
|
||||
if (summaryLines) {
|
||||
summaryLines.textContent = `패티 ${patty === "2" ? "두" : "한"} 장 / ${cheeseLabel} / ${names.onion[onion]} / ${names.pickle[pickle]} / ${names.sauce[sauce]}`;
|
||||
}
|
||||
if (summaryServing) summaryServing.textContent = servingLabel;
|
||||
if (summaryStore) summaryStore.textContent = store.label;
|
||||
if (summaryStoreState) summaryStoreState.textContent = soldOut ? "오늘 품절 예시" : store.state;
|
||||
|
||||
const allergens = ["알", "대두", "밀", "쇠고기"];
|
||||
if (Number(cheese) > 0) allergens.splice(1, 0, "우유");
|
||||
if (flavor === "spicy") allergens.push("토마토");
|
||||
if (summaryAllergens) summaryAllergens.textContent = allergens.join(", ");
|
||||
if (summaryTotal) summaryTotal.textContent = formatWon(builderPrice({ flavor, patty, cheese, serving }));
|
||||
|
||||
if (saveButton) {
|
||||
saveButton.disabled = soldOut || !store.available;
|
||||
if (soldOut) saveButton.textContent = "오늘 품절 예시";
|
||||
else if (!store.available) saveButton.textContent = "다른 매장을 골라 주세요";
|
||||
else saveButton.textContent = "선택 내용 저장";
|
||||
}
|
||||
if (saveStatus) {
|
||||
saveStatus.textContent = "";
|
||||
saveStatus.classList.remove("is-error");
|
||||
}
|
||||
};
|
||||
|
||||
builder?.addEventListener("change", updateBuilder);
|
||||
|
||||
const applyPreset = (preset) => {
|
||||
if (!builder) return;
|
||||
if (preset === "single") {
|
||||
setRadio("flavor", "classic");
|
||||
setRadio("patty", "1");
|
||||
} else if (preset === "spicy") {
|
||||
setRadio("flavor", "spicy");
|
||||
setRadio("patty", "2");
|
||||
} else {
|
||||
setRadio("flavor", "classic");
|
||||
setRadio("patty", "2");
|
||||
}
|
||||
updateBuilder();
|
||||
builderSection?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
window.setTimeout(() => builder.querySelector("input:checked")?.focus({ preventScroll: true }), 350);
|
||||
};
|
||||
|
||||
document.querySelectorAll("[data-preset]").forEach((button) => {
|
||||
button.addEventListener("click", () => applyPreset(button.dataset.preset || "double"));
|
||||
});
|
||||
|
||||
saveButton?.addEventListener("click", () => {
|
||||
if (!builder || !saveStatus) return;
|
||||
const snapshot = Object.fromEntries(new FormData(builder).entries());
|
||||
try {
|
||||
if (fixture === "save-fail") throw new Error("fixture");
|
||||
sessionStorage.setItem("griddle-club-demo-selection", JSON.stringify(snapshot));
|
||||
saveStatus.classList.remove("is-error");
|
||||
saveStatus.textContent = "이 기기의 현재 탭에 선택 내용을 남겼어요. 실제 주문으로 전송되지 않습니다.";
|
||||
} catch {
|
||||
saveStatus.classList.add("is-error");
|
||||
saveStatus.textContent = "선택 내용을 남기지 못했어요. 고른 내용은 화면에 그대로 두었습니다.";
|
||||
}
|
||||
});
|
||||
|
||||
const storeCards = [...document.querySelectorAll("[data-store]")];
|
||||
const mapPins = [...document.querySelectorAll("[data-map-store]")];
|
||||
const storeSearch = document.querySelector("#store-search");
|
||||
const clearStoreSearch = document.querySelector("#clear-store-search");
|
||||
const storeEmpty = document.querySelector("#store-empty");
|
||||
const regionButtons = [...document.querySelectorAll("[data-region]")];
|
||||
let activeRegion = "all";
|
||||
|
||||
const selectStore = (storeKey, shouldMove = false) => {
|
||||
const select = builder?.elements.namedItem("store");
|
||||
if (select instanceof HTMLSelectElement) select.value = storeKey;
|
||||
storeCards.forEach((card) => card.classList.toggle("is-selected", card.dataset.store === storeKey));
|
||||
mapPins.forEach((pin) => pin.classList.toggle("is-active", pin.dataset.mapStore === storeKey));
|
||||
updateBuilder();
|
||||
if (shouldMove) builderSection?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
};
|
||||
|
||||
document.querySelectorAll("[data-select-store]").forEach((button) => {
|
||||
button.addEventListener("click", () => selectStore(button.dataset.selectStore, true));
|
||||
});
|
||||
mapPins.forEach((button) => {
|
||||
button.addEventListener("click", () => selectStore(button.dataset.mapStore, false));
|
||||
});
|
||||
|
||||
const filterStores = () => {
|
||||
const query = storeSearch instanceof HTMLInputElement ? storeSearch.value.trim().toLocaleLowerCase("ko-KR") : "";
|
||||
let visibleCount = 0;
|
||||
storeCards.forEach((card) => {
|
||||
const regionMatch = activeRegion === "all" || card.dataset.region === activeRegion;
|
||||
const queryMatch = !query || (card.dataset.search || "").toLocaleLowerCase("ko-KR").includes(query);
|
||||
const visible = fixture !== "store-none" && regionMatch && queryMatch;
|
||||
card.hidden = !visible;
|
||||
if (visible) visibleCount += 1;
|
||||
const pin = mapPins.find((item) => item.dataset.mapStore === card.dataset.store);
|
||||
if (pin) pin.hidden = !visible;
|
||||
});
|
||||
if (storeEmpty) storeEmpty.hidden = visibleCount > 0;
|
||||
if (clearStoreSearch) clearStoreSearch.hidden = !query;
|
||||
};
|
||||
|
||||
storeSearch?.addEventListener("input", filterStores);
|
||||
clearStoreSearch?.addEventListener("click", () => {
|
||||
if (storeSearch instanceof HTMLInputElement) {
|
||||
storeSearch.value = "";
|
||||
storeSearch.focus();
|
||||
}
|
||||
filterStores();
|
||||
});
|
||||
|
||||
regionButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
activeRegion = button.dataset.region || "all";
|
||||
regionButtons.forEach((item) => {
|
||||
const active = item === button;
|
||||
item.classList.toggle("is-active", active);
|
||||
item.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
filterStores();
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#reset-store-search")?.addEventListener("click", () => {
|
||||
activeRegion = "all";
|
||||
if (storeSearch instanceof HTMLInputElement) storeSearch.value = "";
|
||||
regionButtons.forEach((button) => {
|
||||
const active = button.dataset.region === "all";
|
||||
button.classList.toggle("is-active", active);
|
||||
button.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
filterStores();
|
||||
storeSearch?.focus();
|
||||
});
|
||||
|
||||
updateBuilder();
|
||||
filterStores();
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue