/**
* YS Project - Store Detail Page JavaScript
* Mobile-first, vanilla JS (no framework dependency)
*/
(function () {
'use strict';
// ============================================================
// State
// ============================================================
const state = {
storeId: window.YS.storeId,
store: null,
isHearted: false,
heartCount: 0,
reviewPage: 1,
reviewTotalPages: 1,
userReviewId: null,
selectedRating: 0,
reportingReviewId: null,
isSubmitting: false,
};
// ============================================================
// DOM Helpers
// ============================================================
const $ = (sel) => document.querySelector(sel);
const $$ = (sel) => document.querySelectorAll(sel);
const dom = {
storeLoading: $('#storeLoading'),
storeError: $('#storeError'),
storeErrorText: $('#storeErrorText'),
storeContent: $('#storeContent'),
storeImageWrap: $('#storeImageWrap'),
storeBadge: $('#storeBadge'),
storeName: $('#storeName'),
storeMeta: $('#storeMeta'),
storeRating: $('#storeRating'),
storeDescription: $('#storeDescription'),
storeActions: $('#storeActions'),
storeDetails: $('#storeDetails'),
storeTable: $('#storeTable'),
tabsSection: $('#tabsSection'),
tabAtt: $('#tabAtt'),
tabPromo: $('#tabPromo'),
promoList: $('#promoList'),
promoEmpty: $('#promoEmpty'),
attList: $('#attList'),
attEmpty: $('#attEmpty'),
reviewCountBadge: $('#reviewCountBadge'),
reviewFormWrap: $('#reviewFormWrap'),
reviewForm: $('#reviewForm'),
alreadyReviewed: $('#alreadyReviewed'),
loginPrompt: $('#loginPrompt'),
reviewList: $('#reviewList'),
reviewPagination: $('#reviewPagination'),
reviewLoading: $('#reviewLoading'),
noReviews: $('#noReviews'),
starSelector: $('#starSelector'),
ratingValue: $('#ratingValue'),
reviewContent: $('#reviewContent'),
reviewPhoto: $('#reviewPhoto'),
photoFileName: $('#photoFileName'),
reviewSubmitBtn: $('#reviewSubmitBtn'),
reviewFormError: $('#reviewFormError'),
reportModal: $('#reportModal'),
reportModalClose: $('#reportModalClose'),
reportCancelBtn: $('#reportCancelBtn'),
reportSubmitBtn: $('#reportSubmitBtn'),
};
// ============================================================
// API Helpers
// ============================================================
async function fetchJSON(url, options = {}) {
try {
const resp = await fetch(url, options);
const data = await resp.json();
if (!resp.ok || !data.success) {
var err = new Error(data.error || 'Request failed');
err.status = resp.status;
throw err;
}
return data;
} catch (err) {
console.error('API Error:', err);
throw err;
}
}
// ============================================================
// Utility Functions
// ============================================================
function escapeHTML(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function escapeAttr(str) {
if (!str) return '';
return str.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(//g, '>');
}
function formatCount(num) {
num = parseInt(num, 10) || 0;
if (num >= 10000) return (num / 10000).toFixed(1) + '\uB9CC';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return String(num);
}
function renderStars(rating, size) {
const full = Math.floor(rating);
const half = rating - full >= 0.5 ? 1 : 0;
const empty = 5 - full - half;
let html = '';
for (let i = 0; i < full; i++) html += '★';
if (half) html += '★';
for (let i = 0; i < empty; i++) html += '★';
return html;
}
// ============================================================
// Toast Notification
// ============================================================
let toastTimeout = null;
function showToast(message) {
let toast = document.querySelector('.toast');
if (!toast) {
toast = document.createElement('div');
toast.className = 'toast';
document.body.appendChild(toast);
}
toast.textContent = message;
toast.classList.add('toast--visible');
if (toastTimeout) clearTimeout(toastTimeout);
toastTimeout = setTimeout(() => {
toast.classList.remove('toast--visible');
}, 2500);
}
// ============================================================
// Load Store Detail
// ============================================================
async function loadStoreDetail() {
dom.storeLoading.style.display = 'block';
dom.storeError.style.display = 'none';
dom.storeContent.style.display = 'none';
try {
const resp = await fetchJSON('/api/store_detail.php?id=' + state.storeId);
const { store, promotions, attendances, review_summary, is_hearted } = resp.data;
state.store = store;
state.isHearted = is_hearted;
state.heartCount = parseInt(store.heart_count, 10) || 0;
dom.storeLoading.style.display = 'none';
dom.storeContent.style.display = 'block';
// SSR(서버 렌더) 블록은 인터랙티브 UI로 대체되었으므로 숨김 (중복 h1 방지)
var ssrSeo = document.getElementById('ssrSeo');
if (ssrSeo) ssrSeo.style.display = 'none';
// Update page title
document.title = store.store_name + ' - ' + document.title.split(' - ').pop();
renderStoreHeader(store);
renderActions(store);
renderDetails(store);
renderPromotions(promotions);
renderAttendances(attendances);
dom.tabsSection.style.display = 'block';
// Show review board links
var reviewLinksEl = document.getElementById('reviewLinks');
if (reviewLinksEl) reviewLinksEl.style.display = '';
renderReviewSummary(review_summary);
// Show review form or prompt
setupReviewSection();
// Load reviews
loadReviews();
} catch (err) {
dom.storeLoading.style.display = 'none';
dom.storeError.style.display = 'block';
// 권한 부족/오류 시 SSR 블록도 숨겨 기존 UX(에러만 표시) 유지
var ssrSeoErr = document.getElementById('ssrSeo');
if (ssrSeoErr) ssrSeoErr.style.display = 'none';
if (err.status === 401) {
dom.storeErrorText.textContent = err.message || '로그인 후 이용 가능합니다.';
} else if (err.status === 403) {
dom.storeErrorText.textContent = err.message || '열람 권한이 없습니다.';
} else {
dom.storeErrorText.textContent = err.message || '매장을 찾을 수 없습니다.';
}
}
}
function showToast(msg) {
var t = document.createElement('div');
t.textContent = msg;
t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 20px;border-radius:6px;font-size:14px;z-index:99999;opacity:0;transition:opacity .3s;';
document.body.appendChild(t);
requestAnimationFrame(function(){ t.style.opacity = '1'; });
setTimeout(function(){ t.style.opacity = '0'; setTimeout(function(){ t.remove(); }, 300); }, 2500);
}
// ============================================================
// Render Store Header
// ============================================================
function renderStoreHeader(store) {
// Image
if (store.image_url && !store.image_url.endsWith('.svg')) {
dom.storeImageWrap.innerHTML = '
';
} else {
dom.storeImageWrap.innerHTML = '
✂
';
}
// Badge
if (store.ad_plan === 'premium') {
dom.storeBadge.textContent = 'PREMIUM';
dom.storeBadge.className = 'sd__badge sd__badge--premium';
dom.storeBadge.style.display = 'inline-block';
}
// Name
dom.storeName.textContent = store.store_name;
// Meta
let metaHTML = '';
if (store.full_region_name) {
metaHTML += '📍 ' + escapeHTML(store.full_region_name) + '';
}
if (store.industry_name) {
metaHTML += '💈 ' + escapeHTML(store.industry_name) + '';
}
dom.storeMeta.innerHTML = metaHTML;
// Rating
const avgRating = parseFloat(store.avg_rating) || 0;
const reviewCount = parseInt(store.review_count, 10) || 0;
dom.storeRating.innerHTML = '' + renderStars(avgRating, 'header') + '
' +
'' + store.avg_rating + '' +
'(' + formatCount(reviewCount) + '개 리뷰)';
// Description (server sanitizes to only allow and
)
if (store.description) {
dom.storeDescription.innerHTML = store.description;
dom.storeDescription.style.display = 'block';
}
}
// ============================================================
// Render Action Buttons
// ============================================================
function renderActions(store) {
let html = '';
// Heart button
const heartClass = state.isHearted ? ' active' : '';
const heartIcon = state.isHearted ? '♥' : '♡';
html += '';
// Phone
if (store.phone_number) {
html += '' +
'📞' +
'전화하기' +
'';
}
// Kakao
if (store.messenger_kakao) {
html += '' +
'💬' +
'카카오톡' +
'';
}
// Telegram
if (store.messenger_telegram) {
html += '' +
'✈' +
'텔레그램' +
'';
}
// WhatsApp
if (store.messenger_whatsapp) {
html += '' +
'💬' +
'왓츠앱' +
'';
}
// Message button (logged-in users only, not owners, min level check)
if (window.YS.isLoggedIn && !window.YS.isOwner) {
var minLv = window.YS.messageMinLevel || 5;
if ((window.YS.userLevel || 0) >= minLv) {
html += '' +
'✉' +
'메시지 보내기' +
'';
} else {
html += '' +
'✉' +
'메시지 (Lv.' + minLv + ')' +
'';
}
}
dom.storeActions.innerHTML = html;
// Bind heart button
var heartBtn = document.getElementById('heartBtn');
if (heartBtn) {
heartBtn.addEventListener('click', toggleHeart);
}
}
// ============================================================
// Render Store Details Table
// ============================================================
function renderDetails(store) {
let rows = '';
if (store.phone_number) {
rows += '| 전화번호 | ' + escapeHTML(store.phone_number) + ' |
';
}
if (store.address) {
rows += '| 주소 | ' + escapeHTML(store.address) + ' |
';
}
if (store.business_hours) {
rows += '| 영업시간 | ' + escapeHTML(store.business_hours) + ' |
';
}
if (store.full_region_name) {
rows += '| 지역 | ' + escapeHTML(store.full_region_name) + ' |
';
}
if (store.industry_name) {
rows += '| 업종 | ' + escapeHTML(store.industry_name) + ' |
';
}
if (rows) {
dom.storeTable.innerHTML = rows;
dom.storeDetails.style.display = 'block';
}
}
// ============================================================
// Render Promotions
// ============================================================
function renderPromotions(promotions) {
if (!promotions || promotions.length === 0) {
dom.promoEmpty.style.display = 'block';
return;
}
dom.promoEmpty.style.display = 'none';
let html = '';
// Show first 3 promotions, rest toggleable
promotions.forEach(function (promo, idx) {
var hiddenClass = idx >= 3 ? ' style="display:none;" data-extra-promo="true"' : '';
html += '' +
'' +
'' + promo.content + '
' +
'' + escapeHTML(promo.created_at ? promo.created_at.substring(0, 10) : '') + '
' +
'';
});
if (promotions.length > 3) {
html += '';
}
dom.promoList.innerHTML = html;
// Toggle handler
var toggle = document.getElementById('promoToggle');
if (toggle) {
toggle.addEventListener('click', function () {
var extras = dom.promoList.querySelectorAll('[data-extra-promo]');
var hidden = extras[0] && extras[0].style.display === 'none';
extras.forEach(function (el) {
el.style.display = hidden ? 'block' : 'none';
});
toggle.textContent = hidden ? '접기' : '더보기 (' + extras.length + '개)';
});
}
}
// ============================================================
// Render Attendances
// ============================================================
function renderAttendances(attendances) {
if (!attendances || attendances.length === 0) {
dom.attEmpty.style.display = 'block';
return;
}
dom.attEmpty.style.display = 'none';
let html = '';
var store = state.store || {};
var descText = (store.description || '').replace(/<[^>]*>/g, '').trim();
attendances.forEach(function (att) {
// 표시 제목: title → description → store_name
var displayTitle = (att.title && att.title.trim())
? att.title
: (descText || store.store_name || '');
var titleHtml = displayTitle
? '' + escapeHTML(displayTitle) + '
'
: '';
html += '' +
'' + escapeHTML(att.date || '') + '
' +
titleHtml +
'' + att.content + '
' +
'';
});
dom.attList.innerHTML = html;
}
// ============================================================
// Render Review Summary
// ============================================================
function renderReviewSummary(summary) {
var count = summary.review_count || 0;
dom.reviewCountBadge.textContent = count > 0 ? '(' + formatCount(count) + ')' : '';
}
// ============================================================
// Setup Review Section (form visibility)
// ============================================================
function setupReviewSection() {
if (window.YS.isLoggedIn && !window.YS.isOwner) {
// Will show form or already-reviewed after reviews load
dom.reviewFormWrap.style.display = 'block';
} else if (!window.YS.isLoggedIn) {
dom.loginPrompt.style.display = 'block';
}
// Owner: show nothing (owners don't review)
}
// ============================================================
// Load Reviews
// ============================================================
async function loadReviews() {
dom.reviewLoading.style.display = 'block';
dom.reviewList.innerHTML = '';
dom.reviewPagination.innerHTML = '';
dom.noReviews.style.display = 'none';
try {
var params = new URLSearchParams({
store_id: state.storeId,
page: state.reviewPage,
per_page: 10,
});
var resp = await fetchJSON('/api/reviews/list.php?' + params);
var data = resp.data;
dom.reviewLoading.style.display = 'none';
state.userReviewId = data.user_review_id;
state.reviewTotalPages = data.pagination.total_pages;
// Show/hide form based on whether user already reviewed
if (window.YS.isLoggedIn && !window.YS.isOwner) {
if (state.userReviewId) {
dom.reviewFormWrap.style.display = 'none';
dom.alreadyReviewed.style.display = 'block';
} else {
dom.reviewFormWrap.style.display = 'block';
dom.alreadyReviewed.style.display = 'none';
}
}
if (data.reviews.length === 0) {
dom.noReviews.style.display = 'block';
return;
}
renderReviews(data.reviews);
renderReviewPagination(data.pagination);
} catch (err) {
dom.reviewLoading.style.display = 'none';
dom.noReviews.style.display = 'block';
dom.noReviews.textContent = '리뷰를 불러오지 못했습니다.';
}
}
// ============================================================
// Render Reviews
// ============================================================
function renderReviews(reviews) {
var fragment = document.createDocumentFragment();
reviews.forEach(function (review) {
var card = document.createElement('div');
card.className = 'review-card' + (review.is_blinded ? ' review-card--blinded' : '');
var starsHTML = '';
if (review.rating !== null) {
starsHTML = '' + renderStars(review.rating, 'card') + '
';
}
var photoHTML = '';
if (review.photo_url) {
photoHTML = '
';
}
var actionsHTML = '';
if (window.YS.isLoggedIn && !review.is_blinded) {
var btns = '';
if (window.YS.userId === review.user_id) {
btns += '';
} else {
btns += '';
}
actionsHTML = '' + btns + '
';
}
var levelBadge = '';
if (review.level_label && review.level_class) {
levelBadge = '' + escapeHTML(review.level_label) + ' ';
}
card.innerHTML = '' +
'
' +
'' + levelBadge + escapeHTML(review.nickname || '탈퇴한 회원') + '' +
'' + escapeHTML(review.created_at) + '' +
'
' +
starsHTML +
'
' +
'' + escapeHTML(review.content) + '
' +
photoHTML +
actionsHTML;
fragment.appendChild(card);
});
dom.reviewList.appendChild(fragment);
}
// ============================================================
// Render Review Pagination
// ============================================================
function renderReviewPagination(pagination) {
var page = pagination.page;
var totalPages = pagination.total_pages;
if (totalPages <= 1) return;
var container = dom.reviewPagination;
container.innerHTML = '';
// Previous
var prevBtn = createPageBtn('\u2039 이전', page - 1, page <= 1);
container.appendChild(prevBtn);
var startPage = Math.max(1, page - 2);
var endPage = Math.min(totalPages, page + 2);
if (startPage > 1) {
container.appendChild(createPageBtn('1', 1));
if (startPage > 2) {
var dots = document.createElement('span');
dots.className = 'pagination__btn';
dots.textContent = '...';
dots.style.pointerEvents = 'none';
container.appendChild(dots);
}
}
for (var i = startPage; i <= endPage; i++) {
var btn = createPageBtn(String(i), i);
if (i === page) btn.classList.add('pagination__btn--active');
container.appendChild(btn);
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
var dots2 = document.createElement('span');
dots2.className = 'pagination__btn';
dots2.textContent = '...';
dots2.style.pointerEvents = 'none';
container.appendChild(dots2);
}
container.appendChild(createPageBtn(String(totalPages), totalPages));
}
// Next
var nextBtn = createPageBtn('다음 \u203A', page + 1, page >= totalPages);
container.appendChild(nextBtn);
}
function createPageBtn(label, page, disabled) {
var btn = document.createElement('button');
btn.className = 'pagination__btn';
btn.innerHTML = label;
btn.disabled = !!disabled;
if (!disabled) {
btn.addEventListener('click', function () {
state.reviewPage = page;
loadReviews();
// Scroll to reviews section
var section = document.querySelector('.sd__reviews');
if (section) {
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
}
return btn;
}
// ============================================================
// Heart Toggle
// ============================================================
async function toggleHeart() {
if (!window.YS.isLoggedIn) {
showToast(window.YS.isOwner ? '일반 회원으로 로그인 후 이용해 주세요.' : '로그인 후 이용해 주세요.');
return;
}
try {
var resp = await fetchJSON('/api/heart_toggle.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ store_id: state.storeId }),
});
state.isHearted = resp.data.is_hearted;
state.heartCount = resp.data.heart_count;
// Update button
var heartBtn = document.getElementById('heartBtn');
if (heartBtn) {
if (state.isHearted) {
heartBtn.classList.add('active');
heartBtn.querySelector('.sd__action-icon').innerHTML = '♥';
document.getElementById('heartBtnText').textContent = '즐겨찾기 해제';
} else {
heartBtn.classList.remove('active');
heartBtn.querySelector('.sd__action-icon').innerHTML = '♡';
document.getElementById('heartBtnText').textContent = '즐겨찾기';
}
document.getElementById('heartBtnCount').textContent = '(' + formatCount(state.heartCount) + ')';
}
showToast(state.isHearted ? '즐겨찾기에 추가했습니다.' : '즐겨찾기를 해제했습니다.');
} catch (err) {
showToast('처리 중 오류가 발생했습니다.');
}
}
// ============================================================
// Star Rating Selector
// ============================================================
function setupStarSelector() {
if (!dom.starSelector) return;
var stars = dom.starSelector.querySelectorAll('.review-form__star');
stars.forEach(function (star) {
star.addEventListener('click', function () {
var rating = parseInt(this.dataset.rating, 10);
state.selectedRating = rating;
dom.ratingValue.value = rating;
updateStarDisplay(rating);
});
star.addEventListener('mouseenter', function () {
var rating = parseInt(this.dataset.rating, 10);
updateStarDisplay(rating);
});
});
dom.starSelector.addEventListener('mouseleave', function () {
updateStarDisplay(state.selectedRating);
});
}
function updateStarDisplay(rating) {
var stars = dom.starSelector.querySelectorAll('.review-form__star');
stars.forEach(function (star, idx) {
if (idx < rating) {
star.classList.add('review-form__star--active');
} else {
star.classList.remove('review-form__star--active');
}
});
}
// ============================================================
// Photo File Name Display
// ============================================================
function setupPhotoInput() {
if (!dom.reviewPhoto) return;
dom.reviewPhoto.addEventListener('change', function () {
if (this.files && this.files[0]) {
dom.photoFileName.textContent = this.files[0].name;
} else {
dom.photoFileName.textContent = '';
}
});
}
// ============================================================
// Review Form Submit
// ============================================================
function setupReviewForm() {
if (!dom.reviewForm) return;
dom.reviewForm.addEventListener('submit', async function (e) {
e.preventDefault();
if (state.isSubmitting) return;
// Validate
var rating = parseInt(dom.ratingValue.value, 10);
var content = dom.reviewContent.value.trim();
if (rating < 1 || rating > 5) {
showFormError('별점을 선택해주세요.');
return;
}
if (content.length < 10) {
showFormError('리뷰 내용을 10자 이상 입력해주세요.');
return;
}
state.isSubmitting = true;
dom.reviewSubmitBtn.disabled = true;
dom.reviewSubmitBtn.textContent = '등록 중...';
hideFormError();
try {
var formData = new FormData(dom.reviewForm);
var resp = await fetch('/api/reviews/create.php', {
method: 'POST',
body: formData,
});
var data = await resp.json();
if (!data.success) {
throw new Error(data.error || '리뷰 등록에 실패했습니다.');
}
showToast('리뷰가 등록되었습니다!');
// Reset form
dom.reviewForm.reset();
state.selectedRating = 0;
updateStarDisplay(0);
dom.photoFileName.textContent = '';
// Hide form, show already-reviewed
dom.reviewFormWrap.style.display = 'none';
dom.alreadyReviewed.style.display = 'block';
// Reload reviews
state.reviewPage = 1;
loadReviews();
// Reload store to update avg rating
reloadStoreRating();
} catch (err) {
showFormError(err.message);
} finally {
state.isSubmitting = false;
dom.reviewSubmitBtn.disabled = false;
dom.reviewSubmitBtn.textContent = '리뷰 등록';
}
});
}
function showFormError(msg) {
dom.reviewFormError.textContent = msg;
dom.reviewFormError.style.display = 'block';
}
function hideFormError() {
dom.reviewFormError.style.display = 'none';
}
// ============================================================
// Reload Store Rating After Review
// ============================================================
async function reloadStoreRating() {
try {
var resp = await fetchJSON('/api/store_detail.php?id=' + state.storeId);
var store = resp.data.store;
// Update rating display
var avgRating = parseFloat(store.avg_rating) || 0;
var reviewCount = parseInt(store.review_count, 10) || 0;
dom.storeRating.innerHTML = '' + renderStars(avgRating, 'header') + '
' +
'' + store.avg_rating + '' +
'(' + formatCount(reviewCount) + '개 리뷰)';
dom.reviewCountBadge.textContent = reviewCount > 0 ? '(' + formatCount(reviewCount) + ')' : '';
} catch (err) {
// Silently fail
}
}
// ============================================================
// Report Modal
// ============================================================
function setupReportModal() {
// Event delegation for report and delete buttons
dom.reviewList.addEventListener('click', function (e) {
var reportBtn = e.target.closest('.review-card__report-btn');
if (reportBtn) {
var reviewId = parseInt(reportBtn.dataset.reviewId, 10);
openReportModal(reviewId);
return;
}
var deleteBtn = e.target.closest('.review-card__delete-btn');
if (deleteBtn) {
var delReviewId = parseInt(deleteBtn.dataset.reviewId, 10);
handleDeleteReview(delReviewId, deleteBtn);
}
});
// Close modal
if (dom.reportModalClose) {
dom.reportModalClose.addEventListener('click', closeReportModal);
}
if (dom.reportCancelBtn) {
dom.reportCancelBtn.addEventListener('click', closeReportModal);
}
// Close on overlay click
if (dom.reportModal) {
dom.reportModal.addEventListener('click', function (e) {
if (e.target === dom.reportModal) {
closeReportModal();
}
});
}
// Submit report
if (dom.reportSubmitBtn) {
dom.reportSubmitBtn.addEventListener('click', submitReport);
}
}
function openReportModal(reviewId) {
if (!window.YS.isLoggedIn) {
showToast(window.YS.isOwner ? '일반 회원으로 로그인 후 이용해 주세요.' : '로그인 후 이용해 주세요.');
return;
}
state.reportingReviewId = reviewId;
// Reset radio
var radios = dom.reportModal.querySelectorAll('input[name="report_reason"]');
radios.forEach(function (r) { r.checked = false; });
dom.reportModal.style.display = 'flex';
}
function closeReportModal() {
dom.reportModal.style.display = 'none';
state.reportingReviewId = null;
}
async function submitReport() {
var reason = '';
var radios = dom.reportModal.querySelectorAll('input[name="report_reason"]');
radios.forEach(function (r) {
if (r.checked) reason = r.value;
});
if (!reason) {
showToast('신고 사유를 선택해주세요.');
return;
}
try {
dom.reportSubmitBtn.disabled = true;
dom.reportSubmitBtn.textContent = '처리 중...';
var resp = await fetchJSON('/api/reviews/report.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
review_id: state.reportingReviewId,
reason: reason,
csrf_token: window.YS.csrfToken,
}),
});
showToast(resp.data.message);
closeReportModal();
} catch (err) {
showToast(err.message || '신고 처리 중 오류가 발생했습니다.');
} finally {
dom.reportSubmitBtn.disabled = false;
dom.reportSubmitBtn.textContent = '신고하기';
}
}
// ============================================================
// Delete Review
// ============================================================
async function handleDeleteReview(reviewId, btn) {
if (!confirm('리뷰를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.')) {
return;
}
try {
btn.disabled = true;
btn.textContent = '삭제 중...';
await fetchJSON('/api/reviews/delete.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
review_id: reviewId,
csrf_token: window.YS.csrfToken,
}),
});
showToast('리뷰가 삭제되었습니다.');
// Reload reviews
state.reviewPage = 1;
dom.reviewList.innerHTML = '';
loadReviews();
refreshStoreStats();
} catch (err) {
showToast(err.message || '리뷰 삭제 중 오류가 발생했습니다.');
btn.disabled = false;
btn.textContent = '삭제';
}
}
// ============================================================
// Initialize
// ============================================================
// ============================================================
// Tab Switching
// ============================================================
function setupTabs() {
var tabs = $$('.sd__tab');
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var target = this.dataset.tab;
tabs.forEach(function (t) { t.classList.remove('sd__tab--active'); });
this.classList.add('sd__tab--active');
// Toggle panels
dom.tabAtt.classList.toggle('sd__tab-panel--active', target === 'att');
dom.tabPromo.classList.toggle('sd__tab-panel--active', target === 'promo');
});
});
}
function init() {
setupTabs();
setupStarSelector();
setupPhotoInput();
setupReviewForm();
setupReportModal();
loadStoreDetail();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();