diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8e39034..2a86c92 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,7 +3,7 @@ name: Deploy To EC2 on: push: branches: - - integrate-fe-be + - feat/filter-vacancy jobs: deploy: diff --git a/src/main/resources/static/css/date.css b/src/main/resources/static/css/date.css index 5eb5689..d3f318f 100644 --- a/src/main/resources/static/css/date.css +++ b/src/main/resources/static/css/date.css @@ -70,7 +70,6 @@ color: black; } - /* 토요일: 파란색 */ .date-buttons button.day.saturday .day-date, .date-buttons button.day.saturday .day-week { @@ -83,10 +82,19 @@ color: #ff0000; } -/* 선택된 날짜 버튼 스타일 */ +/* 선택된 날짜 버튼 스타일 (파란색 배경 + 흰색 텍스트)*/ .date-buttons button.day.selected { - background-color: #007bff; /* 파란색 배경 */ - color: white; /* 텍스트 흰색 */ + background-color: #007bff; +} +.date-buttons button.day.selected .day-date, +.date-buttons button.day.selected .day-week { + color: white; +} +.date-buttons button.day.saturday.selected .day-date, +.date-buttons button.day.saturday.selected .day-week, +.date-buttons button.day.sunday.selected .day-date, +.date-buttons button.day.sunday.selected .day-week { + color: white; } /* 좌우 화살표 버튼 */ diff --git a/src/main/resources/static/css/filter.css b/src/main/resources/static/css/filter.css index b474b8b..a0a17bf 100644 --- a/src/main/resources/static/css/filter.css +++ b/src/main/resources/static/css/filter.css @@ -111,4 +111,34 @@ border: none; border-radius: 12px; cursor: pointer; +} + +#vacancyRange { + width: 100%; /* 부모 영역 다 채우기 */ + max-width: 300px; /* 너무 길어지지 않도록 제한 */ + margin-top: 8px; + +} + +/* 슬라이더 트랙 (크롬, 사파리 등) */ +#vacancyRange::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: #007bff; + cursor: pointer; + border: none; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); +} + +/* 파이어폭스 대응 */ +#vacancyRange::-moz-range-thumb { + width: 20px; + height: 20px; + border-radius: 50%; + background: #007bff; + border: none; + cursor: pointer; } \ No newline at end of file diff --git a/src/main/resources/static/js/filter.js b/src/main/resources/static/js/filter.js index 6944f83..9bbe7c4 100644 --- a/src/main/resources/static/js/filter.js +++ b/src/main/resources/static/js/filter.js @@ -1,4 +1,12 @@ const filterState = {}; // ex: { gender: { all: [...], unchecked: [...] }, platform: { ... } } +let minVacancy = 0 + +const vacancySlider = document.getElementById("vacancyRange"); +const vacancyDisplay = document.getElementById("vacancyValue"); + +vacancySlider.addEventListener("input", () => { + vacancyDisplay.textContent = vacancySlider.value === "0" ? "모든 매치" : `${vacancySlider.value}인 이상 신청 가능`; +}) // 필터 상태 초기화 (최초 1회), 전역 필터링 상태 저장 -> renderMatches() 호출 시 전역 필터 상태 기반 필터링 function initFilterState(modalId) { @@ -37,9 +45,14 @@ function saveFilterState(modalId) { document.addEventListener("DOMContentLoaded", () => { // 필터 모달 초기화 (모달 id 기준) - // 모든 .modal-overlay를 자동 탐색하여 초기화 - document.querySelectorAll(".modal-overlay").forEach(modal => { - initFilterState(modal.id); + + // 멀티 필터 초기화 + document.querySelectorAll('[data-multifilter][data-modal-target]').forEach(btn => { + const modalSelector = btn.getAttribute("data-modal-target"); // ex: "#genderModal" + const modal = document.querySelector(modalSelector); + if (modal) { + initFilterState(modal.id); + } }); // 모달 열기 버튼 @@ -47,12 +60,21 @@ document.addEventListener("DOMContentLoaded", () => { btn.addEventListener("click", () => { const selector = btn.getAttribute("data-modal-target"); const modal = document.querySelector(selector); - if (modal) { + if(!modal) return; + + //멀티 필터 모달일 시, + if (btn.hasAttribute("data-multi-filter")) { renderFilterState(modal.id); // 상태 복원 - modal.classList.add("active"); - //CSS .modal-overlay.active -> 화면에 display 되도록, modal-overlay는 선택시 나타나는 전체 회색배경. - //이 배경이 active 되면 내부 modal-content도 함께 나타난다. } + + if (modal.id === "vacancyModal") { + vacancySlider.value = minVacancy; + vacancyDisplay.textContent = vacancySlider.value === "0" ? "모든 매치" : `${vacancySlider.value}인 이상 신청 가능`; + } + + //CSS .modal-overlay.active -> 화면에 display 되도록, modal-overlay는 선택시 나타나는 전체 회색배경. + //이 배경이 active 되면 내부 modal-content도 함께 나타난다. + modal.classList.add("active"); }); }); @@ -61,7 +83,7 @@ document.addEventListener("DOMContentLoaded", () => { btn.addEventListener("click", () => { const modal = btn.closest(".modal-overlay"); if (modal) { - //닫기 버튼 클릭시 체크 박스 상태는 저장하지 않는다. + //닫기 버튼 클릭시 상태는 저장하지 않는다. modal.classList.remove("active"); } }); @@ -71,7 +93,7 @@ document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".modal-overlay").forEach(modal => { modal.addEventListener("click", (e) => { if (e.target === modal) { - //바깥 클릭으로 닫을시 체크 박스 상태는 저장하지 않는다. + //바깥 클릭으로 닫을시 상태는 저장하지 않는다. modal.classList.remove("active"); } }); @@ -81,20 +103,24 @@ document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll(".modal-overlay .apply-btn").forEach(btn => { btn.addEventListener("click", () => { const modal = btn.closest(".modal-overlay"); - if (modal) { - saveFilterState(modal.id); // 상태 저장 - modal.classList.remove("active"); + if(!modal) return; + + const triggerBtn = document.querySelector(`[data-modal-target="#${modal.id}"]`); + if(!triggerBtn) return; + + let isFiltered = false; + if(triggerBtn.hasAttribute("data-multifilter")) { + saveFilterState(modal.id); + isFiltered = filterState[modal.id]?.unchecked?.length > 0; + } else if(modal.id === "vacancyModal") { + minVacancy = vacancySlider.value; + isFiltered = minVacancy > 0; + } - // 필터링 선택 버튼 활성화 (적용 사항 있을시) - const triggerBtn = document.querySelector(`[data-modal-target="#${modal.id}"]`); - const isFiltered = filterState[modal.id].unchecked.length > 0 - if (triggerBtn) { - triggerBtn.classList.toggle("active", isFiltered); - } + modal.classList.remove("active"); + triggerBtn.classList.toggle("active", isFiltered); - renderMatches(); - console.log(`[${modal.id}] 필터 적용됨:`, getActiveValues(modal.id)); - } + renderMatches(); }); }); }) @@ -109,7 +135,8 @@ function applyFilters(matchList) { return matchList .filter(match => activeGenders.includes(match.sex)) .filter(match => activePlatforms.includes(match.platform)) - .filter(match => !(hideFull && match.isFull)); //마감가리기 활성화 && 마감된 매치일 시 필터링 + .filter(match => !(hideFull && match.vacancy === 0)) //마감가리기 활성화 && 마감된 매치일 시 필터링 + .filter(match => match.vacancy >= minVacancy); } // 현재 적용된 항목만 가져오는 헬퍼 함수 diff --git a/src/main/resources/static/js/home.js b/src/main/resources/static/js/home.js index 8acfdcd..0d845e1 100644 --- a/src/main/resources/static/js/home.js +++ b/src/main/resources/static/js/home.js @@ -19,7 +19,8 @@ function fetchMatches(date) { .then(data => { allMatches = data.map(match => ({ ...match, - isFull: parseInt(match.cur_player) >= parseInt(match.max_player) //마감 여부 추가. '마감가리기' 필터용 + vacancy: parseInt(match.max_player ?? "999") - parseInt(match.cur_player ?? "0") + //인원 정보가 없을 경우, 잔여인원을 크게 설정하여 필터링 되지 않도록 한다. })); renderMatches(); }) diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 60ae5f6..fdddc99 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -44,12 +44,18 @@ - - + + + + - + +