PageSourceSearch

https://localpay.kr/assets/MapView-DvEjez3N.js

js localpay.kr collected 2026-10-02 00:11:53 UTC 50,245 bytes, 101 lines download raw bytes

1import{s as G,_ as wt,r as y,D as Ne,o as E,c as A,b as d,t as x,l as xe,m as Pe,E as Kt,e as qe,v as mt,h as W,G as je,x as Wt,C as Ue,y as Gt,K as ve,V as Jt,J as Zt,n as K,u as ue,F as rt,f as lt,j as ct,d as Qt,a as dt,g as Yt,T as Xt,U as en,i as tn,N as _e}from"./index-BNZs4-Rt.js";import{d as nn,a as an,b as ut,c as sn,e as ft,f as on,g as pt,h as rn}from"./dots-horizontal-circle-BsmDJU3z.js";const ln="donggurangpay",cn=1,R="stores",X="metadata";let U=null;async function B(){return U||new Promise((a,s)=>{const o=indexedDB.open(ln,cn);o.onerror=()=>s(o.error),o.onsuccess=()=>{U=o.result,a(U)},o.onupgradeneeded=i=>{const r=i.target.result;r.objectStoreNames.contains(R)||r.createObjectStore(R,{keyPath:"chunkId"}).createIndex("hash","hash",{unique:!1}),r.objectStoreNames.contains(X)||r.createObjectStore(X,{keyPath:"key"})}})}async function $e(a,s,o){return await B(),new Promise((i,r)=>{const p=U.transaction(R,"readwrite").objectStore(R).put({chunkId:a,data:s,hash:o,savedAt:new Date().toISOString()});p.onsuccess=()=>i(),p.onerror=()=>r(p.error)})}async function dn(a){return await B(),new Promise((s,o)=>{const c=U.transaction(R,"readonly").objectStore(R).get(a);c.onsuccess=()=>s(c.result),c.onerror=()=>o(c.error)})}async function kt(){return await B(),new Promise((a,s)=>{const r=U.transaction(R,"readonly").objectStore(R).getAll();r.onsuccess=()=>a(r.result),r.onerror=()=>s(r.error)})}async function V(a,s){return await B(),new Promise((o,i)=>{const h=U.transaction(X,"readwrite").objectStore(X).put({key:a,value:s,savedAt:new Date().toISOString()});h.onsuccess=()=>o(),h.onerror=()=>i(h.error)})}async function vt(a){return await B(),new Promise((s,o)=>{const c=U.transaction(X,"readonly").objectStore(X).get(a);c.onsuccess=()=>s(c.result?.value),c.onerror=()=>o(c.error)})}async function un(a){return await B(),new Promise((s,o)=>{const r=U.transaction(R,"readwrite").objectStore(R).delete(a);r.onsuccess=()=>s(),r.onerror=()=>o(r.error)})}async function bt(a,s=[]){await B();const o=new Set(s.map(c=>`${a}_${c}`)),i=await kt(),r=`${a}_`;for(const c of i)c.chunkId.startsWith(r)&&!o.has(c.chunkId)&&await un(c.chunkId)}async function xa(){return await B(),new Promise((a,s)=>{const o=U.transaction([R,X],"readwrite");o.objectStore(R).clear(),o.objectStore(X).clear(),o.oncomplete=()=>a(),o.onerror=()=>s(o.error)})}async function Me(){return await B(),new Promise((a,s)=>{const r=U.transaction(R,"readonly").objectStore(R).getAll();r.onsuccess=()=>{const c={};r.result.forEach(h=>{c[h.chunkId]=h.hash}),a(c)},r.onerror=()=>s(r.error)})}const fn="https://runcksytemonsqxkqpfq.supabase.co",se=500;async function Se(a,s,o){const{data:i,error:r}=await G.functions.invoke("upload-chunk",{body:{action:"upload",bucketName:a,filePath:s,contentType:"application/json",data:o}});if(r)throw r;if(i?.error)throw new Error(i.error);return i}async function pn(a,s){const o=(s||[]).filter(Boolean);if(!o.length)return{removed:0};const{data:i,error:r}=await G.functions.invoke("upload-chunk",{body:{action:"delete",bucketName:a,filePaths:o}});if(r)throw r;if(i?.error)throw new Error(i.error);return i}function yt(a){const s=JSON.stringify(a);let o=0;for(let i=0;i<s.length;i++){const r=s.charCodeAt(i);o=(o<<5)-o+r,o=o&o}return Math.abs(o).toString(16).slice(0,8)}async function ht(a){const s=await fetch(`${a}/manifest.json?t=${Date.now()}`);if(!s.ok)throw new Error("Manifest 로드 실패");const o=s.headers.get("content-type");if(o&&o.includes("text/html"))throw new Error("Manifest 파일이 없습니다 (HTML 반환됨).");return s.json()}async function He(a){const{data:s,error:o}=await G.storage.from(a).download("manifest.json");if(o||!s)throw new Error("Manifest 로드 실패");const i=await s.text();try{return JSON.parse(i)}catch{throw new Error("Manifest 파일이 올바른 JSON 형식이 아닙니다.")}}async function vn(a,s){const{data:o,error:i}=await G.storage.from(a).download(s);if(i||!o)throw new Error(`청크 로드 실패: ${s}`);const r=await o.text();try{return JSON.parse(r)}catch{throw new Error(`청크 JSON 파싱 실패: ${s}`)}}async function Ve(a,s){const o=[...s?.chunks||[]].sort((c,h)=>c.id.localeCompare(h.id)),i=o.map(c=>c.id);await bt(a,i);const r=[];for(const c of o){const h=`${a}_${c.id}`,p=await dn(h);Array.isArray(p?.data)&&r.push(...p.data)}return r}async function Sa(a="29110"){await B();const s=`${fn}/storage/v1/object/public/${a}`,o=`/${a}`;let i,r="public",c=s;try{i=await ht(s)}catch{try{i=await He(a),r="storage-sdk"}catch{i=await ht(o),r="local",c=o}}const h=[...i.chunks].sort((f,b)=>f.id.localeCompare(b.id)),p=await Me(),l=[],u=[];for(const f of h){const b=`${a}_${f.id}`,M=p[b];!M||M!==f.hash?l.push(f):u.push(f.id)}if(l.length>0){const f=l.map(async b=>{let M;if(r==="storage-sdk")M=await vn(a,b.filename);else{const C=await fetch(`${c}/${b.filename}?t=${Date.now()}`);if(!C.ok)throw new Error(`청크 로드 실패: ${b.filename}`);M=await C.json()}const m=`${a}_${b.id}`;return await $e(m,M,b.hash),{id:b.id,data:M}});await Promise.all(f),await V(`${a}_manifestVersion`,i.version),await V(`${a}_manifestUpdatedAt`,i.updatedAt)}return Ve(a,i)}async function $a(a,s=()=>{},o="29110"){s(0,"청크 분할 중...");const i=[];for(let l=0;l<a.length;l+=se){const u=a.slice(l,l+se),f=`chunk_${String(Math.floor(l/se)).padStart(3,"0")}`;i.push({id:f,filename:`${f}.json`,data:u,hash:yt(u)})}s(10,"기존 manifest 확인 중...");let r=null;try{r=await He(o)}catch{}const c=[];if(r){for(const u of i){const f=r.chunks.find(b=>b.id===u.id);(!f||f.hash!==u.hash)&&c.push(u)}const l=r.chunks.filter(u=>!i.find(f=>f.id===u.id));for(const u of l)try{await pn(o,[u.filename])}catch{}}else c.push(...i);for(let l=0;l<c.length;l++){const u=c[l],f=20+Math.floor(l/c.length*60);
1s(f,`청크 업로드 중... (${l+1}/${c.length})`),await Se(o,u.filename,u.data)}s(85,"manifest 업데이트 중...");const h=new Date().toISOString();i.sort((l,u)=>l.id.localeCompare(u.id));const p={version:r?r.version+1:1,createdAt:r?.createdAt||h,updatedAt:h,totalRecords:a.length,chunkSize:se,totalChunks:i.length,chunks:i.map(l=>({id:l.id,filename:l.filename,hash:l.hash,recordCount:l.data.length,updatedAt:c.find(u=>u.id===l.id)?h:r?.chunks.find(u=>u.id===l.id)?.updatedAt||h}))};return await Se(o,"manifest.json",p),s(100,"저장 완료!"),{version:p.version,totalRecords:a.length,chunksUpdated:c.length,totalChunks:i.length}}async function Ma(a,s,o="29110"){const i=Math.floor(s/se),r=`chunk_${String(i).padStart(3,"0")}`,c=i*se,h=Math.min(c+se,a.length),p=a.slice(c,h),l=yt(p);await Se(o,`${r}.json`,p);const u=await He(o),f=new Date().toISOString(),b=u.chunks.find(M=>M.id===r);return b&&(b.hash=l,b.recordCount=p.length,b.updatedAt=f),u.version+=1,u.updatedAt=f,u.totalRecords=a.length,await Se(o,"manifest.json",u),{chunkId:r,hash:l}}const _t="https://runcksytemonsqxkqpfq.supabase.co";async function hn(a){if(!a)return!1;try{const{data:s,error:o}=await G.storage.from(String(a)).list("",{limit:10,search:"manifest.json"});return o?(console.warn("서버 데이터 확인 실패:",o.message),!1):Array.isArray(s)&&s.some(i=>i.name==="manifest.json")}catch(s){return console.error("서버 데이터 확인 오류:",s),!1}}async function fe(a){const s=await fetch(`${a}/manifest.json?t=${Date.now()}`);if(!s.ok)throw new Error("Manifest 로드 실패");return s.json()}async function ze(a,s){const o=await fetch(`${a}/${s}`);if(!o.ok)throw new Error(`청크 로드 실패: ${s}`);return o.json()}async function gn(a=()=>{},s="29110"){await B();const o=`${_t}/storage/v1/object/public/${s}`,i=`/${s}`;console.log(`🌍 데이터 로드 지역: ${s}`);let r=null,c=o;try{r=await fe(o),console.log("✅ Storage 사용")}catch(l){console.warn("⚠️ Storage 접근 실패, 로컬 폴더 사용:",l.message);try{r=await fe(i),c=i,console.log("✅ 로컬 public 폴더 사용")}catch(u){throw console.error("❌ Manifest 로드 실패:",u.message),u}}const h=await Me(),p=[];for(const l of r.chunks){const u=`${s}_${l.id}`,f=h[u];(!f||f!==l.hash)&&p.push({...l,uniqueChunkId:u})}if(p.length===0){const l=await Ve(s,r);if(l.length>0||r.totalRecords===0)return console.log(`⚡ manifest 기준 캐시 사용 (${l.length}건)`),a(100,100,"캐시 사용"),wn(s,o,i).catch(u=>{console.warn("백그라운드 업데이트 확인 실패:",u)}
1),await V("lastRegion",s),l}a(5,100,"서버 확인 중...");try{const l=p.length>0?p:r.chunks.map(m=>({...m,uniqueChunkId:`${s}_${m.id}`}));console.log(`📊 다운로드 필요 청크: ${l.length}개`);let u=0;const f=l.length;a(10,100,"다운로드 준비 중...");const b=l.map(async m=>{const C=await ze(c,m.filename);await $e(m.uniqueChunkId,C,m.hash),u++;const I=10+Math.round(u/f*80);a(I,100,`데이터 다운로드 중... ${Math.round(u/f*100)}%`)});await Promise.all(b),await V(`${s}_manifestVersion`,r.version),await V(`${s}_manifestUpdatedAt`,r.updatedAt),a(95,100,"데이터 병합 중...");const M=await Ve(s,r);return a(100,100,"로딩 완료!"),console.log(`📍 총 ${M.length}개 가맹점 설정 완료`),await V("lastRegion",s),M}catch(l){throw console.error("데이터 로드 오류:",l),l}}async function wn(a,s,o){try{let i,r=s;try{i=await fe(s)}catch{i=await fe(o),r=o}const c=await Me(),h=[];for(const p of i.chunks){const l=`${a}_${p.id}`,u=c[l];(!u||u!==p.hash)&&h.push({...p,uniqueChunkId:l})}if(h.length>0){console.log(`🔄 백그라운드 업데이트: ${h.length}개 청크`);for(const p of h){const l=await ze(r,p.filename);await $e(p.uniqueChunkId,l,p.hash)}await bt(a,i.chunks.map(p=>p.id)),await V(`${a}_manifestVersion`,i.version),await V(`${a}_manifestUpdatedAt`,i.updatedAt),console.log("✅ 백그라운드 업데이트 완료 (다음 로드 시 반영)")}else console.log("✅ 데이터가 최신 상태입니다")}catch(i){console.warn("백그라운드 업데이트 실패:",i)}}async function mn(){await B();const a=await vt("manifestVersion"),s=await vt("manifestUpdatedAt"),o=await kt();let i=0;return o.forEach(r=>{i+=r.data.length}),{hasCache:o.length>0,manifestVersion:a,manifestUpdatedAt:s,chunksCount:o.length,totalRecords:i}}async function kn(a="29110"){await B();const s=`${_t}/storage/v1/object/public/${a}`,o=`/${a}`;try{let i,r=s;try{i=await fe(s)}catch{i=await fe(o),r=o}const c=await Me(),h=[];for(const p of i.chunks){const l=`${a}_${p.id}`,u=c[l];(!u||u!==p.hash)&&h.push({...p,uniqueChunkId:l})}if(h.length>0){console.log(`🔄 수동 업데이트: ${h.length}개 청크`);for(const p of h){const l=await ze(r,p.filename);await $e(p.uniqueChunkId,l,p.hash)}return await V(`${a}_manifestVersion`,i.version),await V(`${a}_manifestUpdatedAt`,i.updatedAt),console.log("✅ 수동 업데이트 완료"),{updated:!0,updatedChunks:h.length}}else return console.log("✅ 데이터가 최신 상태입니다"),{updated:!1,updatedChunks:0}}catch(i){throw console.error("업데이트 확인 실패:",i),i}}const gt=[{category:"음식·식품",description:"식당, 카페, 식자재 판매",items:["스넥","서양음식","슈퍼 마켓","농축수산품","일반한식","정 육 점","기타음료식품","일식회집","편 의 점","제과점","중국음식","주류판매점","기타건강식","미곡상","홍삼 제품","인삼 제품","칵테일바","주점"]},{category:"병원·약국·의료",description:"병의원, 약국 및 건강 관련 서비스",items:["의원","한의원","동물병원","약국","치과의원","의료 용품","한약방","한방병원","안마/스포츠마사지","병원","기타의료기관및기타의료기기","제약회사"]},{category:"의류·잡화·미용",description:"옷, 신발, 액세서리 및 미용",items:["안경","제 화 점","정장","피부미용실","맞춤복 점","내의판매점","화 장 품","미 용 원","옷감 직물","귀 금 속","가  방","신  발","기타의류","신변잡화수리","악세 사리","미용 재료","사우나","이 용 원","양 품 점","기타직물","단체복","캐쥬얼의류","아동의류","시  계","민예공예품","기념품 점"]},{category:"교육·문화·취미",description:"학원, 서점, 문구 및 문화 생활",items:["예체능학원","보습학원","외국어학원","일반 서적","전문 서적","기타서적문구","문구용 품","문화취미기타","음반,영상물","티켓","화방표구점","악 기 점","완 구 점","화  랑","피아노대리점","기능학원","독서실","컴퓨터학원","학원(회원제형태)","출판인쇄물"]},{category:"레저·스포츠",description:"운동 시설 및 레저 활동",items:["스포츠레져용품","당구장","기타레져업","헬스 크럽","골프연습장","관광여행","노래방","골프 용품","레져용품수리","스크린골프","레져업소(회원제형태)","볼 링 장","수 영 장","기타회원제형태업소4","기타회원제형태업소1"]},{category:"가전·가구·인테리어",description:"집안 살림, 가전제품, 건축 자재",items:["가전 제품","주방용식기","주방 용구","인테 리어","페 인 트","목재,석재,철물","조명 기구","유  리","기타건축자재","기타가구","냉열 기기","기타전기제품","보일러펌프샷시","일반 가구","정 수 기","침구수예점","카페트,커텐,천막,지물","통신 기기","컴 퓨 터","소프트웨어","사무통신기기수리","기계 공구","건축요업품","기타사무용품"]},{category:"자동차·주유",description:"차량 유지 보수 및 연료",
1items:["세 차 장","자동차정비","L P G","자동차부품","자동차시트/타이어","유류 판매","주 차 장","현대정유오일뱅크","SK가스충전소","렌 트 카","이륜차판매","카인테리어"]},{category:"생활서비스·기타",description:"생활 편의 서비스 및 기타 전문점",items:["혼례서비스","애완 동물","사무서비스","종합용역","사진관","기타잡화","기타대인서비스","세탁소","기타전문점","비료/농약/사료/종자","부동산중개/임대","법률회계서비스(개인)","가정용품수리","기타유통업","기타용역서비스","연 쇄 점","기타수리서비스","화  원","기타숙박업"]}],bn={class:"modal-body"},yn={class:"store-info"},_n={class:"store-address"},xn={class:"form-group"},Sn={class:"form-group"},$n={class:"modal-footer"},Mn=["disabled"],Cn={__name:"ReportModal",props:{isOpen:Boolean,storeData:{type:Object,default:()=>({})}},emits:["close","submitted"],setup(a,{emit:s}){const{showToast:o}=Wt(),i=a,r=s,c=y({report_type:"",description:"",reporter_email:""}),h=y(!1);Ne(()=>i.isOpen,u=>{u&&(c.value={report_type:"",description:"",reporter_email:""})});const p=()=>{r("close")},l=async()=>{h.value=!0;try{const u=await je(),{data:f,error:b}=await G.from("reports").insert([{store_name:i.storeData.가맹점명,category:i.storeData.카테고리,business_type:i.storeData.가맹점업종명,address:i.storeData.사업장주소,latitude:i.storeData.위도,longitude:i.storeData.경도,report_type:c.value.report_type,description:c.value.description,reporter_email:c.value.reporter_email||null,region_code:u||i.storeData.region_code||null,status:"pending"}]).select();if(b)throw b;o("제출되었습니다.","success"),r("submitted"),setTimeout(()=>{p()},1500)}catch(u){console.error("제보 전송 실패:",u),o("제보 전송에 실패했습니다. 다시 시도해주세요.","error")}finally{h.value=!1}};return(u,f)=>a.isOpen?(E(),A("div",{key:0,class:"modal-overlay",onClick:p},[d("div",{class:"modal-container",onClick:f[2]||(f[2]=xe(()=>{},["stop"]))},[d("div",{class:"modal-header"},[f[3]||(f[3]=d("h2",null,"🚨 정보 제보하기",-1)),d("button",{class:"close-btn",onClick:p},"×")]),d("div",bn,[d("div",yn,[d("h3",null,x(a.storeData.가맹점명),1),d("p",_n,x(a.storeData.사업장주소),1)]),d("form",{onSubmit:xe(l,["prevent"])},[d("div",xn,[f[5]||(f[5]=d("label",{for:"reportType"},"제보 유형 *",-1)),Pe(d("select",{id:"reportType","onUpdate:modelValue":f[0]||(f[0]=b=>c.value.report_type=b),required:""},[...f[4]||(f[4]=[qe('<option value="" data-v-97e23714>선택해주세요</option><option value="wrong_info" data-v-97e23714>잘못된 정보</option><option value="closed" data-v-97e23714>폐업</option><option value="moved" data-v-97e23714>이전</option><option value="wrong_category" data-v-97e23714>업종 오류</option><option value="other" data-v-97e23714>기타</option>',6)])],512),[[Kt,c.value.report_type]])]),d("div",Sn,[f[6]||(f[6]=d("label",{for:"description"},"상세 내용 *",-1)),Pe(d("textarea",{id:"description","onUpdate:modelValue":f[1]||(f[1]=b=>c.value.description=b),placeholder:"어떤 정보가 잘못되었는지 자세히 알려주세요.",rows:"4",required:""},null,512),[[mt,c.value.description]])]),d("div",$n,[d("button",{type:"button",class:"btn-cancel",onClick:p}," 취소 "),d("button",{type:"submit",class:"btn-submit",disabled:h.value},x(h.value?"전송 중...":"제보하기"),9,Mn)])],32)])])])):W("",!0)}},En=wt(Cn,[["__scopeId","data-v-97e23714"]]),An=()=>/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)?"mobile":"desktop",J=async(a,s,o={})=>{try{const i=An();console.log("[Analytics] RPC 호출 시작:",{p_region_code:a,p_event_type:s,p_event_data:o,p_device_type:i});const{data:r,error:c}=await G.rpc("track_event",{p_region_code:a,p_event_type:s,p_event_data:o,p_device_type:i});c?console.error("[Analytics] RPC 에러:",c):console.log("[Analytics] RPC 성공:",r)}catch(i){console.error("[Analytics] 추적 실패:",i)}},Ln=a=>J(a,"page_view"),In=(a,s,o)=>J(a,"store_click",{store_id:s,category:o||"unknown"}),Tn=(a,s)=>J(a,"search",{keyword:s}),Rn=(a,s)=>J(a,"category_filter",{category:s}),Dn=(a,s)=>J(a,"map_nav_kakao",{store_id:s}),On=(a,s)=>J(a,"map_nav_naver",{store_id:s}),Bn=a=>J(a,"my_location"),Fn=(a,s)=>J(a,"report_click",{store_id:s}),Un=a=>J(a,"data_refresh"),Nn={class:"map-wrapper"},Pn={class:"sidebar-header"},qn={class:"filter-info"},jn={key:0},Vn={key:1},Hn={class:"filter-count"},zn={class:"category-list"},Kn=["onClick"],Wn={class:"category-icon"},Gn={class:"category-name"},Jn=["disabled"],Zn={class:"refresh-text"},Qn={key:0,class:"loading-overlay"},Yn={class:"loading-content"},Xn={class:"loading-text"},ea={class:"progress-bar-container"},ta={class:"loading-percentage"},na={key:1,class:"cluster-loading-overlay"},aa={class:"store-count-text"},sa={key:0,class:"filter-badge"},oa={class:"event-count"},ia={class:"search-bar-container"},ra={class:"search-bar"},la=["disabled"],ca={class:"location-text"},da={key:0,class:"search-results"},ua=["onMousedown"],fa={class:"result-name"},pa={class:"result-address"},va={class:"result-category"},ha={key:1,class:"search-no-results"},ga={key:0,class:"toast-overlay"},wa={class:"material-icons toast-icon"},ma=1,ka={__name:"MapView",emits:["require-data-upload"],setup(a,{emit:s}){const o={food:rn,health:pt,medical:pt,fashion:on,culture:ft,education:ft,sports:sn,living:ut,home:ut,car:an,etc:nn},i=s,r=y({lat:35.1461,lng:126.9231}),c=y(null),h=en(),p=tn(),l=y("29110"),u=Ue(()=>h.name==="Map"),f=y(!1),b=()=>{f.value=!1,i("require-data-upload")},M=()=>{ie.value=!1,f.value=!0};let m=null,C=null,I=null,oe=!1,H=new Map,Ke=0,Ce=null,Ee=!1;const We={1:5e3,2:4e3,3:3e3,4:2500,5:2e3,6:1500,7:1e3,8:800,9:600,10:500,
111:400,12:300,13:200,14:150},he=y([]),Ge=y(0),ie=y(!0),P=y("데이터 준비 중..."),q=y(0),xt=y(!1),ee=y(!1),L=y([]),j=y(""),te=y(!1),Ae=y(null),z=y(!1),Je=y(null);let ge=null;const we=y(!1),Ze=y(null),me=y(!1),Le=y(new Set),Ie=y(0),re=y(!1),Qe=y(""),Te=y("success"),le=y(!1),ke=gt.map(n=>({name:n.category,icon:St(n.category),color:$t(n.category)}));function St(n){return{"음식·식품":"🍴","병원·약국·의료":"🏥","의류·잡화·미용":"👗","교육·문화·취미":"📚","레저·스포츠":"⚽","가전·가구·인테리어":"🏠","자동차·주유":"🚗","생활서비스·기타":"🔧"}[n]||"📍"}function $t(n){return{"음식·식품":"#FF6B6B","병원·약국·의료":"#4ECDC4","의류·잡화·미용":"#A855F7","교육·문화·취미":"#3B82F6","레저·스포츠":"#22C55E","가전·가구·인테리어":"#F59E0B","자동차·주유":"#6366F1","생활서비스·기타":"#64748B"}[n]||"#888"}const ne=Ue(()=>{let n=he.value;return L.value.length>0&&(n=n.filter(e=>L.value.includes(et(e)))),me.value&&(n=n.filter(e=>{const t=String(e.id).includes("-")?e.id:`${l.value}-${e.id}`;return Le.value.has(String(e.id))||Le.value.has(t)})),n}),pe=Ue(()=>{const n=j.value.trim().toLowerCase();return n?he.value.filter(e=>{const t=e.가맹점명?.toLowerCase().includes(n),v=e.사업장주소?.toLowerCase().includes(n);return t||v}).slice(0,20):[]}),Re={},Mt=[{width:"40px",height:"40px",background:"rgba(76, 175, 80, 0.9)",borderRadius:"20px",color:"#fff",textAlign:"center",fontWeight:"bold",lineHeight:"40px",fontSize:"12px"},{width:"46px",height:"46px",background:"rgba(33, 150, 243, 0.9)",borderRadius:"23px",color:"#fff",textAlign:"center",fontWeight:"bold",lineHeight:"46px",fontSize:"13px"},{width:"52px",height:"52px",background:"rgba(236, 64, 122, 0.9)",borderRadius:"26px",color:"#fff",textAlign:"center",fontWeight:"bold",lineHeight:"52px",fontSize:"14px"},{width:"58px",height:"58px",background:"rgba(171, 71, 188, 0.9)",borderRadius:"29px",color:"#fff",textAlign:"center",fontWeight:"bold",lineHeight:"58px",fontSize:"15px"},{width:"66px",height:"66px",background:"rgba(103, 58, 183, 0.9)",borderRadius:"33px",color:"#fff",textAlign:"center",fontWeight:"bold",lineHeight:"66px",fontSize:"16px"}],be={food:"#FF6B6B",health:"#4ECDC4",medical:"#4ECDC4",fashion:"#A855F7",culture:"#3B82F6",education:"#3B82F6",sports:"#22C55E",living:"#F59E0B",home:"#F59E0B",car:"#6366F1",life:"#64748b",etc:"#64748b"},Ye={"음식·식품":"food","병원·약국·의료":"medical","의류·잡화·미용":"fashion","교육·문화·취미":"education","레저·스포츠":"sports","가전·가구·인테리어":"living","자동차·주유":"car","생활서비스·기타":"etc",일반음식점:"food",음식점:"food",음식:"food",식품:"food","병의원/약국":"medical","병원/약국"
1:"medical",병원:"medical",약국:"medical",의료:"medical",의원:"medical",한의원:"medical",치과:"medical","의류/잡화":"fashion","미용/보건위생":"fashion",미용:"fashion",보건위생:"fashion",의류:"fashion",미용원:"fashion","학원/교육":"education","교육/학원":"education",교육:"education",학원:"education","문화/레저":"sports","레저/스포츠":"sports",레저:"sports",스포츠:"sports","유통/마트":"living","가구/인테리어":"living","가전/가구":"living",가구:"living",인테리어:"living",마트:"living",슈퍼마켓:"living",슈퍼:"living",편의점:"living","주유소/자동차":"car",주유소:"car",자동차:"car",정비:"car",숙박업:"living",숙박:"living",기타:"etc"},De={},Oe={};gt.forEach(n=>{const e=n.category,t=Ye[e]||"etc";n.items.forEach(v=>{const k=v.trim().replace(/\s+/g,"");De[k]=t,Oe[k]=e})});const Xe=n=>{if(!n)return"etc";if(n.가맹점업종명){const e=n.가맹점업종명.trim().replace(/\s+/g,"");if(De[e])return De[e]}if(n.카테고리&&n.카테고리.trim()){const e=Ye[n.카테고리.trim()];if(e)return e;const t=n.카테고리;if(t.includes("음식")||t.includes("식품")||t.includes("제과")||t.includes("식당"))return"food";if(t.includes("약국")||t.includes("병원")||t.includes("의료")||t.includes("의원")||t.includes("한의원")||t.includes("치과"))return"medical";if(t.includes("의류")||t.includes("잡화")||t.includes("미용")||t.includes("위생")||t.includes("화장품")||t.includes("목욕")||t.includes("사우나"))return"fashion";if(t.includes("교육")||t.includes("학원")||t.includes("학교")||t.includes("서적")||t.includes("서점")||t.includes("독서실"))return"education";if(t.includes("레저")||t.includes("스포츠")||t.includes("문화")||t.includes("취미")||t.includes("당구")||t.includes("노래방")||t.includes("헬스")||t.includes("골프"))return"sports";if(t.includes("유통")||t.includes("가구")||t.includes("마트")||t.includes("인테리어")||t.includes("가전")||t.includes("컴퓨터")||t.includes("조명")||t.includes("주방"))return"living";if(t.includes("주유")||t.includes("자동차")||t.includes("가스")||t.includes("정비")||t.includes("세차"))return"car";if(t.includes("숙박")||t.includes("호텔")||t.includes("모텔")||t.includes("펜션"))return"living"}if(n.categoryEn)return n.categoryEn==="gas"?"car":n.categoryEn==="health"?"medical":n.categoryEn==="culture"?"education":n.categoryEn==="home"?"living":n.categoryEn;if(n.가맹점업종명){const e=n.가맹점업종명;if(e.includes("식당")||e.includes("한식")||e.includes("중식")||e.includes("일식")||e.includes("양식")||e.includes("카페")||e.includes("커피")||e.includes("제과")||e.includes("빵")||e.includes("분식")||e.includes("음식"))return"food";if(e.includes("의원")||e.includes("병원")||e.includes("치과")||e.includes("한의원")||e.includes("약국")||e.includes("동물병원"))return"medical";if(e.includes("미용")||e.includes("뷰티")||e.includes("네일")||e.includes("의류")||e.includes("옷")||e.includes("안경"))return"fashion";if(e.includes("학원")||e.includes("독서실")||e.includes("서점")||e.includes("도서"))return"education";if(e.includes("헬스")||e.includes("피트니스")||e.includes("스포츠")||e.includes("골프")||e.includes("당구")||e.includes("노래")||e.includes("낚시"))return"sports";if(e.includes("슈퍼")||e.includes("마트")||e.includes("편의점")||e.includes("유통")||e.includes("가구")||e.includes("인테리어")||e.includes("철물")||e.includes("컴퓨터")||e.includes("폰"))return"living";if(e.includes("주유")||e.includes("충전")||e.includes("카센타")||e.includes("정비")||e.includes("세차"))return"car"}return"etc"},et=n=>{if(!n)return"-";if(n.카테고리&&n.카테고리.trim())return n.카테고리;if(n.가맹점업종명){const e=n.가맹점업종명.trim().replace(/\s+/g,"");if(Oe[e])return Oe[e];const t=n.가맹점업종명;if(t.includes("식당")||t.includes("한식")||t.includes("중식")||t.includes("일식")||t.includes("양식")||t.includes("카페")||t.includes("커피")||t.includes("제과")||t.includes("빵")||t.includes("분식")||t.includes("음식"))return"음식·식품";if(t.includes("의원")||t.includes("병원")||t.includes("치과")||t.includes("한의원")||t.includes("약국")||t.includes("동물병원"))return"병원·약국·의료";if(t.includes("미용")||t.includes("뷰티")||t.includes("네일")||t.includes("의류")||t.includes("옷")||t.includes("안경"))return"의류·잡화·미용";if(t.includes("학원")||t.includes("독서실")||t.includes("서점")||t.includes("도서"))return"교육·문화·취미";if(t.includes("헬스")||t.includes("피트니스")||t.includes("스포츠")||t.includes("골프")||t.includes("당구")||t.includes("노래")||t.includes("낚시"))return"레저·스포츠";if(t.includes("슈퍼")||t.includes("마트")||t.includes("편의점")||t.includes("유통")||t.includes("가구")||t.includes("인테리어")||t.includes("철물")||t.includes("컴퓨터")||t.includes("폰"))return"가전·가구·인테리어";if(t.includes("주유")||t.includes("충전")||t.includes("카센타")||t.includes("정비")||t.includes("세차"))return"자동차·주유"}return n.카테고리||"-"},Ct=n=>{const e=be[n]||be.etc,t=o[n]||o.etc,v=`pin-grad-${n||"etc"}`,k=`<svg xmlns="http://www.w3.org/2000/svg" width="40" height="52" viewBox="0 0 40 52">
2    <style>
3      .icon-svg-content path, .icon-svg-content circle, .icon-svg-content rect, .icon-svg-content polygon, .icon-svg-content ellipse {
4        fill: #ffffff !important;
5      }
6    </style>
7    <!-- 바닥 그림자 -->
8    <ellipse cx="20" cy="48" rx="12" ry="3.5" fill="rgba(0, 0, 0, 0.25)" />
9    
10    <!-- 그라데이션 정의 -->
11    <defs>
12      <linearGradient id="${v}
12" x1="0%" y1="0%" x2="0%" y2="100%">
13        <stop offset="0%" stop-color="${e}" stop-opacity="0.85" />
14        <stop offset="100%" stop-color="${e}" stop-opacity="1" />
15      </linearGradient>
16    </defs>
17    
18    <!-- 물방울 핀 바디 (외곽선 제거) -->
19    <path d="M20 2 C10.1 2 2 10.1 2 20 C2 31.5 20 48 20 48 C20 48 38 31.5 38 20 C38 10.1 29.9 2 20 2 Z" fill="url(#${v})" />
20    
21    <!-- 흰색 내부 아이콘 -->
22    <g class="icon-svg-content" fill="#ffffff" transform="translate(12, 12) scale(0.66)">
23      ${t.body}
24    </g>
25  </svg>`;return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(k)}`},Et=n=>{const e=be[n]||be.etc,t=o[n]||o.etc,v=`<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36">
26    <style>
27      .icon-svg-content path, .icon-svg-content circle, .icon-svg-content rect, .icon-svg-content polygon, .icon-svg-content ellipse {
28        fill: #ffffff !important;
29      }
30    </style>
31    <circle cx="18" cy="18" r="17" fill="${e}" />
32    <g class="icon-svg-content" fill="#ffffff" transform="translate(11, 11) scale(0.58)">
33      ${t.body}
34    </g>
35  </svg>`;return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(v)}`},F=n=>String(n??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"),ye=n=>{if(!n)return"";const e=n.split(/[-./]/);if(e.length===3){const t=e[0].trim(),v=e[1].trim(),k=e[2].trim();return`${t}년 ${v}월 ${k}일`}return n},At=n=>{if(!n)return"";try{const e=new URL(String(n).trim(),window.location.origin),t=e.protocol.toLowerCase();return t==="http:"||t==="https:"?e.href:""}catch{return""}},Lt=n=>{const e=n||"default";return!Re[e]&&window?.kakao?.maps&&(Re[e]=new window.kakao.maps.MarkerImage(Ct(n),new window.kakao.maps.Size(40,52),{offset:new window.kakao.maps.Point(20,48)})),Re[e]},It=()=>{if(!c.value||!window?.kakao?.maps)return;const n={center:new window.kakao.maps.LatLng(r.value.lat,r.value.lng),level:5};m=new window.kakao.maps.Map(c.value,n);const e=new window.kakao.maps.ZoomControl;m.addControl(e,window.kakao.maps.ControlPosition.RIGHT),C=new window.kakao.maps.MarkerClusterer({map:m,averageCenter:!1,minLevel:2,minClusterSize:5,gridSize:80,calculator:[10,50,100,500],styles:Mt,disableClickZoom:!0,animation:!1}
35),window.kakao.maps.event.addListener(m,"click",()=>{Date.now()-Ke<500||(I&&(I.setMap(null),I=null),ce())}),window.kakao.maps.event.addListener(C,"clusterclick",t=>{if(!m||(t.getMarkers?.()||[]).length===0)return;const k=t.getCenter?.();if(!k)return;const g=Math.max(m.getLevel()-1,1);m.setLevel(g,{anchor:k}),m.panTo(k)}),window.kakao.maps.event.addListener(m,"idle",()=>{oe&&(Ce&&clearTimeout(Ce),Ce=setTimeout(()=>{st(!1)},200))})},Tt=()=>new Promise((n,e)=>{const v=Date.now(),k=()=>{if(window.kakao&&window.kakao.maps){window.kakao.maps.load(()=>n());return}if(Date.now()-v>1e4){e(new Error("Kakao SDK 로드 타임아웃 - 네트워크 상태를 확인해주세요."));return}setTimeout(k,100)};k()}),tt=(n,e)=>{const t=window.FlutterOpenUrl!==void 0||window.FlutterLocation!==void 0,v=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);if(t&&window.FlutterOpenUrl){window.FlutterOpenUrl.postMessage(JSON.stringify({appScheme:n,webUrl:e}));return}if(!v){window.open(e,"_blank");return}if(/Android/i.test(navigator.userAgent)){const g=document.createElement("a");g.href=n,g.style.display="none",document.body.appendChild(g),g.click(),document.body.removeChild(g),setTimeout(()=>{document.hasFocus()&&(window.location.href=e)},2e3)}else window.location.href=n,setTimeout(()=>{document.hasFocus()&&(window.location.href=e)},2e3)},Rt=async()=>{await _e();try{await Tt(),It()}catch(n){console.error("Kakao Maps SDK 로드 실패:",n),P.value="지도 로드 실패. 새로고침 해주세요."}},nt=async n=>{if(!m||!window?.kakao?.maps)return;I&&I.setMap(null),ce(),In(l.value,n.id,n.카테고리);let e=null;try{const _=String(n.id).includes("-")?n.id:`${l.value}-${n.id}`,{data:T,error:D}=await G.from("store_details").select("phone, website, event, event_start_date, event_end_date").in("store_id",[String(n.id),_]).maybeSingle();D||(e=T)}catch{}const t=document.createElement("div");t.className="info-window-wrap";let v="",k="";if(e){const _=At(e.website),T=F(e.phone||""),D=T||_,w=e.event;if(D&&(v+='<span class="info-badge info-badge-extra" data-toggle="extra">📌 추가정보</span>',k+=`
36        <tr class="extra-info-row" style="display: none;">
37          <td colspan="2" class="extra-info-content">
38            ${T?`<div class="extra-info-item">📞 <a href="tel:${T}" class="info-link">${T}</a></div>`:""}
39            ${_?`<div class="extra-info-item">🌐 <a href="${F(_)}" target="_blank" rel="noopener noreferrer" class="info-link">샵 홈페이지로 가기</a></div>`:""}
40          </td>
41        </tr>
42      `),w){let $="";e.event_start_date&&e.event_end_date?$=`이벤트 기간 ${F(ye(e.event_start_date))} ~ ${F(ye(e.event_end_date))}`:e.event_start_date?$=`이벤트 기간 ${F(ye(e.event_start_date))} ~`:e.event_end_date&&($=`이벤트 기간 ~ ${F(ye(e.event_end_date))}`),v+='<span class="info-badge info-badge-event" data-toggle="event">🎉 이벤트</span>',k+=`
43        <tr class="event-info-row" style="display: none;">
44          <td colspan="2" class="event-info-content">
45            ${$?`<div class="event-period">${$}</div>`:""}
46            <div class="event-text">${F(e.event)}</div>
47          </td>
48        </tr>
49      `}}t.innerHTML=`
50    <div class="info-window-content">
51      <button class="close-btn">&times;</button>
52      <div class="info-header">
53        <img src="${F(Et(Xe(n)))}" class="info-icon" onerror="this.src='/images/default.png'" />
54        <h3 class="info-title">${F(n.가맹점명||"")}</h3>
55      </div>
56      <table class="info-table">
57        <tr>
58          <td class="info-label">카테고리</td>
59          <td class="info-value">${F(et(n))}</td>
60        </tr>
61        <tr>
62          <td class="info-label">업종</td>
63          <td class="info-value">${F(n.가맹점업종명||"-")}</td>
64        </tr>
65        <tr>
66          <td class="info-label">주소</td>
67          <td class="info-value">${F(n.사업장주소||"-")}</td>
68        </tr>
69        ${k}
70      </table>
71      <div class="map-navigation-btns">
72        <button class="nav-btn kakao-btn" data-nav="kakao">
73          <span class="nav-icon">
74            <svg viewBox="0 0 24 24" width="16" height="16">
75              <path d="M12 3a7 7 0 0 0-7 7c0 5 7 11.5 7 11.5s7-6.5 7-11.5a7 7 0 0 0-7-7z" fill="#007cff" />
76              <circle cx="12" cy="10" r="2.3" fill="#fee500" />
77            </svg>
78          </span>
79          <span class="nav-text">카카오맵</span>
80        </button>
81        <button class="nav-btn naver-btn" data-nav="naver">
82          <span class="nav-icon">
83            <svg viewBox="0 0 24 24" width="16" height="16" style="border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.15);">
84              <rect width="24" height="24" rx="5" fill="#ffffff" />
85              <path d="M9.5 17.5h8c1 0 1.5.5 1.5 1.5" stroke="#2f7bf2" stroke-width="3" fill="none" stroke-linecap="round" />
86              <path d="M9.5 5a4.5 4.5 0 0 0-4.5 4.5c0 3.2 4.5 8 4.5 8s4.5-4.8 4.5-8A4.5 4.5 0 0 0 9.5 5z" fill="#00c73c" />
87              <text x="9.5" y="11.2" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-weight="900" font-size="5.2" fill="#ffffff" text-anchor="middle" letter-spacing="-0.2">N</text>
88            </svg>
89          </span>
90          <span class="nav-text">네이버지도</span>
91        </button>
92      </div>
93      <div class="info-badges">
94        ${v}
95        <button class="report-btn" title="정보 제보">🚨</button>
96      </div>
97    </div>
98  `,t.querySelector(".close-btn")?.addEventListener("click",()=>{I?.setMap(null),I=null});const S=_=>{Ke=Date.now(),_.stopPropagation()};t.addEventListener("mousedown",S),t.addEventListener("touchstart",S,{passive:!1}),t.addEventListener("pointerdown",S),t.addEventListener("click",S),t.querySelectorAll(".info-badge").forEach(_=>{_.addEventListener("click",T=>{T.stopPropagation();const D=_.getAttribute("data-toggle");if(D==="extra"){const w=t.querySelector(".extra-info-row");w&&(w.style.display=w.style.display==="none"?"table-row":"none",_.classList.toggle("active"))}else if(D==="event"){const w=t.querySelector(".event-info-row");w&&(w.style.display=w.style.display==="none"?"table-row":"none",_.classList.toggle("active"))}})}),t.querySelectorAll(".nav-btn").forEach(_=>{_.addEventListener("click",T=>{T.preventDefault(),T.stopPropagation();const D=_.getAttribute("data-nav"),w=n.위도,$=n.경도,O=encodeURIComponent(n.가맹점명||"가맹점");if(D==="kakao"){Dn(l.value,n.id);const Y=`kakaomap://look?p=${w},${$}`,de=`https://map.kakao.com/link/map/${O},${w},${$}`;tt(Y,de)}else if(D==="naver"){On(l.value,n.id);const Y=`nmap://place?lat=${w}&lng=${$}&name=${O}&appname=com.localpay`,de=`https://map.naver.com/p/search/${O}?c=${$},${w},15,0,0,0,dh`;tt(Y,de)}})});const Z=t.querySelector(".report-btn");Z&&Z.addEventListener("click",_=>{_.preventDefault(),_.stopPropagation(),Fn(l.value,n.id),Ze.value=n,we.value=!0});const Q=new window.kakao.maps.LatLng(n.위도,n.경도);I=new window.kakao.maps.CustomOverlay({position:Q,content:t,yAnchor:1.15,zIndex:10}),I.setMap(m),m.panTo(Q)},at=()=>{H.forEach(n=>n.setMap(null)),H.clear(),C?.clear()},Dt=n=>{if(!m)return n;
98const e=m.getBounds(),t=e.getSouthWest(),v=e.getNorthEast();return n.filter(k=>{const g=k.위도,S=k.경도;return g>=t.getLat()&&g<=v.getLat()&&S>=t.getLng()&&S<=v.getLng()})},Ot=n=>We[n]||We[14],Bt=n=>{const e=new window.kakao.maps.Marker({position:new window.kakao.maps.LatLng(n.위도,n.경도),image:Lt(Xe(n)),clickable:!0});return window.kakao.maps.event.addListener(e,"click",()=>{nt(n)}),e},st=async(n=!0)=>{if(!C||!window?.kakao?.maps||!m||Ee)return;Ee=!0,n&&(ie.value=!0,P.value="마커 준비 중...",q.value=0,await _e(),await new Promise(w=>setTimeout(w,10)));const e=m.getLevel(),t=Ot(e);let v=Dt(ne.value);if(e>3&&v.length>t){const w=Math.ceil(v.length/t);v=v.filter(($,O)=>O%w===0)}const k=new Set(v.map(w=>String(w.id))),g=m.getBounds(),S=g.getSouthWest(),N=g.getNorthEast(),ae=(N.getLat()-S.getLat())*.5,Z=(N.getLng()-S.getLng())*.5,Q=[];H.forEach((w,$)=>{if(!k.has($)){const O=w.getPosition(),Y=O.getLat(),de=O.getLng();(Y<S.getLat()-ae||Y>N.getLat()+ae||de<S.getLng()-Z||de>N.getLng()+Z)&&Q.push($)}}),Q.forEach(w=>{const $=H.get(w);$&&($.setMap(null),H.delete(w))});const _=v.filter(w=>!H.has(String(w.id)));n&&(q.value=30,P.value=`마커 ${_.length.toLocaleString()}개 추가 중...`);const T=300;for(let w=0;w<_.length;w+=T)_.slice(w,w+T).forEach(O=>{const Y=Bt(O);H.set(String(O.id),Y)}),n&&(q.value=30+Math.round(w/_.length*60)),await new Promise(O=>setTimeout(O,0));n&&(P.value="지도에 표시 중...",q.value=95),await _e();const D=Array.from(H.values());e<=ma?(C.clear(),D.forEach(w=>
98w.setMap(m))):(D.forEach(w=>w.setMap(null)),C.clear(),C.addMarkers(D)),n&&(await new Promise(w=>setTimeout(w,100)),await _e(),q.value=100,P.value="완료!",await new Promise(w=>setTimeout(w,300))),Ge.value=H.size,oe||(oe=!0),n&&(ie.value=!1),Ee=!1},Be=async(n=ne.value,e=!0)=>(at(),st(e)),Ft=n=>{const e=L.value.indexOf(n);e===-1?(L.value.push(n),Rn(l.value,n)):L.value.splice(e,1),ce()},Ut=()=>{L.value.length===ke.length?L.value=[]:L.value=ke.map(n=>n.name),ce()},Nt=()=>{pe.value.length>0&&(te.value=!0)},Pt=()=>{setTimeout(()=>{te.value=!1},200)},ce=()=>{Ae.value&&Ae.value.blur(),te.value=!1},qt=n=>{if(j.value.trim()&&Tn(l.value,j.value.trim()),j.value=n.가맹점명||"",te.value=!1,ce(),m&&n.위도&&n.경도){const e=new window.kakao.maps.LatLng(n.위도,n.경도);m.setLevel(1),m.panTo(e),setTimeout(()=>nt(n),300)}};Ne(j,n=>{te.value=n.trim().length>0&&pe.value.length>0}),Ne(ne,n=>{oe&&Be(n,!1)});const ot=(n,e)=>{if(!m||!window?.kakao?.maps)return;ge&&ge.setMap(null);const t=new window.kakao.maps.LatLng(n,e),v=document.createElement("div");v.className="user-location-marker",v.innerHTML=`
99    <div class="location-pulse"></div>
100    <div class="location-dot"></div>
101  `,ge=new window.kakao.maps.CustomOverlay({position:t,content:v,zIndex:1e4}),ge.setMap(m)},jt=()=>{if(!m)return;z.value=!0,ce(),Bn(l.value),window.FlutterLocation!==void 0?window.FlutterLocation.postMessage("REQUEST_LOCATION"):"geolocation"in navigator?navigator.geolocation.getCurrentPosition(e=>{const{latitude:t,longitude:v}=e.coords;Je.value={lat:t,lng:v};const k=new window.kakao.maps.LatLng(t,v);m.setLevel(4),m.panTo(k),ot(t,v),z.value=!1},e=>{z.value=!1;let t="위치를 가져올 수 없습니다.";switch(e.code){case e.PERMISSION_DENIED:t="위치 권한이 거부되었습니다. 브라우저 설정에서 위치 권한을 허용해주세요.";break;case e.POSITION_UNAVAILABLE:t="위치 정보를 사용할 수 없습니다.";break;case e.TIMEOUT:t="위치 요청 시간이 초과되었습니다.";break}le(t,"error"),console.error("위치 오류:",e)},{enableHighAccuracy:!0,timeout:1e4,maximumAge:0}):(z.value=!1,le("이 브라우저는 위치 서비스를 지원하지 않습니다.","error"))},it=async()=>{try{(await mn()).hasCache;let e=h.params.regionCode;if(e||(e=await je()),u.value&&!await hn(e)){M();return}const t=await gn((g,S,N)=>{P.value=N||"데이터 로드 중...",typeof g=="number"&&typeof S=="number"&&S>0&&(q.value=g)},e);if((!t||t.length===0)&&u.value){M();return}const v=t.filter(g=>g.경도&&g.위도),k=new Map;if(v.length===0){if(u.value){M();return}const g=h.params.regionCode||l.value||e||"";p.replace({name:"MapNoData",query:g?{region:String(g)}:void 0});return}he.value=v.map(g=>{const S=`${Number(g.위도).toFixed(5)}_${Number(g.경도).toFixed(5)}`;let N={...g};if(k.has(S)){const ae=k.get(S);k.set(S,ae+1);const Z=ae*(Math.PI*.45),Q=3e-5*Math.ceil(ae/2);N.위도=Number(g.위도)+Math.sin(Z)*Q,N.경도=Number(g.경도)+Math.cos(Z)*Q}else k.set(S,1);return N}),Ge.value=he.value.length}catch(n){if(console.error("데이터 로드 실패:",n),u.value){M();return}throw P.value="데이터 로드 실패",n}},Vt=async()=>{try{const n=new Date().toISOString().split("T")[0],{data:e,error:t}=await G.from("store_details").select("store_id, event, event_start_date, event_end_date").eq("region_code",l.value).not("event","is",null);if(t){console.error("이벤트 데이터 로드 오류:",t);return}const v=e.filter(g=>!g.event_start_date&&!g.event_end_date?!0:g.event_start_date&&!g.event_end_date?g.event_start_date<=n:(!g.event_start_date&&g.event_end_date||g.event_start_date<=n)&&g.event_end_date>=n),k=new Set(v.map(g=>String(g.store_id)));Le.value=k,Ie.value=k.size}catch(n){console.error("이벤트 데이터 로드 실패:",n)}},Ht=()=>{me.value=!me.value},Fe=(n,e="success",t=2500)=>{Qe.value=n,Te.value=e,le.value=!0,setTimeout(()=>{le.value=!1},t)},zt=async()=>{if(!re.value){Un(l.value),re.value=!0;try{const n=await kn(l.value);n.updated?(Fe(`데이터가 갱신되었습니다 (${n.updatedChunks}개 데이터)`,"success"),await it(),C&&await Be(ne.value,!1)):Fe("데이터가 최신상태입니다","success")}catch(n){console.error("데이터 갱신 실패:",n),Fe("갱신 확인 실패","error")}finally{re.value=!1}}};return Gt(async()=>{try{let n=h.params.regionCode;n||(n=await je()),l.value=n;const e=ve(n);if(!u.value&&(!e||e.enabled!==!0)){p.replace({name:"MapNoData",query:{region:String(n||"")}});return}if(!await Jt(n)){p.replace({name:"MapExpired",query:{region:String(n||"")}});return}e&&e.center&&(r.value=e.center),Ln(n),ie.value=!0,P.value="지도를 초기화하는 중입니다...",q.value=0,await Rt(),P.value="데이터를 불러오는 중입니다...",q.value=20,await it(),Vt(),C&&await Be(ne.value,!0),oe=!0}catch(n){console.error(n),u.value&&(f.value=!0),ie.value=!1,oe=!0}window.addEventListener("message",n=>{if(n.data&&n.data.type==="FLUTTER_LOCATION"){const{latitude:e,longitude:t}=n.data;if(Je.value={lat:e,lng:t}
101,m){const v=new window.kakao.maps.LatLng(e,t);m.setLevel(4),m.panTo(v),ot(e,t)}z.value=!1}else n.data&&n.data.type==="FLUTTER_LOCATION_ERROR"&&(z.value=!1,le("위치 권한이 거부되었습니다. 앱 설정에서 위치 권한을 허용해주세요.","error"))})}),Zt(()=>{I&&(I.setMap(null),I=null),C&&(C.clear(),C=null),at(),m=null}),(n,e)=>(E(),A("div",Nn,[d("button",{class:K(["sidebar-toggle",{"sidebar-open":ee.value}]),onClick:e[0]||(e[0]=t=>ee.value=!ee.value)},x(ee.value?"◀":"▶"),3),d("aside",{class:K(["sidebar",{"sidebar-collapsed":!ee.value}])},[d("div",Pn,[e[6]||(e[6]=d("h3",null,"🏷️ 업종 필터",-1)),d("button",{class:"toggle-all-btn",onClick:Ut},x(L.value.length===ue(ke).length?"전체 해제":"전체 선택"),1)]),d("div",qn,[L.value.length===0?(E(),A("span",jn,"전체 표시 중")):(E(),A("span",Vn,x(L.value.length)+"개 카테고리 선택",1)),d("span",Hn,"("+x(ne.value.length.toLocaleString())+"개)",1)]),d("div",zn,[(E(!0),A(rt,null,lt(ue(ke),t=>(E(),A("div",{key:t.name,class:K(["category-item",{"category-selected":L.value.includes(t.name),"category-dimmed":L.value.length>0&&!L.value.includes(t.name)}]),onClick:v=>Ft(t.name)},[d("span",Wn,x(t.icon),1),d("span",Gn,x(t.name),1),d("span",{class:"category-indicator",style:ct({backgroundColor:t.color})},null,4)],10,Kn))),128)),d("button",{class:"refresh-data-btn",onClick:zt,disabled:re.value},[d("span",{class:K(["material-icons refresh-icon",{spinning:re.value}])},"sync",2),d("span",Zn,x(re.value?"확인 중...":"데이터갱신"),1)],8,Jn)])],2),d("div",{class:K(["map-container",{"with-sidebar":ee.value}])},[d("div",{ref_key:"mapContainer",ref:c,class:"kakao-map"},null,512)],2),ie.value?(E(),A("div",Qn,[d("div",Yn,[e[7]||(e[7]=qe('<div class="bouncing-pin-wrap" data-v-69bdbb79><svg class="bouncing-pin" viewBox="0 0 24 24" width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg" data-v-69bdbb79><path d="M12 2C8.13 2 5 5.13 5 9C5 14.25 12 22 12 22C12 22 19 14.25 19 9C19 5.13 15.87 2 12 2ZM12 11.5C10.62 11.5 9.5 10.38 9.5 9C9.5 7.62 10.62 6.5 12 6.5C13.38 6.5 14.5 7.62 14.5 9C14.5 10.38 13.38 11.5 12 11.5Z" fill="url(#pin-grad)" data-v-69bdbb79></path><defs data-v-69bdbb79><linearGradient id="pin-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse" data-v-69bdbb79><stop stop-color="#818cf8" data-v-69bdbb79></stop><stop offset="1" stop-color="#4f46e5" data-v-69bdbb79></stop></linearGradient></defs></svg><div class="pin-shadow" data-v-69bdbb79></div></div><h4 class="loading-title" data-v-69bdbb79>가맹점 지도 준비 중</h4>',2)),d("div",Xn,x(P.value),1),d("div",ea,[d("div",{class:"progress-bar-fill",style:ct({width:q.value+"%"})},null,4)]),d("div",ta,x(q.value)+"%",1)])])):W("",!0),xt.value?(E(),A("div",na,[...e[8]||(e[8]=[d("div",{class:"cluster-loading-content"},[d("div",{class:"dot-flashing"}),d("span",{class:"cluster-loading-text"},"지도 렌더링 중...")],-1)])])):W("",!0),d("div",{class:K(["marker-count",{"with-sidebar":ee.value}])},[d("span",aa," 📍 "+x(ne.value.length.toLocaleString())+"개 "+x(ue(ve)(l.value)?.currencyName?ue(ve)(l.value).currencyName.endsWith("가맹점")?ue(ve)(l.value).currencyName:ue(ve)(l.value).currencyName+" 가맹점":"가맹점"),1),L.value.length>0?(E(),A("span",sa,"필터 적용")):W("",!0),Ie.value>0?(E(),A("button",{key:1,class:K(["event-filter-btn",{active:me.value}]),onClick:Ht},[e[9]||(e[9]=Qt(" 🎉 이벤트중 ",-1)),d("span",oa,x(Ie.value),1)],2)):W("",!0)],2),d("div",ia,[d("div",ra,[e[10]||(e[10]=d("span",{class:"search-icon"},"🔍",-1)),Pe(d("input",{ref_key:"searchInputRef",ref:Ae,type:"text","onUpdate:modelValue":e[1]||(e[1]=t=>j.value=t),placeholder:"가맹점명 또는 주소로 검색...",class:"search-input",onFocus:Nt,onBlur:Pt},null,544),[[mt,j.value]]),j.value?(E(),A("button",{key:0,class:"search-clear",onClick:e[2]||(e[2]=t=>j.value="")},"✕")):W("",!0)]),d("button",{class:K(["my-location-btn",{locating:z.value}]),onClick:jt,disabled:z.value},[e[11]||(e[11]=d("span",{class:"location-icon"},"📍",-1)),d("span",ca,x(z.value?"위치 확인 중...":"내 위치로 이동"),1)],10,la),te.value&&pe.value.length>0?(E(),A("div",da,[(E(!0),A(rt,null,lt(pe.value,(t,v)=>(E(),A("div",{key:v,class:"search-result-item",onMousedown:xe(k=>
101qt(t),["prevent"])},[d("div",fa,x(t.가맹점명),1),d("div",pa,x(t.사업장주소),1),d("div",va,x(t.카테고리),1)],40,ua))),128))])):te.value&&j.value&&pe.value.length===0?(E(),A("div",ha," 검색 결과가 없습니다. ")):W("",!0)]),dt(En,{isOpen:we.value,storeData:Ze.value||{},onClose:e[3]||(e[3]=t=>we.value=!1),onSubmitted:e[4]||(e[4]=t=>we.value=!1)},null,8,["isOpen","storeData"]),f.value?(E(),A("div",{key:2,class:"fixed inset-0 bg-slate-900/40 backdrop-blur-sm flex items-center justify-center z-[70] p-4",onClick:e[5]||(e[5]=xe(t=>f.value=!1,["self"]))},[d("div",{class:"bg-white rounded-2xl w-full max-w-sm shadow-xl border border-slate-100 overflow-hidden"},[d("div",{class:"p-6 text-center"},[e[12]||(e[12]=qe('<div class="w-12 h-12 mx-auto bg-amber-100 rounded-full flex items-center justify-center mb-3 text-amber-600 border border-amber-200" data-v-69bdbb79><i class="material-icons text-2xl" data-v-69bdbb79>warning_amber</i></div><h3 class="text-sm font-bold text-slate-800 mb-2 leading-relaxed" data-v-69bdbb79>가맹점 데이터 없음</h3><p class="text-xs text-slate-600 mb-6 bg-slate-50 p-3 rounded-lg border border-slate-100 font-semibold leading-relaxed" data-v-69bdbb79> 현재 불러올 가맹점 데이터가 없습니다.<br data-v-69bdbb79><strong class="text-slate-800" data-v-69bdbb79>[원본자료관리 → 자료관리]</strong> 메뉴에서 원본 엑셀 데이터를 먼저 업로드해 주세요. </p>',3)),d("button",{type:"button",class:"w-full py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-xl text-xs transition cursor-pointer shadow-lg shadow-indigo-500/10",onClick:b}," 확인 ")])])])):W("",!0),dt(Xt,{name:"toast-fade"},{default:Yt(()=>[le.value?(E(),A("div",ga,[d("div",{class:K(["toast-message",[`toast-${Te.value}`]])},[d("span",wa,x(Te.value==="success"?"check_circle":"error"),1),d("span",null,x(Qe.value),1)],2)])):W("",!0)]),_:1})]))}},ba=wt(ka,[["__scopeId","data-v-69bdbb79"]]),Ca=Object.freeze(Object.defineProperty({__proto__:null,default:ba},Symbol.toStringTag,{value:"Module"}));export{ba as M,xa as a,$a as b,gt as c,Ca as d,Sa as f,hn as h,Ma as s};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.