Skip to content

Commit b9a5263

Browse files
authored
Merge pull request #77 from pirogramming/fix/custom-beverage
fix: 음료입력창 직접입력필드 추가
2 parents 4518d42 + 0d36b31 commit b9a5263

3 files changed

Lines changed: 128 additions & 2 deletions

File tree

‎static/css/favorites.css‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,53 @@
198198
align-items: start;
199199
}
200200

201+
.custom_form {
202+
display: flex;
203+
flex-direction: column;
204+
gap: 10px;
205+
width: 100%;
206+
max-width: 320px;
207+
margin: 0 auto;
208+
/* 모달 배경(카드 svg)이 고정 비율이라, 폼이 카드 영역보다 커지면
209+
카드 밖으로 삐져나간다. 카드 안에서 자체 스크롤되도록 높이를 제한한다. */
210+
max-height: 200px;
211+
overflow-y: auto;
212+
padding-right: 8px;
213+
}
214+
215+
.custom_form::-webkit-scrollbar {
216+
width: 6px;
217+
}
218+
219+
.custom_form::-webkit-scrollbar-thumb {
220+
background-color: var(--cd-brown-light);
221+
border-radius: 10px;
222+
}
223+
224+
.custom_form label {
225+
display: flex;
226+
flex-direction: column;
227+
gap: 6px;
228+
font-size: 14px;
229+
color: var(--cd-brown-ink);
230+
text-align: left;
231+
}
232+
233+
.custom_form input {
234+
padding: 8px 14px;
235+
border: 1px solid var(--cd-brown-light);
236+
border-radius: 20px;
237+
font-size: 14px;
238+
font-family: var(--cd-font-body);
239+
color: var(--cd-brown-ink);
240+
background: #fff;
241+
}
242+
243+
.custom_form input:focus {
244+
outline: none;
245+
border-color: var(--cd-brown-dark);
246+
}
247+
201248
.option_search {
202249
display: block;
203250
width: 100%;
@@ -392,6 +439,10 @@
392439
grid-template-columns: repeat(2, 1fr);
393440
}
394441

442+
.custom_form {
443+
max-width: 100%;
444+
}
445+
395446
.option_item img {
396447
width: 50px;
397448
height: 50px;

‎static/js/favorites.js‎

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* favorites.js — 즐겨찾는 음료(최대 3개) 목록을 GET /users/me/drinks/로 채우고,
33
* "추가"/"수정" 오버레이(종류→브랜드→메뉴→사이즈→아이콘)로 선택한 프리셋을
44
* POST .../from-preset/(추가) 또는 PATCH .../<id>/(교체)로 저장한다.
5+
* 브랜드 단계에서 "직접입력"을 고르면 메뉴/사이즈 프리셋 단계 대신 자유 입력
6+
* 폼으로 가고, POST/PATCH .../drinks/(직접입력 전용 엔드포인트)로 저장한다.
57
*/
68
(function () {
79
const listEl = document.getElementById('favorite-drinks-list');
@@ -14,12 +16,20 @@
1416
brand: overlay.querySelector('[data-step="2"]'),
1517
menu: overlay.querySelector('[data-step="3"]'),
1618
size: overlay.querySelector('[data-step="4"]'),
19+
custom: overlay.querySelector('[data-step="custom"]'),
1720
icon: overlay.querySelector('[data-step="5"]'),
1821
};
1922

23+
const customBrandInput = document.getElementById('custom-brand-input');
24+
const customNameInput = document.getElementById('custom-name-input');
25+
const customSizeInput = document.getElementById('custom-size-input');
26+
const customCaffeineInput = document.getElementById('custom-caffeine-input');
27+
const customNextBtn = document.getElementById('wizard-custom-next-btn');
28+
2029
let wizard = {
2130
mode: 'add', editingId: null,
2231
type: null, brand: null, name: null, size: null, iconKey: null,
32+
customBrandName: '', caffeineMg: null,
2333
presetsForType: [],
2434
};
2535

@@ -123,13 +133,18 @@
123133
wizard = {
124134
mode, editingId: editingId ?? null,
125135
type: null, brand: null, name: null, size: null, iconKey: null,
136+
customBrandName: '', caffeineMg: null,
126137
presetsForType: [],
127138
};
128139
steps.type.querySelectorAll('.option_item').forEach((el) => el.classList.remove('is-selected'));
129140
clearOptionList(steps.brand);
130141
clearOptionList(steps.menu);
131142
clearOptionList(steps.size);
132143
clearOptionList(steps.icon);
144+
customBrandInput.value = '';
145+
customNameInput.value = '';
146+
customSizeInput.value = '';
147+
customCaffeineInput.value = '';
133148
showStep('type');
134149
overlay.style.display = 'flex';
135150
}
@@ -144,7 +159,9 @@
144159

145160
const seen = new Map();
146161
wizard.presetsForType.forEach((p) => { if (!seen.has(p.brand)) seen.set(p.brand, p.brand_name); });
147-
renderOptionList(steps.brand, [...seen.entries()].map(([value, label]) => ({ value, label })), (value) => {
162+
const options = [...seen.entries()].map(([value, label]) => ({ value, label }));
163+
options.push({ value: 'custom', label: '직접입력' });
164+
renderOptionList(steps.brand, options, (value) => {
148165
wizard.brand = value;
149166
});
150167
}
@@ -187,7 +204,21 @@
187204

188205
async function saveDrink() {
189206
let res;
190-
if (wizard.mode === 'add') {
207+
if (wizard.brand === 'custom') {
208+
const payload = {
209+
type: wizard.type,
210+
brand: 'custom',
211+
custom_brand_name: wizard.customBrandName,
212+
name: wizard.name,
213+
size: wizard.size,
214+
caffeine_mg: wizard.caffeineMg,
215+
icon_key: wizard.iconKey,
216+
is_favorite: true,
217+
};
218+
res = wizard.mode === 'add'
219+
? await apiFetch('/users/me/drinks/', { method: 'POST', body: JSON.stringify(payload) })
220+
: await apiFetch(`/users/me/drinks/${wizard.editingId}/`, { method: 'PATCH', body: JSON.stringify(payload) });
221+
} else if (wizard.mode === 'add') {
191222
res = await apiFetch('/users/me/drinks/from-preset/', {
192223
method: 'POST',
193224
body: JSON.stringify({
@@ -237,10 +268,30 @@
237268

238269
steps.brand.querySelector('.next_btn').addEventListener('click', () => {
239270
if (!wizard.brand) { alert('브랜드를 선택해주세요.'); return; }
271+
if (wizard.brand === 'custom') {
272+
showStep('custom');
273+
return;
274+
}
240275
loadMenuOptions();
241276
showStep('menu');
242277
});
243278

279+
customNextBtn.addEventListener('click', () => {
280+
const name = customNameInput.value.trim();
281+
const caffeine = Number(customCaffeineInput.value);
282+
if (!name) { alert('음료 이름을 입력해주세요.'); return; }
283+
if (!customCaffeineInput.value || !(caffeine > 0 && caffeine <= 1000)) {
284+
alert('카페인 함량을 1~1000mg 사이로 입력해주세요.');
285+
return;
286+
}
287+
wizard.customBrandName = customBrandInput.value.trim();
288+
wizard.name = name;
289+
wizard.size = customSizeInput.value.trim();
290+
wizard.caffeineMg = caffeine;
291+
loadIconOptions();
292+
showStep('icon');
293+
});
294+
244295
steps.menu.querySelector('.next_btn').addEventListener('click', () => {
245296
if (!wizard.name) { alert('메뉴를 선택해주세요.'); return; }
246297
loadSizeOptions();

‎templates/diary/favorites.html‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,30 @@ <h3>어떤 사이즈로 마시나요?</h3>
6464
<button type="button" class="next_btn" data-next="icon">다음</button>
6565
</div>
6666

67+
<!-- 2단계에서 "직접입력"을 고르면 메뉴/사이즈 프리셋 단계를 건너뛰고 여기로 온다 -->
68+
<div class="overlay_step overlay_step--custom" data-step="custom" style="display:none;">
69+
<h3>음료 정보를 입력해주세요</h3>
70+
<div class="custom_form">
71+
<label>
72+
<span>브랜드명 (선택)</span>
73+
<input type="text" id="custom-brand-input" placeholder="예: GS25" maxlength="100" autocomplete="off">
74+
</label>
75+
<label>
76+
<span>음료 이름</span>
77+
<input type="text" id="custom-name-input" placeholder="예: 아메리카노" maxlength="100" autocomplete="off">
78+
</label>
79+
<label>
80+
<span>사이즈 (선택)</span>
81+
<input type="text" id="custom-size-input" placeholder="예: M" maxlength="50" autocomplete="off">
82+
</label>
83+
<label>
84+
<span>카페인 함량 (mg)</span>
85+
<input type="number" id="custom-caffeine-input" placeholder="예: 150" min="1" max="1000" inputmode="numeric">
86+
</label>
87+
</div>
88+
<button type="button" class="next_btn" id="wizard-custom-next-btn">다음</button>
89+
</div>
90+
6791
<!-- 5단계: 아이콘 선택 -->
6892
<div class="overlay_step overlay_step--icons" data-step="5" style="display:none;">
6993
<h3>아이콘을 선택하세요</h3>

0 commit comments

Comments
 (0)