@@ -20,7 +20,7 @@ <h1 class="display-5 fw-bold mb-4">성장을 이끌어줄 현업 <span class="te
2020 </ p >
2121
2222 <!-- 키워드 검색 -->
23- < form th:action =" @{/} " method =" get " id =" searchForm ">
23+ < form id =" searchForm " onsubmit =" event.preventDefault(); loadSeniors(); ">
2424
2525 <!-- ── 검색창 + 토글 + 검색버튼 ──────────────── -->
2626 < div class ="row justify-content-center mb-3 ">
@@ -62,8 +62,8 @@ <h1 class="display-5 fw-bold mb-4">성장을 이끌어줄 현업 <span class="te
6262 <!-- 헤더 -->
6363 < div class ="d-flex justify-content-between align-items-center mb-4 ">
6464 < span class ="fw-semibold text-white "> 상세 필터</ span >
65- < a th: href ="@{/} " class ="text-secondary text-decoration-none "
66- style ="font-size:0.83rem; "> 초기화</ a >
65+ < a href ="# " class ="text-secondary text-decoration-none "
66+ style ="font-size:0.83rem; " onclick =" resetFilters() " > 초기화</ a >
6767 </ div >
6868
6969 < div class ="row g-4 ">
@@ -116,7 +116,7 @@ <h1 class="display-5 fw-bold mb-4">성장을 이끌어줄 현업 <span class="te
116116
117117 <!-- 적용 버튼 -->
118118 < div class ="d-flex justify-content-end mt-4 ">
119- < button type ="submit " class ="btn btn-mint rounded-pill px-4 fw-bold "> 적용</ button >
119+ < button type ="button " class ="btn btn-mint rounded-pill px-4 fw-bold " onclick =" loadSeniors() "> 적용</ button >
120120 </ div >
121121 </ div >
122122 </ div >
@@ -127,10 +127,13 @@ <h1 class="display-5 fw-bold mb-4">성장을 이끌어줄 현업 <span class="te
127127 < div class ="d-flex justify-content-center gap-2 flex-wrap mt-2 ">
128128 < span class ="text-secondary me-1 " style ="font-size:0.9rem; "> 🔥 인기 기술스택</ span >
129129 < th:block th:each ="s : ${popularSkills} ">
130- < a th:href ="@{/(skill=${s}, careerYears=${condition.careerYears}, maxPrice=${condition.maxPrice}, keyword=${condition.keyword})} "
131- class ="badge rounded-pill py-2 px-3 text-decoration-none "
132- th:classappend ="${condition.skill == s} ? ' bg-mint text-dark' : ' badge-dark' "
133- th:text ="${s} "> </ a >
130+ < button type ="button "
131+ class ="badge rounded-pill py-2 px-3 border-0 "
132+ th:classappend ="${condition.skill == s} ? ' bg-mint text-dark' : ' badge-dark' "
133+ th:attr ="data-skill=${s} "
134+ th:text ="${s} "
135+ onclick ="filterBySkill(this.dataset.skill) ">
136+ </ button >
134137 </ th:block >
135138 </ div >
136139
@@ -142,57 +145,102 @@ <h1 class="display-5 fw-bold mb-4">성장을 이끌어줄 현업 <span class="te
142145 < script th:inline ="javascript ">
143146 const MAX_PRICE = 200000 ;
144147
148+ // 페이지 로드 시 Thymeleaf에서 현재 선택된 skill 값을 초기화
149+ let currentSkill = /*[[${condition.skill}]]*/ '' ;
150+
151+ // ── 슬라이더 표시 업데이트 ───────────────────────────
145152 function updateCareerDisplay ( val ) {
146153 const v = parseInt ( val ) ;
147- const display = document . getElementById ( 'careerDisplay' ) ;
148- if ( v === 0 ) {
149- display . textContent = '전체' ;
150- document . getElementById ( 'careerSlider' ) . name = '' ;
151- } else {
152- display . textContent = v + '년 이상' ;
153- document . getElementById ( 'careerSlider' ) . name = 'careerYears' ;
154- }
154+ document . getElementById ( 'careerDisplay' ) . textContent = v === 0 ? '전체' : v + '년 이상' ;
155155 }
156156
157157 function updatePriceDisplay ( val ) {
158158 const v = parseInt ( val ) ;
159- const display = document . getElementById ( 'priceDisplay' ) ;
160- if ( v >= MAX_PRICE ) {
161- display . textContent = '제한 없음' ;
162- document . getElementById ( 'priceSlider' ) . name = '' ;
163- } else {
164- display . textContent = '₩' + v . toLocaleString ( 'ko-KR' ) + ' 이하' ;
165- document . getElementById ( 'priceSlider' ) . name = 'maxPrice' ;
166- }
159+ document . getElementById ( 'priceDisplay' ) . textContent =
160+ v >= MAX_PRICE ? '제한 없음' : '₩' + v . toLocaleString ( 'ko-KR' ) + ' 이하' ;
161+ }
162+
163+ // ── 현재 필터 값을 쿼리 파라미터로 수집 ─────────────
164+ function getFilterParams ( ) {
165+ const keyword = document . querySelector ( 'input[name="keyword"]' ) . value ;
166+ const career = parseInt ( document . getElementById ( 'careerSlider' ) . value ) ;
167+ const price = parseInt ( document . getElementById ( 'priceSlider' ) . value ) ;
168+
169+ const params = new URLSearchParams ( ) ;
170+ if ( keyword ) params . set ( 'keyword' , keyword ) ;
171+ if ( career > 0 ) params . set ( 'careerYears' , career ) ;
172+ if ( price < MAX_PRICE ) params . set ( 'maxPrice' , price ) ;
173+ if ( currentSkill ) params . set ( 'skill' , currentSkill ) ;
174+ return params ;
175+ }
176+
177+ // ── AJAX로 시니어 목록만 교체 ────────────────────────
178+ function loadSeniors ( ) {
179+ const params = getFilterParams ( ) ;
180+ history . pushState ( null , '' , '/?' + params . toString ( ) ) ;
181+
182+ fetch ( '/seniors/fragment?' + params . toString ( ) )
183+ . then ( res => res . text ( ) )
184+ . then ( html => {
185+ document . getElementById ( 'seniorSection' ) . outerHTML = html ;
186+ } )
187+ . catch ( err => console . error ( '시니어 목록 로딩 실패:' , err ) ) ;
188+ }
189+
190+ // ── 기술스택 배지 토글 ───────────────────────────────
191+ function filterBySkill ( skill ) {
192+ currentSkill = ( currentSkill === skill ) ? '' : skill ;
193+
194+ document . querySelectorAll ( '[data-skill]' ) . forEach ( btn => {
195+ const isActive = btn . dataset . skill === currentSkill ;
196+ btn . classList . toggle ( 'bg-mint' , isActive ) ;
197+ btn . classList . toggle ( 'text-dark' , isActive ) ;
198+ btn . classList . toggle ( 'badge-dark' , ! isActive ) ;
199+ } ) ;
200+
201+ loadSeniors ( ) ;
202+ }
203+
204+ // ── 필터 전체 초기화 ─────────────────────────────────
205+ function resetFilters ( ) {
206+ document . querySelector ( 'input[name="keyword"]' ) . value = '' ;
207+ document . getElementById ( 'careerSlider' ) . value = 0 ;
208+ document . getElementById ( 'priceSlider' ) . value = MAX_PRICE ;
209+ updateCareerDisplay ( 0 ) ;
210+ updatePriceDisplay ( MAX_PRICE ) ;
211+ currentSkill = '' ;
212+ document . querySelectorAll ( '[data-skill]' ) . forEach ( btn => {
213+ btn . classList . remove ( 'bg-mint' , 'text-dark' ) ;
214+ btn . classList . add ( 'badge-dark' ) ;
215+ } ) ;
216+ loadSeniors ( ) ;
167217 }
168218
169219 document . addEventListener ( 'DOMContentLoaded' , function ( ) {
170220 const careerSlider = document . getElementById ( 'careerSlider' ) ;
171- updateCareerDisplay ( careerSlider . value ) ;
172- careerSlider . addEventListener ( 'change' , function ( ) {
173- document . getElementById ( 'searchForm' ) . submit ( ) ;
174- } ) ;
221+ const priceSlider = document . getElementById ( 'priceSlider' ) ;
175222
176- const priceSlider = document . getElementById ( 'priceSlider' ) ;
223+ updateCareerDisplay ( careerSlider . value ) ;
177224 updatePriceDisplay ( priceSlider . value ) ;
178- priceSlider . addEventListener ( 'change' , function ( ) {
179- document . getElementById ( 'searchForm' ) . submit ( ) ;
180- } ) ;
225+
226+ // 슬라이더 조작 완료 시 AJAX 조회
227+ careerSlider . addEventListener ( 'change' , loadSeniors ) ;
228+ priceSlider . addEventListener ( 'change' , loadSeniors ) ;
181229
182230 // 필터가 적용된 상태면 패널 자동 열기
183231 const hasFilter = parseInt ( careerSlider . value ) > 0
184- || parseInt ( priceSlider . value ) < MAX_PRICE ;
232+ || parseInt ( priceSlider . value ) < MAX_PRICE
233+ || currentSkill !== '' ;
185234 if ( hasFilter ) {
186- const panel = document . getElementById ( 'filterPanel' ) ;
187- new bootstrap . Collapse ( panel , { toggle : false } ) . show ( ) ;
235+ new bootstrap . Collapse ( document . getElementById ( 'filterPanel' ) , { toggle : false } ) . show ( ) ;
188236 document . getElementById ( 'filterToggleBtn' ) . classList . add ( 'active' ) ;
189237 }
190238 } ) ;
191239 </ script >
192240</ th:block >
193241
194242 <!-- ── 시니어 목록 섹션 ──────────────────────────────── -->
195- < section class ="container py-5 ">
243+ < section class ="container py-5 " id =" seniorSection " th:fragment =" seniorSection " >
196244 < div class ="d-flex justify-content-between align-items-end mb-4 ">
197245 < div >
198246 < h3 class ="fw-bold "> 당신에게 딱 맞는 시니어 멘토</ h3 >
0 commit comments