2015 lines
70 KiB
JavaScript
2015 lines
70 KiB
JavaScript
// zzim.js - 찜관리 모듈
|
||
|
||
class ZzimManager {
|
||
constructor() {
|
||
this.SUPABASE_URL = null;
|
||
this.SUPABASE_ANON_KEY = null;
|
||
this.access_token = null;
|
||
this.user_id = null;
|
||
this.zzimInProgress = false;
|
||
this.currentEditIndex = null;
|
||
}
|
||
|
||
async init() {
|
||
console.log('ZzimManager 초기화 중...');
|
||
|
||
try {
|
||
// Chrome Extension 환경에서 설정값 가져오기
|
||
if (typeof chrome !== 'undefined' && chrome.storage) {
|
||
const config = await chrome.storage.local.get(['zzim_config']);
|
||
|
||
if (config.zzim_config) {
|
||
this.access_token = config.zzim_config.ACCESS_TOKEN;
|
||
this.user_id = config.zzim_config.USER_ID;
|
||
this.SUPABASE_URL = config.zzim_config.SUPABASE_URL || "https://ko.wrmc.cc";
|
||
this.SUPABASE_ANON_KEY = config.zzim_config.SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzYyMzA2ODc5LCJleHAiOjIwNzc2NjY4Nzl9.aKF_nREC06KK81yOJKA1pOwz9gmgC0xsLwLWqqIVcsU";
|
||
} else {
|
||
// 기존 방식으로 폴백
|
||
const fallbackConfig = await chrome.storage.local.get(['access_token', 'user_id', 'SUPABASE_URL', 'SUPABASE_ANON_KEY']);
|
||
this.access_token = fallbackConfig.access_token;
|
||
this.user_id = fallbackConfig.user_id;
|
||
this.SUPABASE_URL = fallbackConfig.SUPABASE_URL || "https://ko.wrmc.cc";
|
||
this.SUPABASE_ANON_KEY = fallbackConfig.SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzYyMzA2ODc5LCJleHAiOjIwNzc2NjY4Nzl9.aKF_nREC06KK81yOJKA1pOwz9gmgC0xsLwLWqqIVcsU";
|
||
}
|
||
|
||
if (!this.access_token || !this.user_id) {
|
||
throw new Error('로그인이 필요합니다.');
|
||
}
|
||
|
||
console.log('Chrome Extension 환경에서 설정 로드 완료', {
|
||
hasToken: !!this.access_token,
|
||
hasUserId: !!this.user_id,
|
||
hasUrl: !!this.SUPABASE_URL,
|
||
hasKey: !!this.SUPABASE_ANON_KEY
|
||
});
|
||
} else {
|
||
throw new Error('Chrome Extension 환경이 아닙니다.');
|
||
}
|
||
|
||
// 사용자 찜 설정 초기화
|
||
await this.initializeUserZzimSettings();
|
||
|
||
// 통계 및 마켓 정보 로드
|
||
await Promise.all([
|
||
this.loadZzimStats(),
|
||
this.loadMyMarkets()
|
||
]);
|
||
|
||
// 이벤트 바인딩
|
||
this.bindEvents();
|
||
|
||
// 버튼 상태 초기화
|
||
this.updateButtonStates();
|
||
|
||
// 총 찜하기 간격 초기화
|
||
this.updateTotalDelay();
|
||
|
||
console.log('ZzimManager 초기화 완료');
|
||
} catch (error) {
|
||
console.error('ZzimManager 초기화 실패:', error);
|
||
this.showError('초기화 실패: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async initializeUserZzimSettings() {
|
||
try {
|
||
console.log('사용자 찜 설정 초기화 시작:', {
|
||
user_id: this.user_id,
|
||
supabase_url: this.SUPABASE_URL
|
||
});
|
||
|
||
// user_markets 테이블에 사용자 레코드가 없으면 생성
|
||
const userMarketsResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('user_markets 조회 응답:', {
|
||
status: userMarketsResponse.status,
|
||
ok: userMarketsResponse.ok
|
||
});
|
||
|
||
if (userMarketsResponse.ok) {
|
||
const userMarkets = await userMarketsResponse.json();
|
||
console.log('기존 user_markets 데이터:', userMarkets);
|
||
|
||
if (userMarkets.length === 0) {
|
||
console.log('user_markets 초기 레코드 생성 중...');
|
||
|
||
// user_markets 테이블에 초기 레코드 생성
|
||
const createResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json',
|
||
'Prefer': 'return=representation'
|
||
},
|
||
body: JSON.stringify({
|
||
user_id: this.user_id,
|
||
my_markets: []
|
||
})
|
||
});
|
||
|
||
console.log('user_markets 생성 응답:', {
|
||
status: createResponse.status,
|
||
ok: createResponse.ok
|
||
});
|
||
|
||
if (!createResponse.ok) {
|
||
const errorText = await createResponse.text();
|
||
console.error('user_markets 생성 실패:', errorText);
|
||
|
||
// 409 Conflict (이미 존재)는 무시하고 계속 진행
|
||
if (createResponse.status !== 409) {
|
||
throw new Error('user_markets 레코드 생성 실패: ' + errorText);
|
||
} else {
|
||
console.log('user_markets 레코드가 이미 존재함 (409 Conflict 무시)');
|
||
}
|
||
} else {
|
||
const createResult = await createResponse.json();
|
||
console.log('user_markets 초기 레코드 생성 완료:', createResult);
|
||
}
|
||
} else {
|
||
console.log('기존 user_markets 레코드 존재함');
|
||
}
|
||
} else {
|
||
console.error('user_markets 조회 실패:', userMarketsResponse.status);
|
||
const errorText = await userMarketsResponse.text();
|
||
console.error('user_markets 조회 오류 상세:', errorText);
|
||
|
||
// 404 Not Found인 경우 테이블이 없을 수 있으므로 계속 진행
|
||
if (userMarketsResponse.status !== 404) {
|
||
throw new Error('user_markets 조회 실패: ' + errorText);
|
||
}
|
||
}
|
||
|
||
console.log('users 테이블 필드 초기화 중...');
|
||
|
||
// users 테이블의 my_zzim, zzim_mile 필드가 null이면 초기화
|
||
const usersUpdateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/users?id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json',
|
||
'Prefer': 'return=representation'
|
||
},
|
||
body: JSON.stringify({
|
||
my_zzim: 0, // COALESCE 대신 직접 0으로 설정
|
||
zzim_mile: 0
|
||
})
|
||
});
|
||
|
||
console.log('users 테이블 업데이트 응답:', {
|
||
status: usersUpdateResponse.status,
|
||
ok: usersUpdateResponse.ok
|
||
});
|
||
|
||
if (!usersUpdateResponse.ok) {
|
||
const errorText = await usersUpdateResponse.text();
|
||
console.error('users 테이블 업데이트 실패:', errorText);
|
||
// users 테이블 업데이트 실패는 치명적이지 않으므로 경고만 출력
|
||
console.warn('users 테이블 업데이트 실패, 계속 진행');
|
||
} else {
|
||
const usersResult = await usersUpdateResponse.json();
|
||
console.log('users 테이블 필드 초기화 완료:', usersResult);
|
||
}
|
||
|
||
console.log('사용자 찜 설정 초기화 완료');
|
||
|
||
} catch (error) {
|
||
console.error('찜 설정 초기화 오류:', error);
|
||
this.showError('찜 설정 초기화 중 오류가 발생했습니다: ' + error.message);
|
||
}
|
||
}
|
||
|
||
bindEvents() {
|
||
// 마켓 추가 버튼
|
||
const addMarketBtn = document.getElementById('add-market-btn');
|
||
if (addMarketBtn) {
|
||
addMarketBtn.addEventListener('click', () => this.addMarket());
|
||
}
|
||
|
||
// 내 마켓 찜하기 버튼
|
||
const myMarketZzimBtn = document.getElementById('my-market-zzim-btn');
|
||
if (myMarketZzimBtn) {
|
||
myMarketZzimBtn.addEventListener('click', () => this.startMyMarketZzim());
|
||
}
|
||
|
||
// 품앗이 찜하기 버튼
|
||
const mutualZzimBtn = document.getElementById('mutual-zzim-btn');
|
||
if (mutualZzimBtn) {
|
||
mutualZzimBtn.addEventListener('click', () => this.startMutualZzim());
|
||
}
|
||
|
||
// 찜하기 중단 버튼
|
||
const stopZzimBtn = document.getElementById('stop-zzim-btn');
|
||
if (stopZzimBtn) {
|
||
stopZzimBtn.addEventListener('click', () => this.stopZzim());
|
||
}
|
||
|
||
// 모달 취소 버튼
|
||
const modalCancelBtn = document.getElementById('modal-cancel-btn');
|
||
if (modalCancelBtn) {
|
||
modalCancelBtn.addEventListener('click', () => this.closeEditModal());
|
||
}
|
||
|
||
// 모달 저장 버튼
|
||
const modalSaveBtn = document.getElementById('modal-save-btn');
|
||
if (modalSaveBtn) {
|
||
modalSaveBtn.addEventListener('click', () => this.saveMarketEdit());
|
||
}
|
||
|
||
// 모달 닫기 버튼 (×)
|
||
const modalCloseBtn = document.getElementById('modal-close-btn');
|
||
if (modalCloseBtn) {
|
||
modalCloseBtn.addEventListener('click', () => this.closeEditModal());
|
||
}
|
||
|
||
// 마켓 URL 입력 시 자동 변환
|
||
const marketUrlInput = document.getElementById('market-url');
|
||
if (marketUrlInput) {
|
||
marketUrlInput.addEventListener('input', (e) => {
|
||
let url = e.target.value;
|
||
if (url && !url.startsWith('https://smartstore.naver.com/')) {
|
||
// URL이 smartstore로 시작하지 않으면 자동으로 앞에 추가
|
||
if (!url.startsWith('http')) {
|
||
url = 'https://smartstore.naver.com/' + url;
|
||
e.target.value = url;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 찜하기 속도 설정 이벤트
|
||
const userDelayInput = document.getElementById('user-delay');
|
||
if (userDelayInput) {
|
||
userDelayInput.addEventListener('input', () => this.updateTotalDelay());
|
||
}
|
||
|
||
// 디버깅용 테스트 버튼 (있는 경우에만)
|
||
const testBtn = document.getElementById('test-debug-btn');
|
||
if (testBtn) {
|
||
testBtn.addEventListener('click', () => this.testDebug());
|
||
}
|
||
}
|
||
|
||
// 총 찜하기 간격 업데이트
|
||
updateTotalDelay() {
|
||
const baseDelay = parseInt(document.getElementById('base-delay')?.value || 500);
|
||
const userDelay = parseInt(document.getElementById('user-delay')?.value || 0);
|
||
const totalDelay = baseDelay + userDelay;
|
||
|
||
const totalDelayEl = document.getElementById('total-delay');
|
||
if (totalDelayEl) {
|
||
totalDelayEl.textContent = `${totalDelay}ms`;
|
||
}
|
||
}
|
||
|
||
// 찜하기 설정 가져오기
|
||
getZzimSettings() {
|
||
const baseDelay = parseInt(document.getElementById('base-delay')?.value || 500);
|
||
const userDelay = parseInt(document.getElementById('user-delay')?.value || 0);
|
||
const latestFirst = document.getElementById('latest-first')?.checked || false;
|
||
const backgroundMode = document.getElementById('background-mode')?.checked || false;
|
||
|
||
return {
|
||
totalDelay: baseDelay + userDelay,
|
||
latestFirst: latestFirst,
|
||
backgroundMode: backgroundMode
|
||
};
|
||
}
|
||
|
||
// 마켓 URL 생성 (최신상품 우선 옵션 고려)
|
||
generateMarketUrl(marketUrl, latestFirst = false) {
|
||
try {
|
||
// 기본 마켓 URL에서 마지막 슬래시 제거
|
||
const baseUrl = marketUrl.replace(/\/$/, '');
|
||
|
||
console.log('[ZzimManager] 마켓 URL 생성:', {
|
||
baseUrl: baseUrl,
|
||
latestFirst: latestFirst
|
||
});
|
||
|
||
// 네이버 스마트스토어 URL 구조 분석
|
||
const urlParts = baseUrl.split('/');
|
||
const storeName = urlParts[urlParts.length - 1];
|
||
|
||
let targetUrl;
|
||
|
||
if (latestFirst) {
|
||
// 최신상품 우선: /best?cp=1
|
||
targetUrl = `${baseUrl}/best?cp=1`;
|
||
} else {
|
||
// 전체상품: /category/ALL?cp=1
|
||
targetUrl = `${baseUrl}/category/ALL?cp=1`;
|
||
}
|
||
|
||
console.log('[ZzimManager] 생성된 마켓 URL:', targetUrl);
|
||
return targetUrl;
|
||
|
||
} catch (error) {
|
||
console.error('[ZzimManager] 마켓 URL 생성 오류:', error);
|
||
// 오류 발생 시 기본 URL 반환
|
||
return marketUrl + (latestFirst ? '/best?cp=1' : '/category/ALL?cp=1');
|
||
}
|
||
}
|
||
|
||
async loadZzimStats() {
|
||
try {
|
||
console.log('찜 통계 로드 시작 (Background 요청)...');
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
action: 'getZzimStats',
|
||
userId: this.user_id,
|
||
token: this.access_token
|
||
});
|
||
|
||
if (!response || !response.success) {
|
||
throw new Error(response?.error || '통계 정보를 불러올 수 없습니다.');
|
||
}
|
||
|
||
const { stats: userStats, limits } = response;
|
||
|
||
// 3. 오늘 찜한 개수
|
||
let todayZzimCount = userStats.today_zzim_count || 0;
|
||
|
||
// 4. 사용 가능한 찜 마일리지
|
||
const totalZzimMile = userStats.zzim_mile || 0;
|
||
const availableZzimMile = userStats.available_zzim_mile || totalZzimMile;
|
||
|
||
// 5. UI 업데이트
|
||
const todayCountEl = document.getElementById('today-zzim-count');
|
||
const todayLimitEl = document.getElementById('today-zzim-limit');
|
||
const mileageCountEl = document.getElementById('zzim-mileage');
|
||
const mileageLimitEl = document.getElementById('zzim-mileage-limit');
|
||
const totalZzimEl = document.getElementById('total-zzim-received');
|
||
|
||
if (todayCountEl) todayCountEl.textContent = todayZzimCount;
|
||
if (todayLimitEl) todayLimitEl.textContent = limits.daily_zzim_limit;
|
||
if (mileageCountEl) mileageCountEl.textContent = availableZzimMile;
|
||
if (mileageLimitEl) mileageLimitEl.textContent = limits.max_zzim_mileage;
|
||
if (totalZzimEl) totalZzimEl.textContent = userStats.my_zzim || 0;
|
||
|
||
// 6. 진행률 표시
|
||
const todayProgress = (todayZzimCount / limits.daily_zzim_limit) * 100;
|
||
const mileageProgress = (availableZzimMile / limits.max_zzim_mileage) * 100;
|
||
|
||
const todayProgressBar = document.getElementById('today-progress-bar');
|
||
const mileageProgressBar = document.getElementById('mileage-progress-bar');
|
||
|
||
if (todayProgressBar) {
|
||
todayProgressBar.style.width = `${Math.min(todayProgress, 100)}%`;
|
||
todayProgressBar.className = `progress-bar ${todayProgress >= 100 ? 'full' : todayProgress >= 80 ? 'warning' : 'normal'}`;
|
||
}
|
||
|
||
if (mileageProgressBar) {
|
||
mileageProgressBar.style.width = `${Math.min(mileageProgress, 100)}%`;
|
||
mileageProgressBar.className = `progress-bar ${mileageProgress >= 100 ? 'full' : mileageProgress >= 80 ? 'warning' : 'normal'}`;
|
||
}
|
||
|
||
// 7. 제한 상태 표시
|
||
const todayLimitReached = todayZzimCount >= limits.daily_zzim_limit;
|
||
const mileageLimitReached = availableZzimMile >= limits.max_zzim_mileage;
|
||
|
||
const myMarketBtn = document.getElementById('my-market-zzim-btn');
|
||
const mutualZzimBtn = document.getElementById('mutual-zzim-btn');
|
||
const backgroundModeCheckbox = document.getElementById('background-mode');
|
||
|
||
if (myMarketBtn) {
|
||
myMarketBtn.disabled = todayLimitReached;
|
||
myMarketBtn.title = todayLimitReached ? `일일 찜 제한 도달 (${todayZzimCount}/${limits.daily_zzim_limit})` : '';
|
||
}
|
||
|
||
if (mutualZzimBtn) {
|
||
mutualZzimBtn.disabled = !limits.mutual_zzim_enabled || mileageLimitReached;
|
||
mutualZzimBtn.title = !limits.mutual_zzim_enabled
|
||
? '품앗이 기능이 비활성화되어 있습니다.'
|
||
: mileageLimitReached
|
||
? `마일리지 한도 도달 (${availableZzimMile}/${limits.max_zzim_mileage})`
|
||
: '';
|
||
}
|
||
|
||
if (backgroundModeCheckbox) {
|
||
backgroundModeCheckbox.disabled = !limits.background_zzim_enabled;
|
||
if (!limits.background_zzim_enabled) {
|
||
backgroundModeCheckbox.checked = false;
|
||
backgroundModeCheckbox.title = '백그라운드 모드는 프리미엄/VIP 회원만 사용 가능합니다.';
|
||
}
|
||
}
|
||
|
||
// 8. 상태 메시지 표시
|
||
const statusMessage = document.getElementById('zzim-status-message');
|
||
if (statusMessage) {
|
||
if (todayLimitReached) {
|
||
statusMessage.innerHTML = `⚠️ 오늘 찜 제한에 도달했습니다. (${todayZzimCount}/${limits.daily_zzim_limit})`;
|
||
statusMessage.className = 'status-warning';
|
||
} else if (mileageLimitReached) {
|
||
statusMessage.innerHTML = `⚠️ 찜 마일리지 한도에 도달했습니다. (${availableZzimMile}/${limits.max_zzim_mileage})`;
|
||
statusMessage.className = 'status-warning';
|
||
} else {
|
||
statusMessage.innerHTML = `✅ 찜하기 가능 (오늘: ${todayZzimCount}/${limits.daily_zzim_limit}, 마일리지: ${availableZzimMile}/${limits.max_zzim_mileage})`;
|
||
statusMessage.className = 'status-success';
|
||
}
|
||
}
|
||
|
||
// 현재 제한 정보를 인스턴스 변수에 저장
|
||
this.currentLimits = limits;
|
||
this.currentStats = {
|
||
todayZzimCount,
|
||
availableZzimMile,
|
||
totalReceived: userStats.my_zzim || 0
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('찜 통계 로드 오류:', error);
|
||
this.showError('찜 통계를 불러올 수 없습니다: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async loadMyMarkets() {
|
||
try {
|
||
console.log('마켓 목록 로드 시작 (Background 요청)...');
|
||
|
||
const response = await chrome.runtime.sendMessage({
|
||
action: 'getMyMarkets',
|
||
userId: this.user_id,
|
||
token: this.access_token
|
||
});
|
||
|
||
if (!response || !response.success) {
|
||
throw new Error(response?.error || '마켓 목록을 불러올 수 없습니다.');
|
||
}
|
||
|
||
const markets = response.markets || [];
|
||
|
||
// 생성일 기준으로 최신순 정렬
|
||
const sortedMarkets = markets.sort((a, b) => {
|
||
const dateA = new Date(a.created_at || 0);
|
||
const dateB = new Date(b.created_at || 0);
|
||
return dateB - dateA;
|
||
});
|
||
|
||
this.renderMarketsList(sortedMarkets);
|
||
|
||
} catch (error) {
|
||
console.error('마켓 목록 로드 오류:', error);
|
||
this.showError('마켓 목록을 불러올 수 없습니다: ' + error.message);
|
||
}
|
||
}
|
||
|
||
renderMarketsList(markets) {
|
||
console.log('마켓 목록 렌더링 시작:', markets);
|
||
|
||
const marketsList = document.getElementById('my-markets-list');
|
||
if (!marketsList) {
|
||
console.error('my-markets-list 엘리먼트를 찾을 수 없습니다.');
|
||
return;
|
||
}
|
||
|
||
if (markets.length === 0) {
|
||
console.log('등록된 마켓이 없음');
|
||
marketsList.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">등록된 마켓이 없습니다.</p>';
|
||
return;
|
||
}
|
||
|
||
console.log('마켓 목록 HTML 생성 중...');
|
||
|
||
const marketHTML = markets.map((market, index) => {
|
||
console.log(`마켓 ${index} 렌더링:`, market);
|
||
|
||
// 고유 식별자 생성 (마켓 URL + 생성일시)
|
||
const marketId = `${market.market_url}_${market.created_at}`;
|
||
|
||
return `
|
||
<div class="market-item" data-index="${index}" data-market-id="${marketId}">
|
||
<div class="market-header">
|
||
<div class="market-checkboxes">
|
||
<div class="market-checkbox">
|
||
<input type="checkbox" id="market-check-${index}" class="market-checkbox-input"
|
||
${market.for_zzim !== false ? 'checked' : ''}
|
||
data-market-id="${marketId}" data-checkbox-type="for_zzim">
|
||
<label for="market-check-${index}" class="market-checkbox-label">내 찜하기</label>
|
||
</div>
|
||
<div class="market-checkbox">
|
||
<input type="checkbox" id="market-mutual-${index}" class="market-checkbox-input"
|
||
${market.for_mutual_zzim !== false ? 'checked' : ''}
|
||
data-market-id="${marketId}" data-checkbox-type="for_mutual_zzim">
|
||
<label for="market-mutual-${index}" class="market-checkbox-label">품앗이 대상</label>
|
||
</div>
|
||
</div>
|
||
<div class="market-info">
|
||
<div class="market-name">${market.market_name || '마켓명 없음'}</div>
|
||
<div class="market-nickname">${market.market_nickname || '별명 없음'}</div>
|
||
<div class="market-url" title="${market.market_url || ''}">${this.truncateUrl(market.market_url || '')}</div>
|
||
<div class="market-stats">
|
||
<span class="zzim-count">찜받은수: ${market.zzim_received_count || 0}개</span>
|
||
<span class="created-date">등록일: ${this.formatDate(market.created_at)}</span>
|
||
</div>
|
||
</div>
|
||
<div class="market-actions">
|
||
<button class="btn btn-warning market-edit-btn" data-market-id="${marketId}">수정</button>
|
||
<button class="btn btn-danger market-delete-btn" data-market-id="${marketId}">삭제</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
marketsList.innerHTML = marketHTML;
|
||
|
||
// 이벤트 리스너 바인딩
|
||
this.bindMarketEvents();
|
||
|
||
console.log('마켓 목록 렌더링 완료');
|
||
}
|
||
|
||
// 마켓 관련 이벤트 바인딩
|
||
bindMarketEvents() {
|
||
// 체크박스 이벤트
|
||
const checkboxes = document.querySelectorAll('.market-checkbox-input');
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.addEventListener('change', (e) => {
|
||
const marketId = e.target.dataset.marketId;
|
||
const checkboxType = e.target.dataset.checkboxType;
|
||
this.toggleMarketCheckbox(marketId, checkboxType, e.target.checked);
|
||
});
|
||
});
|
||
|
||
// 수정 버튼 이벤트
|
||
const editBtns = document.querySelectorAll('.market-edit-btn');
|
||
editBtns.forEach(btn => {
|
||
btn.addEventListener('click', (e) => {
|
||
const marketId = e.target.dataset.marketId;
|
||
this.editMarket(marketId);
|
||
});
|
||
});
|
||
|
||
// 노출/숨김 버튼 이벤트 (삭제됨)
|
||
/*
|
||
const visibilityBtns = document.querySelectorAll('.market-visibility-btn');
|
||
visibilityBtns.forEach(btn => {
|
||
btn.addEventListener('click', (e) => {
|
||
const marketId = e.target.dataset.marketId;
|
||
this.toggleMarketVisibility(marketId);
|
||
});
|
||
});
|
||
*/
|
||
|
||
// 삭제 버튼 이벤트
|
||
const deleteBtns = document.querySelectorAll('.market-delete-btn');
|
||
deleteBtns.forEach(btn => {
|
||
btn.addEventListener('click', (e) => {
|
||
const marketId = e.target.dataset.marketId;
|
||
this.deleteMarket(marketId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// URL을 적절한 길이로 자르기
|
||
truncateUrl(url) {
|
||
if (!url) return '';
|
||
if (url.length <= 50) return url;
|
||
return url.substring(0, 47) + '...';
|
||
}
|
||
|
||
// 날짜 포맷팅
|
||
formatDate(dateString) {
|
||
if (!dateString) return '알 수 없음';
|
||
try {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('ko-KR', {
|
||
year: 'numeric',
|
||
month: 'short',
|
||
day: 'numeric'
|
||
});
|
||
} catch (error) {
|
||
return '알 수 없음';
|
||
}
|
||
}
|
||
|
||
// 마켓 체크박스 토글 (통합)
|
||
async toggleMarketCheckbox(marketId, checkboxType, isChecked) {
|
||
try {
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
// 마켓 ID로 해당 마켓 찾기
|
||
const market = markets.find(market => `${market.market_url}_${market.created_at}` === marketId);
|
||
|
||
if (!market) {
|
||
this.showError('잘못된 마켓 ID입니다.');
|
||
return;
|
||
}
|
||
|
||
// 체크박스 타입에 따라 해당 필드 업데이트
|
||
if (checkboxType === 'for_zzim') {
|
||
market.for_zzim = isChecked;
|
||
} else if (checkboxType === 'for_mutual_zzim') {
|
||
market.for_mutual_zzim = isChecked;
|
||
// 품앗이 대상 체크 시 '노출' 상태도 자동으로 동기화 (사용자 편의성)
|
||
market.is_visible = isChecked;
|
||
}
|
||
|
||
market.updated_at = new Date().toISOString();
|
||
|
||
// user_markets 테이블 업데이트
|
||
const updateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: markets
|
||
})
|
||
});
|
||
|
||
if (!updateResponse.ok) {
|
||
throw new Error('마켓 설정 변경 실패');
|
||
}
|
||
|
||
const statusMessage = checkboxType === 'for_zzim'
|
||
? (isChecked ? '내 찜하기 대상에 포함' : '내 찜하기 대상에서 제외')
|
||
: (isChecked ? '품앗이 대상에 포함' : '품앗이 대상에서 제외');
|
||
|
||
this.showSuccess(`마켓이 ${statusMessage}되었습니다.`);
|
||
|
||
} catch (error) {
|
||
console.error('마켓 설정 변경 오류:', error);
|
||
this.showError('마켓 설정 변경 중 오류가 발생했습니다.');
|
||
|
||
// 오류 발생 시 체크박스 상태 원복
|
||
const checkbox = document.querySelector(`[data-market-id="${marketId}"][data-checkbox-type="${checkboxType}"]`);
|
||
if (checkbox) {
|
||
checkbox.checked = !isChecked;
|
||
}
|
||
}
|
||
}
|
||
|
||
async addMarket() {
|
||
const marketUrl = document.getElementById('market-url').value.trim();
|
||
const marketName = document.getElementById('market-name').value.trim();
|
||
const marketNickname = document.getElementById('market-nickname').value.trim();
|
||
|
||
console.log('마켓 추가 시작:', {
|
||
marketUrl,
|
||
marketName,
|
||
marketNickname
|
||
});
|
||
|
||
if (!marketUrl || !marketName || !marketNickname) {
|
||
this.showError('모든 필드를 입력해주세요.');
|
||
return;
|
||
}
|
||
|
||
if (!marketUrl.startsWith('https://smartstore.naver.com/')) {
|
||
this.showError('올바른 네이버 스마트스토어 URL을 입력해주세요.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
console.log('기존 마켓 목록 조회 중...');
|
||
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('기존 마켓 목록 조회 응답:', {
|
||
status: response.status,
|
||
ok: response.ok
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
console.error('기존 마켓 목록 조회 실패:', errorText);
|
||
throw new Error('기존 마켓 목록을 가져올 수 없습니다: ' + errorText);
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('기존 마켓 데이터:', data);
|
||
|
||
let existingMarkets = [];
|
||
|
||
// 데이터가 없는 경우 처리
|
||
if (!data || data.length === 0) {
|
||
console.log('기존 user_markets 레코드가 없음, 새로 생성 필요');
|
||
|
||
// user_markets 레코드가 없으면 먼저 생성
|
||
const createResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
user_id: this.user_id,
|
||
my_markets: []
|
||
})
|
||
});
|
||
|
||
console.log('user_markets 레코드 생성 응답:', {
|
||
status: createResponse.status,
|
||
ok: createResponse.ok
|
||
});
|
||
|
||
if (!createResponse.ok) {
|
||
const createErrorText = await createResponse.text();
|
||
console.error('user_markets 레코드 생성 실패:', createErrorText);
|
||
throw new Error('사용자 마켓 레코드 생성 실패: ' + createErrorText);
|
||
}
|
||
|
||
existingMarkets = [];
|
||
} else {
|
||
existingMarkets = data[0]?.my_markets || [];
|
||
}
|
||
|
||
console.log('기존 마켓 목록:', existingMarkets);
|
||
|
||
// 새 마켓 객체 생성
|
||
const newMarket = {
|
||
market_url: marketUrl,
|
||
market_name: marketName,
|
||
market_nickname: marketNickname,
|
||
is_visible: true, // 다른 사람에게 노출 (기본값: 노출)
|
||
for_zzim: true, // 내가 찜하기 할 때 포함 (기본값: 포함)
|
||
for_mutual_zzim: true, // 품앗이 대상 여부 (기본값: 포함)
|
||
zzim_received_count: 0,
|
||
created_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString()
|
||
};
|
||
|
||
console.log('새 마켓 객체:', newMarket);
|
||
|
||
// 중복 URL 확인
|
||
const isDuplicate = existingMarkets.some(market => market.market_url === marketUrl);
|
||
if (isDuplicate) {
|
||
console.log('중복 URL 발견');
|
||
this.showError('이미 등록된 마켓 URL입니다.');
|
||
return;
|
||
}
|
||
|
||
// 새 마켓 추가
|
||
const updatedMarkets = [...existingMarkets, newMarket];
|
||
console.log('업데이트된 마켓 목록:', updatedMarkets);
|
||
|
||
// user_markets 테이블 업데이트
|
||
console.log('마켓 목록 업데이트 요청 중...');
|
||
|
||
const updateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json',
|
||
'Prefer': 'return=representation'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: updatedMarkets
|
||
})
|
||
});
|
||
|
||
console.log('마켓 목록 업데이트 응답:', {
|
||
status: updateResponse.status,
|
||
ok: updateResponse.ok,
|
||
statusText: updateResponse.statusText
|
||
});
|
||
|
||
if (!updateResponse.ok) {
|
||
const errorData = await updateResponse.text();
|
||
console.error('마켓 추가 실패 상세:', errorData);
|
||
throw new Error('마켓 추가 실패: ' + errorData);
|
||
}
|
||
|
||
// 응답 데이터 확인
|
||
const updateResult = await updateResponse.json();
|
||
console.log('업데이트 결과:', updateResult);
|
||
|
||
console.log('마켓 추가 성공, 입력 필드 초기화 중...');
|
||
|
||
// 입력 필드 초기화
|
||
document.getElementById('market-url').value = '';
|
||
document.getElementById('market-name').value = '';
|
||
document.getElementById('market-nickname').value = '';
|
||
|
||
console.log('마켓 목록 다시 로드 중...');
|
||
|
||
// 약간의 지연 후 마켓 목록 다시 로드 (DB 반영 시간 고려)
|
||
setTimeout(async () => {
|
||
await this.loadMyMarkets();
|
||
this.showSuccess('마켓이 추가되었습니다.');
|
||
|
||
// 새로 추가된 마켓을 위로 스크롤
|
||
setTimeout(() => {
|
||
const marketsList = document.getElementById('my-markets-list');
|
||
if (marketsList) {
|
||
marketsList.scrollTop = 0;
|
||
}
|
||
}, 100);
|
||
}, 500);
|
||
|
||
} catch (error) {
|
||
console.error('마켓 추가 오류:', error);
|
||
this.showError('마켓 추가 중 오류가 발생했습니다: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async editMarket(marketId) {
|
||
try {
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
// 마켓 ID로 해당 마켓 찾기
|
||
const market = markets.find(market => `${market.market_url}_${market.created_at}` === marketId);
|
||
|
||
if (!market) {
|
||
this.showError('잘못된 마켓 ID입니다.');
|
||
return;
|
||
}
|
||
|
||
// 모달에 기존 데이터 채우기
|
||
this.currentEditIndex = markets.indexOf(market);
|
||
this.openEditModal(market);
|
||
|
||
} catch (error) {
|
||
console.error('마켓 수정 오류:', error);
|
||
this.showError('마켓 정보를 불러올 수 없습니다.');
|
||
}
|
||
}
|
||
|
||
// 모달 열기
|
||
openEditModal(market) {
|
||
const modal = document.getElementById('edit-market-modal');
|
||
const urlInput = document.getElementById('edit-market-url');
|
||
const nameInput = document.getElementById('edit-market-name');
|
||
const nicknameInput = document.getElementById('edit-market-nickname');
|
||
const forZzimCheckbox = document.getElementById('edit-market-for-zzim');
|
||
const forMutualZzimCheckbox = document.getElementById('edit-market-for-mutual-zzim');
|
||
|
||
// 기존 데이터로 폼 채우기
|
||
if (urlInput) urlInput.value = market.market_url || '';
|
||
if (nameInput) nameInput.value = market.market_name || '';
|
||
if (nicknameInput) nicknameInput.value = market.market_nickname || '';
|
||
if (forZzimCheckbox) forZzimCheckbox.checked = market.for_zzim !== false;
|
||
if (forMutualZzimCheckbox) forMutualZzimCheckbox.checked = market.for_mutual_zzim !== false;
|
||
|
||
// 모달 표시
|
||
if (modal) {
|
||
modal.style.display = 'block';
|
||
|
||
// 첫 번째 입력 필드에 포커스
|
||
setTimeout(() => {
|
||
if (nameInput) nameInput.focus();
|
||
}, 100);
|
||
}
|
||
|
||
// ESC 키로 모달 닫기
|
||
this.modalEscapeHandler = (e) => {
|
||
if (e.key === 'Escape') {
|
||
this.closeEditModal();
|
||
}
|
||
};
|
||
document.addEventListener('keydown', this.modalEscapeHandler);
|
||
|
||
// 모달 배경 클릭으로 닫기
|
||
this.modalClickHandler = (e) => {
|
||
if (e.target === modal) {
|
||
this.closeEditModal();
|
||
}
|
||
};
|
||
modal.addEventListener('click', this.modalClickHandler);
|
||
}
|
||
|
||
// 모달 닫기
|
||
closeEditModal() {
|
||
const modal = document.getElementById('edit-market-modal');
|
||
|
||
if (modal) {
|
||
modal.style.display = 'none';
|
||
}
|
||
|
||
// 이벤트 리스너 제거
|
||
if (this.modalEscapeHandler) {
|
||
document.removeEventListener('keydown', this.modalEscapeHandler);
|
||
this.modalEscapeHandler = null;
|
||
}
|
||
|
||
if (this.modalClickHandler) {
|
||
modal.removeEventListener('click', this.modalClickHandler);
|
||
this.modalClickHandler = null;
|
||
}
|
||
|
||
// 편집 인덱스 초기화
|
||
this.currentEditIndex = null;
|
||
}
|
||
|
||
// 마켓 수정 저장
|
||
async saveMarketEdit() {
|
||
const urlInput = document.getElementById('edit-market-url');
|
||
const nameInput = document.getElementById('edit-market-name');
|
||
const nicknameInput = document.getElementById('edit-market-nickname');
|
||
const forZzimCheckbox = document.getElementById('edit-market-for-zzim');
|
||
const forMutualZzimCheckbox = document.getElementById('edit-market-for-mutual-zzim');
|
||
const saveBtn = document.querySelector('.btn-modal-save');
|
||
|
||
const marketUrl = urlInput?.value.trim() || '';
|
||
const marketName = nameInput?.value.trim() || '';
|
||
const marketNickname = nicknameInput?.value.trim() || '';
|
||
const forZzim = forZzimCheckbox?.checked || false;
|
||
const forMutualZzim = forMutualZzimCheckbox?.checked || false;
|
||
// 품앗이 대상이 체크되면 자동으로 노출도 true로 설정
|
||
const isVisible = forMutualZzim;
|
||
|
||
// 유효성 검사
|
||
if (!marketUrl || !marketName || !marketNickname) {
|
||
this.showError('모든 필드를 입력해주세요.');
|
||
return;
|
||
}
|
||
|
||
if (!marketUrl.startsWith('https://smartstore.naver.com/')) {
|
||
this.showError('올바른 네이버 스마트스토어 URL을 입력해주세요.');
|
||
return;
|
||
}
|
||
|
||
if (this.currentEditIndex === null) {
|
||
this.showError('편집할 마켓 정보를 찾을 수 없습니다.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 저장 버튼 비활성화
|
||
if (saveBtn) {
|
||
saveBtn.disabled = true;
|
||
saveBtn.textContent = '저장 중...';
|
||
}
|
||
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
if (this.currentEditIndex < 0 || this.currentEditIndex >= markets.length) {
|
||
throw new Error('잘못된 마켓 인덱스입니다.');
|
||
}
|
||
|
||
// URL 중복 검사 (자기 자신 제외)
|
||
const isDuplicate = markets.some((market, index) =>
|
||
index !== this.currentEditIndex && market.market_url === marketUrl
|
||
);
|
||
|
||
if (isDuplicate) {
|
||
throw new Error('이미 등록된 마켓 URL입니다.');
|
||
}
|
||
|
||
// 마켓 정보 업데이트
|
||
const existingMarket = markets[this.currentEditIndex];
|
||
markets[this.currentEditIndex] = {
|
||
...existingMarket,
|
||
market_url: marketUrl,
|
||
market_name: marketName,
|
||
market_nickname: marketNickname,
|
||
for_zzim: forZzim,
|
||
for_mutual_zzim: forMutualZzim,
|
||
is_visible: isVisible,
|
||
updated_at: new Date().toISOString()
|
||
};
|
||
|
||
// user_markets 테이블 업데이트
|
||
const updateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: markets
|
||
})
|
||
});
|
||
|
||
if (!updateResponse.ok) {
|
||
const errorText = await updateResponse.text();
|
||
throw new Error('마켓 수정 실패: ' + errorText);
|
||
}
|
||
|
||
// 성공 처리
|
||
this.closeEditModal();
|
||
await this.loadMyMarkets();
|
||
this.showSuccess('마켓 정보가 성공적으로 수정되었습니다.');
|
||
|
||
} catch (error) {
|
||
console.error('마켓 수정 저장 오류:', error);
|
||
this.showError('마켓 수정 중 오류가 발생했습니다: ' + error.message);
|
||
} finally {
|
||
// 저장 버튼 복원
|
||
if (saveBtn) {
|
||
saveBtn.disabled = false;
|
||
saveBtn.textContent = '저장';
|
||
}
|
||
}
|
||
}
|
||
|
||
async toggleMarketVisibility(marketId) {
|
||
try {
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
// 마켓 ID로 해당 마켓 찾기
|
||
const market = markets.find(market => `${market.market_url}_${market.created_at}` === marketId);
|
||
|
||
if (!market) {
|
||
this.showError('잘못된 마켓 ID입니다.');
|
||
return;
|
||
}
|
||
|
||
// 노출 상태 토글
|
||
market.is_visible = !market.is_visible;
|
||
market.updated_at = new Date().toISOString();
|
||
|
||
// user_markets 테이블 업데이트
|
||
const updateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: markets
|
||
})
|
||
});
|
||
|
||
if (!updateResponse.ok) {
|
||
throw new Error('마켓 노출 설정 변경 실패');
|
||
}
|
||
|
||
await this.loadMyMarkets();
|
||
this.showSuccess(`마켓이 ${market.is_visible ? '노출' : '숨김'} 상태로 변경되었습니다.`);
|
||
|
||
} catch (error) {
|
||
console.error('마켓 노출 설정 변경 오류:', error);
|
||
this.showError('마켓 노출 설정 변경 중 오류가 발생했습니다.');
|
||
}
|
||
}
|
||
|
||
async deleteMarket(marketId) {
|
||
try {
|
||
// 기존 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
// 마켓 ID로 해당 마켓 찾기
|
||
const market = markets.find(market => `${market.market_url}_${market.created_at}` === marketId);
|
||
|
||
if (!market) {
|
||
this.showError('잘못된 마켓 ID입니다.');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`정말로 "${market.market_nickname}" 마켓을 삭제하시겠습니까?`)) {
|
||
return;
|
||
}
|
||
|
||
// 마켓 삭제 (배열에서 제거)
|
||
markets.splice(markets.indexOf(market), 1);
|
||
|
||
// user_markets 테이블 업데이트
|
||
const updateResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: markets
|
||
})
|
||
});
|
||
|
||
if (!updateResponse.ok) {
|
||
throw new Error('마켓 삭제 실패');
|
||
}
|
||
|
||
await this.loadMyMarkets();
|
||
this.showSuccess('마켓이 삭제되었습니다.');
|
||
|
||
} catch (error) {
|
||
console.error('마켓 삭제 오류:', error);
|
||
this.showError('마켓 삭제 중 오류가 발생했습니다.');
|
||
}
|
||
}
|
||
|
||
async startMyMarketZzim() {
|
||
if (this.zzimInProgress) {
|
||
this.showError('이미 찜하기가 진행 중입니다.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
this.zzimInProgress = true;
|
||
this.updateButtonStates();
|
||
|
||
const markets = await this.getActiveMarkets();
|
||
|
||
if (markets.length === 0) {
|
||
this.showError('등록된 마켓이 없습니다. 먼저 마켓을 등록해주세요.');
|
||
return;
|
||
}
|
||
|
||
const settings = this.getZzimSettings();
|
||
this.showProgress(0, markets.length, '내 마켓 찜하기 준비 중...');
|
||
|
||
for (let i = 0; i < markets.length; i++) {
|
||
if (!this.zzimInProgress) {
|
||
this.showError('찜하기가 중단되었습니다.');
|
||
break;
|
||
}
|
||
|
||
const market = markets[i];
|
||
this.showProgress(i, markets.length, `${market.market_nickname} 찜하기 중...`);
|
||
|
||
await this.executeZzim(market, 'my_market', settings);
|
||
|
||
this.showProgress(i + 1, markets.length, `${market.market_nickname} 완료`);
|
||
|
||
// 마켓 간 대기 시간 (설정된 속도 적용)
|
||
if (i < markets.length - 1 && this.zzimInProgress) {
|
||
this.showProgress(i + 1, markets.length, `다음 마켓 대기 중... (${settings.totalDelay}ms)`);
|
||
await new Promise(resolve => setTimeout(resolve, settings.totalDelay));
|
||
}
|
||
}
|
||
|
||
if (this.zzimInProgress) {
|
||
this.hideProgress();
|
||
this.showSuccess(`내 마켓 찜하기 완료! ${markets.length}개 마켓 처리됨`);
|
||
setTimeout(() => {
|
||
window.close();
|
||
}, 2000); // 2초 후 창 닫기
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('내 마켓 찜하기 오류:', error);
|
||
this.hideProgress();
|
||
this.showError('내 마켓 찜하기 중 오류가 발생했습니다.');
|
||
} finally {
|
||
this.zzimInProgress = false;
|
||
this.updateButtonStates();
|
||
}
|
||
}
|
||
|
||
async startMutualZzim() {
|
||
if (this.zzimInProgress) {
|
||
this.showError('이미 찜하기가 진행 중입니다.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
this.zzimInProgress = true;
|
||
this.updateButtonStates();
|
||
|
||
// 품앗이 마켓 목록 가져오기
|
||
const mutualMarkets = await this.getMutualMarkets();
|
||
|
||
if (mutualMarkets.length === 0) {
|
||
this.showError('품앗이 마켓이 없습니다.');
|
||
return;
|
||
}
|
||
|
||
const settings = this.getZzimSettings();
|
||
this.showProgress(0, mutualMarkets.length, '품앗이 찜하기 준비 중...');
|
||
|
||
for (let i = 0; i < mutualMarkets.length; i++) {
|
||
if (!this.zzimInProgress) {
|
||
this.showError('찜하기가 중단되었습니다.');
|
||
break;
|
||
}
|
||
|
||
const market = mutualMarkets[i];
|
||
this.showProgress(i, mutualMarkets.length, `${market.market_nickname} 품앗이 중...`);
|
||
|
||
// 백그라운드 모드를 강제로 활성화 (품앗이는 항상 백그라운드로)
|
||
const mutualSettings = { ...settings, backgroundMode: true };
|
||
|
||
await this.executeZzim(market, 'mutual', mutualSettings);
|
||
|
||
this.showProgress(i + 1, mutualMarkets.length, `${market.market_nickname} 완료`);
|
||
|
||
// 마켓 간 대기 시간 (설정된 속도 적용)
|
||
if (i < mutualMarkets.length - 1 && this.zzimInProgress) {
|
||
this.showProgress(i + 1, mutualMarkets.length, `다음 마켓 대기 중... (${settings.totalDelay}ms)`);
|
||
await new Promise(resolve => setTimeout(resolve, settings.totalDelay));
|
||
}
|
||
}
|
||
|
||
if (this.zzimInProgress) {
|
||
this.hideProgress();
|
||
this.showSuccess(`품앗이 찜하기 완료! ${mutualMarkets.length}개 마켓 처리됨`);
|
||
setTimeout(() => {
|
||
window.close();
|
||
}, 2000); // 2초 후 창 닫기
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('품앗이 찜하기 오류:', error);
|
||
this.hideProgress();
|
||
this.showError('품앗이 찜하기 중 오류가 발생했습니다.');
|
||
} finally {
|
||
this.zzimInProgress = false;
|
||
this.updateButtonStates();
|
||
}
|
||
}
|
||
|
||
async executeZzim(market, zzimType, settings) {
|
||
this.zzimInProgress = true;
|
||
|
||
try {
|
||
// 마켓 URL 생성 (최신상품 우선 옵션 적용)
|
||
const targetUrl = this.generateMarketUrl(market.market_url, settings.latestFirst);
|
||
|
||
if (settings.backgroundMode) {
|
||
// 백그라운드 실행을 위해 background.js로 메시지 전송
|
||
const response = await chrome.runtime.sendMessage({
|
||
action: 'executeBackgroundZzim',
|
||
market: {
|
||
...market,
|
||
target_url: targetUrl
|
||
},
|
||
zzimType: zzimType,
|
||
userId: this.user_id,
|
||
accessToken: this.access_token,
|
||
settings: settings
|
||
});
|
||
|
||
if (response.success) {
|
||
this.showSuccess(`백그라운드 찜하기가 시작되었습니다. (${market.market_nickname})`);
|
||
await this.recordZzim(market, zzimType);
|
||
} else {
|
||
throw new Error(response.error || '백그라운드 찜하기 실행 실패');
|
||
}
|
||
} else {
|
||
// 포그라운드에서 실행 - 로그인 상태 확인 후 처리
|
||
await this.checkLoginAndExecute(market, targetUrl, settings, zzimType);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('찜하기 실행 오류:', error);
|
||
this.showError('찜하기 실행 중 오류가 발생했습니다: ' + error.message);
|
||
} finally {
|
||
this.zzimInProgress = false;
|
||
}
|
||
}
|
||
|
||
// 로그인 상태 확인 및 찜하기 실행
|
||
async checkLoginAndExecute(market, targetUrl, settings, zzimType) {
|
||
try {
|
||
// 먼저 마켓 페이지에 접근해서 로그인 상태 확인
|
||
const testUrl = market.market_url;
|
||
|
||
// 현재 탭에서 마켓으로 이동하여 로그인 상태 확인
|
||
const urlType = settings.latestFirst ? '최신상품' : '전체상품';
|
||
|
||
// 로그인 확인을 위한 모달 표시
|
||
this.showLoginCheckModal(market, targetUrl, settings, zzimType, urlType);
|
||
|
||
} catch (error) {
|
||
console.error('로그인 확인 오류:', error);
|
||
this.showError('로그인 상태 확인 중 오류가 발생했습니다.');
|
||
}
|
||
}
|
||
|
||
// 로그인 확인 모달 표시
|
||
showLoginCheckModal(market, targetUrl, settings, zzimType, urlType) {
|
||
// 기존 모달이 있으면 제거
|
||
this.removeLoginModal();
|
||
|
||
// 모달 HTML 생성
|
||
const modalHTML = `
|
||
<div id="login-check-modal" style="
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
z-index: 999999;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-family: 'Segoe UI', sans-serif;
|
||
">
|
||
<div style="
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 15px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||
max-width: 500px;
|
||
width: 90%;
|
||
text-align: center;
|
||
">
|
||
<h3 style="
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
">🔐 로그인 확인</h3>
|
||
|
||
<p style="
|
||
color: #666;
|
||
margin-bottom: 25px;
|
||
line-height: 1.5;
|
||
font-size: 14px;
|
||
">
|
||
<strong>${market.market_nickname}</strong> 마켓에서 찜하기를 실행합니다.<br>
|
||
(${urlType} 모드)
|
||
</p>
|
||
|
||
<div style="
|
||
background: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-bottom: 25px;
|
||
border-left: 4px solid #007bff;
|
||
">
|
||
<p style="
|
||
margin: 0;
|
||
color: #495057;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
">
|
||
네이버에 로그인되어 있지 않으면 자동으로 로그인 페이지로 이동됩니다.<br>
|
||
로그인 후 다시 찜하기를 실행해 주세요.
|
||
</p>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||
<button id="login-proceed-btn" style="
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
|
||
🚀 찜하기 시작
|
||
</button>
|
||
|
||
<button id="login-cancel-btn" style="
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
" onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'">
|
||
❌ 취소
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 모달을 body에 추가
|
||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||
|
||
// 이벤트 리스너 추가
|
||
const proceedBtn = document.getElementById('login-proceed-btn');
|
||
const cancelBtn = document.getElementById('login-cancel-btn');
|
||
const modal = document.getElementById('login-check-modal');
|
||
|
||
if (proceedBtn) {
|
||
proceedBtn.addEventListener('click', () => {
|
||
this.removeLoginModal();
|
||
this.proceedWithZzim(market, targetUrl, settings, zzimType);
|
||
});
|
||
}
|
||
|
||
if (cancelBtn) {
|
||
cancelBtn.addEventListener('click', () => {
|
||
this.removeLoginModal();
|
||
this.showStatus('찜하기가 취소되었습니다.');
|
||
});
|
||
}
|
||
|
||
// ESC 키로 모달 닫기
|
||
const escapeHandler = (e) => {
|
||
if (e.key === 'Escape') {
|
||
this.removeLoginModal();
|
||
this.showStatus('찜하기가 취소되었습니다.');
|
||
document.removeEventListener('keydown', escapeHandler);
|
||
}
|
||
};
|
||
document.addEventListener('keydown', escapeHandler);
|
||
|
||
// 모달 배경 클릭으로 닫기
|
||
if (modal) {
|
||
modal.addEventListener('click', (e) => {
|
||
if (e.target === modal) {
|
||
this.removeLoginModal();
|
||
this.showStatus('찜하기가 취소되었습니다.');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// 로그인 모달 제거
|
||
removeLoginModal() {
|
||
const modal = document.getElementById('login-check-modal');
|
||
if (modal) {
|
||
modal.remove();
|
||
}
|
||
}
|
||
|
||
// 찜하기 진행 (로그인 리다이렉트 포함)
|
||
async proceedWithZzim(market, targetUrl, settings, zzimType) {
|
||
try {
|
||
// 마켓 URL에서 스토어 ID 추출
|
||
const storeId = market.market_url.split('/').pop();
|
||
|
||
// 올바른 네이버 스마트스토어 URL 생성
|
||
let finalTargetUrl = targetUrl;
|
||
|
||
// URL에 찜하기 파라미터 추가
|
||
const urlObj = new URL(finalTargetUrl);
|
||
urlObj.searchParams.set('auto_zzim', 'true');
|
||
|
||
// 일일 제한 확인 (settings.maxZzim이 있으면 사용, 없으면 기본값)
|
||
const maxZzim = settings.maxZzim || this.currentLimits?.daily_zzim_limit || 100;
|
||
urlObj.searchParams.set('max_zzim', maxZzim.toString());
|
||
|
||
// 네이버 스마트스토어는 cp 파라미터를 사용 (page가 아님)
|
||
if (!urlObj.searchParams.has('cp')) {
|
||
urlObj.searchParams.set('cp', '1'); // 첫 번째 페이지부터 시작
|
||
}
|
||
|
||
finalTargetUrl = urlObj.toString();
|
||
|
||
console.log('[ZzimManager] 최종 찜하기 URL:', finalTargetUrl);
|
||
|
||
// 로그인 리다이렉트 URL 생성
|
||
const loginRedirectUrl = `https://nid.naver.com/nidlogin.login?url=${encodeURIComponent(finalTargetUrl)}`;
|
||
|
||
this.showSuccess(`찜하기 페이지로 이동합니다. (${market.market_nickname})`);
|
||
|
||
// 찜 기록 (실제 찜하기는 새 페이지에서 실행됨)
|
||
await this.recordZzim(market, zzimType);
|
||
|
||
// 현재 탭에서 로그인 리다이렉트 URL로 이동
|
||
window.location.href = loginRedirectUrl;
|
||
|
||
} catch (error) {
|
||
console.error('찜하기 진행 오류:', error);
|
||
this.showError('찜하기 진행 중 오류가 발생했습니다: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async recordZzim(market, zzimType) {
|
||
try {
|
||
// 찜 기록을 별도 테이블에 저장하지 않고 통계만 업데이트
|
||
// (필요시 나중에 zzim_history 테이블 추가 가능)
|
||
|
||
// 통계 업데이트 (실제 찜한 개수는 스크립트 결과를 받아야 하지만 임시로 50개로 설정)
|
||
const estimatedCount = 50;
|
||
await this.updateZzimStats(estimatedCount, zzimType, market);
|
||
|
||
// UI 새로고침
|
||
await Promise.all([
|
||
this.loadZzimStats(),
|
||
this.loadMyMarkets()
|
||
]);
|
||
|
||
} catch (error) {
|
||
console.error('찜 기록 저장 오류:', error);
|
||
}
|
||
}
|
||
|
||
async updateZzimStats(count, zzimType, targetMarket = null) {
|
||
// 이 함수는 더 이상 직접 호출되지 않습니다.
|
||
// Background 스크립트가 처리합니다.
|
||
console.log('updateZzimStats는 이제 Background Script에서 처리됩니다.');
|
||
}
|
||
|
||
// 마켓별 찜받은 개수 업데이트
|
||
async updateMarketZzimCount(marketUrl, count, userId = null) {
|
||
try {
|
||
const targetUserId = userId || this.user_id;
|
||
|
||
const marketsResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${targetUserId}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (marketsResponse.ok) {
|
||
const marketsData = await marketsResponse.json();
|
||
const markets = marketsData[0]?.my_markets || [];
|
||
|
||
// 해당 마켓 찾아서 찜받은 개수 증가
|
||
const updatedMarkets = markets.map(market => {
|
||
if (market.market_url === marketUrl) {
|
||
return {
|
||
...market,
|
||
zzim_received_count: (market.zzim_received_count || 0) + count,
|
||
updated_at: new Date().toISOString()
|
||
};
|
||
}
|
||
return market;
|
||
});
|
||
|
||
// 업데이트된 마켓 목록 저장
|
||
await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${targetUserId}`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
my_markets: updatedMarkets
|
||
})
|
||
});
|
||
|
||
console.log(`마켓 ${marketUrl} 찜받은 개수 ${count} 증가`);
|
||
}
|
||
} catch (error) {
|
||
console.error('마켓 찜받은 개수 업데이트 오류:', error);
|
||
}
|
||
}
|
||
|
||
// 찜 기록 저장
|
||
async saveZzimRecord(zzimType, targetMarket, zzimCount, mileageEarned) {
|
||
try {
|
||
const record = {
|
||
user_id: this.user_id,
|
||
market_url: targetMarket?.market_url || '',
|
||
market_name: targetMarket?.market_name || '',
|
||
market_nickname: targetMarket?.market_nickname || '',
|
||
zzim_type: zzimType,
|
||
zzim_count: zzimCount,
|
||
mileage_earned: mileageEarned,
|
||
target_user_id: targetMarket?.owner_user_id || null,
|
||
created_at: new Date().toISOString()
|
||
};
|
||
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/jjim`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(record)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
console.warn('찜 기록 저장 실패:', response.status);
|
||
} else {
|
||
console.log('찜 기록 저장 완료');
|
||
}
|
||
} catch (error) {
|
||
console.error('찜 기록 저장 오류:', error);
|
||
}
|
||
}
|
||
|
||
// 찜하기 가능 여부 확인
|
||
canZzim(zzimType) {
|
||
if (!this.currentStats || !this.currentLimits) {
|
||
return { canZzim: false, reason: '통계 정보를 불러오는 중입니다.' };
|
||
}
|
||
|
||
if (zzimType === 'my_market') {
|
||
// 내 마켓 찜하기: 오늘 찜한 개수 확인
|
||
const todayCount = this.currentStats.todayZzimCount;
|
||
const dailyLimit = this.currentLimits.daily_zzim_limit;
|
||
|
||
if (todayCount >= dailyLimit) {
|
||
return {
|
||
canZzim: false,
|
||
reason: `오늘 찜 제한에 도달했습니다. (${todayCount}/${dailyLimit})`
|
||
};
|
||
}
|
||
|
||
return { canZzim: true, remaining: dailyLimit - todayCount };
|
||
|
||
} else if (zzimType === 'mutual') {
|
||
// 품앗이 찜하기: 내 마일리지 확인 필요 없음 (오히려 벌어야 함)
|
||
// 단, 계정 보호를 위해 '오늘 찜한 개수' 제한은 동일하게 적용
|
||
const todayCount = this.currentStats.todayZzimCount;
|
||
const dailyLimit = this.currentLimits.daily_zzim_limit;
|
||
|
||
if (todayCount >= dailyLimit) {
|
||
return {
|
||
canZzim: false,
|
||
reason: `오늘 찜 제한에 도달했습니다. (품앗이 포함 통합 제한: ${todayCount}/${dailyLimit})`
|
||
};
|
||
}
|
||
|
||
return { canZzim: true, remaining: dailyLimit - todayCount };
|
||
}
|
||
|
||
return { canZzim: false, reason: '알 수 없는 찜 타입입니다.' };
|
||
}
|
||
|
||
async getActiveMarkets() {
|
||
try {
|
||
// user_markets 테이블에서 내 마켓 목록 가져오기
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
const markets = data[0]?.my_markets || [];
|
||
|
||
// 찜하기 대상으로 체크된 마켓만 반환
|
||
return markets.filter(market => market.for_zzim !== false);
|
||
|
||
} catch (error) {
|
||
console.error('활성 마켓 목록 조회 오류:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async getMutualMarkets() {
|
||
try {
|
||
console.log('[품앗이] 품앗이 마켓 목록 조회 시작');
|
||
|
||
// 내 현재 마일리지 확인 (users 테이블) - 삭제: 품앗이는 마일리지를 버는 행위이므로 보유량 체크 불필요
|
||
/*
|
||
const myStatsResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/users?id=eq.${this.user_id}&select=available_zzim_mile`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
let myAvailableMileage = 0;
|
||
if (myStatsResponse.ok) {
|
||
const myData = await myStatsResponse.json();
|
||
myAvailableMileage = myData[0]?.available_zzim_mile || 0;
|
||
}
|
||
|
||
console.log('[품앗이] 내 사용 가능한 마일리지:', myAvailableMileage);
|
||
|
||
if (myAvailableMileage <= 0) {
|
||
throw new Error('사용 가능한 마일리지가 없습니다. 먼저 다른 사람의 마켓에 찜을 받아 마일리지를 적립하세요.');
|
||
}
|
||
*/
|
||
|
||
// 다른 사용자들의 노출된 마켓 목록 가져오기 (품앗이용) - v_user_market_stats 뷰 사용
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/v_user_market_stats?user_id=neq.${this.user_id}&available_zzim_mile=gt.0&select=user_id,available_zzim_mile,my_markets&limit=100`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('품앗이 마켓 목록을 가져올 수 없습니다.');
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('[품앗이] 조회된 사용자 데이터:', data.length);
|
||
|
||
const mutualMarkets = [];
|
||
|
||
// 각 사용자의 노출된 마켓들을 수집
|
||
data.forEach(userMarket => {
|
||
const markets = userMarket.my_markets || [];
|
||
const userMileage = userMarket.available_zzim_mile || 0;
|
||
|
||
// 마일리지가 있는 사용자의 노출된 마켓만 수집
|
||
if (userMileage > 0) {
|
||
const visibleMarkets = markets.filter(market =>
|
||
market.is_visible !== false &&
|
||
market.for_mutual_zzim !== false // 품앗이 대상으로 설정된 마켓만
|
||
);
|
||
|
||
// 사용자 정보를 마켓에 추가
|
||
visibleMarkets.forEach(market => {
|
||
mutualMarkets.push({
|
||
...market,
|
||
owner_user_id: userMarket.user_id,
|
||
owner_available_mileage: userMileage
|
||
});
|
||
});
|
||
}
|
||
});
|
||
|
||
console.log('[품앗이] 수집된 품앗이 마켓 수:', mutualMarkets.length);
|
||
|
||
if (mutualMarkets.length === 0) {
|
||
throw new Error('현재 품앗이 가능한 마켓이 없습니다.');
|
||
}
|
||
|
||
// 우선순위 기반 정렬 (마일리지가 많은 사용자 우선)
|
||
mutualMarkets.sort((a, b) => {
|
||
// 1차: 마일리지 많은 순
|
||
if (b.owner_available_mileage !== a.owner_available_mileage) {
|
||
return b.owner_available_mileage - a.owner_available_mileage;
|
||
}
|
||
// 2차: 최신 등록 순
|
||
return new Date(b.created_at || 0) - new Date(a.created_at || 0);
|
||
});
|
||
|
||
// 내 마일리지로 처리 가능한 개수만큼 반환 (최대 10개) -> 수정: 제한 없음 (단, 하루 최대 10개 마켓 정도만)
|
||
const maxMarkets = 10;
|
||
const selectedMarkets = mutualMarkets.slice(0, maxMarkets);
|
||
|
||
console.log('[품앗이] 선택된 품앗이 마켓:', selectedMarkets.length);
|
||
|
||
return selectedMarkets;
|
||
|
||
} catch (error) {
|
||
console.error('품앗이 마켓 목록 조회 오류:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
showStatus(message) {
|
||
const statusEl = document.getElementById('zzim-status');
|
||
|
||
// 진행률 표시 숨기기
|
||
this.hideProgress();
|
||
|
||
// 모든 상태 메시지 숨기기
|
||
this.hideAllMessages();
|
||
|
||
// 상태 메시지 표시
|
||
if (statusEl) {
|
||
statusEl.textContent = message;
|
||
statusEl.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
showError(message) {
|
||
const errorEl = document.getElementById('zzim-error');
|
||
|
||
// 진행률 표시 숨기기
|
||
this.hideProgress();
|
||
|
||
// 모든 상태 메시지 숨기기
|
||
this.hideAllMessages();
|
||
|
||
// 오류 메시지 표시
|
||
if (errorEl) {
|
||
errorEl.textContent = message;
|
||
errorEl.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
showSuccess(message) {
|
||
const successEl = document.getElementById('zzim-success');
|
||
|
||
// 진행률 표시 숨기기
|
||
this.hideProgress();
|
||
|
||
// 모든 상태 메시지 숨기기
|
||
this.hideAllMessages();
|
||
|
||
// 성공 메시지 표시
|
||
if (successEl) {
|
||
successEl.textContent = message;
|
||
successEl.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
showProgress(current, total, message) {
|
||
const progressDiv = document.getElementById('zzim-progress');
|
||
const progressBar = document.getElementById('progress-bar');
|
||
const progressText = document.getElementById('progress-text');
|
||
const progressPercent = document.getElementById('progress-percent');
|
||
|
||
// 진행률 표시 영역 보이기
|
||
if (progressDiv) {
|
||
progressDiv.style.display = 'block';
|
||
}
|
||
|
||
// 다른 상태 메시지들 숨기기
|
||
this.hideAllMessages();
|
||
|
||
const percentage = Math.round((current / total) * 100);
|
||
|
||
if (progressBar) {
|
||
progressBar.style.width = `${percentage}%`;
|
||
}
|
||
|
||
if (progressText) {
|
||
progressText.textContent = message;
|
||
}
|
||
|
||
if (progressPercent) {
|
||
progressPercent.textContent = `${percentage}%`;
|
||
}
|
||
}
|
||
|
||
hideProgress() {
|
||
const progressDiv = document.getElementById('zzim-progress');
|
||
|
||
if (progressDiv) {
|
||
progressDiv.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
hideAllMessages() {
|
||
const statusEl = document.getElementById('zzim-status');
|
||
const errorEl = document.getElementById('zzim-error');
|
||
const successEl = document.getElementById('zzim-success');
|
||
|
||
if (statusEl) statusEl.style.display = 'none';
|
||
if (errorEl) errorEl.style.display = 'none';
|
||
if (successEl) successEl.style.display = 'none';
|
||
}
|
||
|
||
stopZzim() {
|
||
this.zzimInProgress = false;
|
||
this.updateButtonStates();
|
||
this.hideProgress();
|
||
this.showStatus('찜하기를 중단했습니다.');
|
||
}
|
||
|
||
updateButtonStates() {
|
||
const myMarketBtn = document.getElementById('my-market-zzim-btn');
|
||
const mutualBtn = document.getElementById('mutual-zzim-btn');
|
||
const stopBtn = document.getElementById('stop-zzim-btn');
|
||
|
||
if (this.zzimInProgress) {
|
||
// 찜하기 진행 중
|
||
if (myMarketBtn) {
|
||
myMarketBtn.disabled = true;
|
||
myMarketBtn.textContent = '💝 찜하기 진행 중...';
|
||
}
|
||
if (mutualBtn) {
|
||
mutualBtn.disabled = true;
|
||
mutualBtn.textContent = '🤝 찜하기 진행 중...';
|
||
}
|
||
if (stopBtn) {
|
||
stopBtn.style.display = 'inline-block';
|
||
stopBtn.disabled = false;
|
||
}
|
||
} else {
|
||
// 찜하기 중지 상태
|
||
if (myMarketBtn) {
|
||
myMarketBtn.disabled = false;
|
||
myMarketBtn.textContent = '💝 내 마켓 찜하기';
|
||
}
|
||
if (mutualBtn) {
|
||
mutualBtn.disabled = false;
|
||
mutualBtn.textContent = '🤝 품앗이 찜하기';
|
||
}
|
||
if (stopBtn) {
|
||
stopBtn.style.display = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 디버깅용 테스트 함수
|
||
async testDebug() {
|
||
console.log('=== 디버깅 테스트 시작 ===');
|
||
|
||
try {
|
||
// 1. 현재 설정 확인
|
||
console.log('현재 설정:', {
|
||
user_id: this.user_id,
|
||
has_token: !!this.access_token,
|
||
supabase_url: this.SUPABASE_URL
|
||
});
|
||
|
||
// 2. user_markets 테이블 상태 확인
|
||
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/user_markets?user_id=eq.${this.user_id}&select=my_markets`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'apikey': this.SUPABASE_ANON_KEY,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('user_markets 테이블 상태:', {
|
||
status: response.status,
|
||
ok: response.ok
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
console.log('user_markets 데이터:', data);
|
||
} else {
|
||
const errorText = await response.text();
|
||
console.error('user_markets 조회 오류:', errorText);
|
||
}
|
||
|
||
// 3. 강제로 마켓 목록 다시 로드
|
||
console.log('마켓 목록 강제 새로고침...');
|
||
await this.loadMyMarkets();
|
||
|
||
this.showSuccess('디버깅 테스트 완료 - 콘솔을 확인하세요');
|
||
|
||
} catch (error) {
|
||
console.error('디버깅 테스트 오류:', error);
|
||
this.showError('디버깅 테스트 실패: ' + error.message);
|
||
}
|
||
|
||
console.log('=== 디버깅 테스트 종료 ===');
|
||
}
|
||
}
|
||
|
||
// 전역 인스턴스 생성
|
||
let zzimManager;
|
||
|
||
// DOM 로드 완료 후 초기화
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
zzimManager = new ZzimManager();
|
||
|
||
// 전역 접근을 위해 window 객체에도 등록
|
||
window.zzimManager = zzimManager;
|
||
|
||
await zzimManager.init();
|
||
});
|