pwaLUPMIS2/dist/assets/index-2WHoRhxp.js
2026-03-04 12:59:40 +01:00

394 lines
106 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{M as Ke}from"./bootstrap-D1-uvFxm.js";import{_ as We,e as Q,d as O,W as Ae,V as F,X as q,M as ut,Y as dt,Z as N,$ as pt,c as H,F as _,S as A,o as ft,O as ht,U as K,a0 as re,a1 as me,a2 as z,k as gt,T as j,a3 as V,a4 as Me,f as be,n as Ie,a5 as ce,K as G,j as yt,L as mt,u as de,a6 as bt}from"./openlayers-D2I-bVN2.js";import{o as wt,a as Et,b as vt,c as Tt,d as xt,e as we,f as Lt,g as J,h as St,i as pe}from"./ol-ext-DytxBANR.js";(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const n of s.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&r(n)}).observe(document,{childList:!0,subtree:!0});function t(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=t(o);fetch(o.href,s)}})();const Oe="function",W="64e10b34-2bf7-4616-9668-f99de5aa046e",kt="get",Pt="has",Ct="set",{isArray:te}=Array;let{SharedArrayBuffer:ae,window:_t}=globalThis,{notify:He,wait:Ve,waitAsync:ne}=Atomics,Je=null;ne||(ne=a=>({value:new Promise(e=>{let t=new Worker("data:application/javascript,onmessage%3D(%7Bdata%3Ab%7D)%3D%3E(Atomics.wait(b%2C0)%2CpostMessage(0))");t.onmessage=e,t.postMessage(a)})}));try{new ae(4)}catch{ae=ArrayBuffer;const e=new WeakMap;if(_t){const t=new Map,{prototype:{postMessage:r}}=Worker,o=s=>{const n=s.data?.[W];if(!te(n)){s.stopImmediatePropagation();const{id:i,sb:l}=n;t.get(i)(l)}};Je=function(s,...n){const i=s?.[W];if(te(i)){const[l,c]=i;e.set(c,l),this.addEventListener("message",o)}return r.call(this,s,...n)},ne=s=>({value:new Promise(n=>{t.set(e.get(s),n)}).then(n=>{t.delete(e.get(s)),e.delete(s);for(let i=0;i<n.length;i++)s[i]=n[i];return"ok"})})}else{const t=(r,o)=>({[W]:{id:r,sb:o}});He=r=>{postMessage(t(e.get(r),r))},addEventListener("message",r=>{const o=r.data?.[W];if(te(o)){const[s,n]=o;e.set(n,s)}})}}/*! (c) Andrea Giammarchi - ISC */const{Int32Array:Ee,Map:De,Uint16Array:ve}=globalThis,{BYTES_PER_ELEMENT:Fe}=Ee,{BYTES_PER_ELEMENT:At}=ve,Mt=(a,e,t)=>{for(;Ve(a,0,0,e)==="timed-out";)t()},Te=new WeakSet,fe=new WeakMap,It={value:{then:a=>a()}};let Ot=0;const Se=(a,{parse:e=JSON.parse,stringify:t=JSON.stringify,transform:r,interrupt:o}=JSON)=>{if(!fe.has(a)){const s=Je||a.postMessage,n=(d,...f)=>s.call(a,{[W]:f},{transfer:d}),i=typeof o===Oe?o:o?.handler,l=o?.delay||42,c=new TextDecoder("utf-16"),u=(d,f)=>d?ne(f,0):(i?Mt(f,l,i):Ve(f,0),It);let p=!1;fe.set(a,new Proxy(new De,{[Pt]:(d,f)=>typeof f=="string"&&!f.startsWith("_"),[kt]:(d,f)=>f==="then"?null:((...y)=>{const b=Ot++;let x=new Ee(new ae(Fe*2)),E=[];Te.has(y.at(-1)||E)&&Te.delete(E=y.pop()),n(E,b,x,f,r?y.map(r):y);const w=a!==globalThis;let L=0;return p&&w&&(L=setTimeout(console.warn,1e3,`💀🔒 - Possible deadlock if proxy.${f}(...args) is awaited`)),u(w,x).value.then(()=>{clearTimeout(L);const v=x[1];if(!v)return;const S=At*v;return x=new Ee(new ae(S+S%Fe)),n([],b,x),u(w,x).value.then(()=>e(c.decode(new ve(x.buffer).slice(0,v))))})}),[Ct](d,f,y){const b=typeof y;if(b!==Oe)throw new Error(`Unable to assign ${f} as ${b}`);if(!d.size){const x=new De;a.addEventListener("message",async E=>{const w=E.data?.[W];if(te(w)){E.stopImmediatePropagation();const[L,v,...S]=w;let m;if(S.length){const[T,P]=S;if(d.has(T)){p=!0;try{const k=await d.get(T)(...P);if(k!==void 0){const _e=t(r?r(k):k);x.set(L,_e),v[1]=_e.length}}catch(k){m=k}finally{p=!1}}else m=new Error(`Unsupported action: ${T}`);v[0]=1}else{const T=x.get(L);x.delete(L);for(let P=new ve(v.buffer),k=0;k<T.length;k++)P[k]=T.charCodeAt(k)}if(He(v,0),m)throw m}})}return!!d.set(f,y)}}))}return fe.get(a)};Se.transfer=(...a)=>(Te.add(a),a);function Re(){let a,e;return{lock:async()=>{for(;a;)await a;a=new Promise(o=>{e=o})},unlock:async()=>{const o=e;a=void 0,e=void 0,o?.()}}}async function Qe(a,e){let t;if(a instanceof Blob?t=a.stream():t=a,t instanceof ReadableStream&&e){const o=t.getReader();switch(e){case"callback":return async()=>(await o.read()).value;case"buffer":const s=[];let n=!1;for(;!n;){const u=await o.read();u.value&&s.push(u.value),n=u.done}const i=s.reduce((u,p)=>u+p.length,0),l=new Uint8Array(i);let c=0;return s.forEach(u=>{l.set(u,c),c+=u.length}),l.buffer}}else return t}class se{constructor(e){Object.defineProperty(this,"sqlite3InitModule",{enumerable:!0,configurable:!0,writable:!0,value:e}),Object.defineProperty(this,"sqlite3",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"db",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"pointers",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"writeCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:"memory"})}async init(e){const{databasePath:t}=e,r=this.getFlags(e);if(!this.sqlite3InitModule){const{default:o}=await We(async()=>{const{default:s}=await import("./index-DTMgZTfd.js");return{default:s}},[]);this.sqlite3InitModule=o}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.DB(t,r),this.config=e,this.initWriteHook()}onWrite(e){return this.writeCallbacks.add(e),()=>{this.writeCallbacks.delete(e)}}async exec(e){if(!this.db)throw new Error("Driver not initialized");return this.execOnDb(this.db,e)}async execBatch(e){if(!this.db)throw new Error("Driver not initialized");const t=[];return this.db.transaction(r=>{const o=new Map;try{for(let s of e){let n=o.get(s.sql);if(!n){const c=r.prepare(s.sql);o.set(s.sql,c),n=c}s.params?.length&&n.bind(s.params);let i=[],l=[];for(;n.step();)i=n.getColumnNames([]),l.push(n.get([]));t.push({columns:i,rows:l}),n.reset()}}finally{o.forEach(s=>{s.finalize()})}}),t}async isDatabasePersisted(){return!1}async getDatabaseSizeBytes(){const t=(await this.exec({sql:`SELECT page_count * page_size AS size
FROM pragma_page_count(), pragma_page_size()`,method:"get"}))?.rows?.[0];if(typeof t!="number")throw new Error("Failed to query database size");return t}async createFunction(e){if(!this.db)throw new Error("Driver not initialized");switch(e.type){case"callback":case"scalar":this.db.createFunction({name:e.name,xFunc:(t,...r)=>e.func(...r),arity:-1});break;case"aggregate":this.db.createFunction({name:e.name,xStep:(t,...r)=>e.func.step(...r),xFinal:(t,...r)=>e.func.final(...r),arity:-1});break}}async import(e){if(!this.sqlite3||!this.db||!this.config)throw new Error("Driver not initialized");const t=await Qe(e,"buffer"),r=this.sqlite3.wasm.allocFromTypedArray(t);this.pointers.push(r);const o=this.sqlite3.capi.sqlite3_deserialize(this.db,"main",r,t.byteLength,t.byteLength,this.config.readOnly?this.sqlite3.capi.SQLITE_DESERIALIZE_READONLY:this.sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);this.db.checkRc(o)}async export(){if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");return{name:"database.sqlite3",data:this.sqlite3.capi.sqlite3_js_db_export(this.db)}}async clear(){}async destroy(){this.closeDb(),this.pointers.forEach(e=>this.sqlite3?.wasm.dealloc(e)),this.pointers=[],this.writeCallbacks.clear()}getFlags(e){const{readOnly:t,verbose:r}=e;return[t===!0?"r":"cw",r===!0?"t":""].join("")}execOnDb(e,t){const r={rows:[],columns:[]},o=e.exec({sql:t.sql,bind:t.params,returnValue:"resultRows",rowMode:"array",columnNames:r.columns});switch(t.method){case"run":break;case"get":r.rows=o[0]??[];break;case"all":default:r.rows=o;break}return r}initWriteHook(){if(!this.config?.reactive)return;if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");const e={[this.sqlite3.capi.SQLITE_INSERT]:"insert",[this.sqlite3.capi.SQLITE_UPDATE]:"update",[this.sqlite3.capi.SQLITE_DELETE]:"delete"};this.sqlite3.capi.sqlite3_update_hook(this.db,(t,r,o,s,n)=>{this.writeCallbacks.forEach(i=>{i({table:s,rowid:n,operation:e[r]})})},0)}closeDb(){this.db&&(this.db.close(),this.db=void 0)}}function Dt(a,e,t){let r,o,s,n,i,l,c=0,u=!1,p=!1,d=!0;if(typeof a!="function")throw new TypeError("Expected a function");e=Number(e)||0,typeof t=="object"&&t!==null&&(u=!!t.leading,p="maxWait"in t,s=p?Math.max(Number(t.maxWait)||0,e):0,d="trailing"in t?!!t.trailing:d);function f(m){const T=r,P=o;return r=o=void 0,c=m,n=a.apply(P,T),n}function y(m){return c=m,i=setTimeout(E,e),u?f(m):n}function b(m){const T=m-(l??0),P=m-c,k=e-T;return p?Math.min(k,s-P):k}function x(m){const T=m-(l??0),P=m-c;return l===void 0||T>=e||T<0||p&&P>=s}function E(){const m=Date.now();if(x(m))return w(m);i=setTimeout(E,b(m))}function w(m){return i=void 0,d&&r?f(m):(r=o=void 0,n)}function L(){i!==void 0&&clearTimeout(i),c=0,r=l=o=i=void 0}function v(){return i===void 0?n:w(Date.now())}function S(){const m=Date.now(),T=x(m);if(r=arguments,o=this,l=m,T){if(i===void 0)return y(l);if(p)return i=setTimeout(E,e),f(l)}return i===void 0&&(i=setTimeout(E,e)),n}return S.cancel=L,S.flush=v,S}function oe(){return crypto.randomUUID()}function Ye(a,e){switch(a){case"session":case":sessionStorage:":let t=sessionStorage._sqlocal_session_key;return t||(t=oe(),sessionStorage._sqlocal_session_key=t),`session:${t}`;case"local":case":localStorage:":return"local";case":memory:":return`memory:${e}`;default:return`path:${a}`}}class Z{constructor(e){Object.defineProperty(this,"driver",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:{}}),Object.defineProperty(this,"userFunctions",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"initMutex",{enumerable:!0,configurable:!0,writable:!0,value:Re()}),Object.defineProperty(this,"transactionMutex",{enumerable:!0,configurable:!0,writable:!0,value:Re()}),Object.defineProperty(this,"transactionKey",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"dirtyTables",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"onmessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"init",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{if(!(!this.config.databasePath||!this.config.clientKey)){await this.initMutex.lock();try{try{await this.driver.init(this.config)}catch{console.warn(`Persistence failed, so ${this.config.databasePath} will not be saved. For origin private file system persistence, make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dev/guide/setup#cross-origin-isolation).`),this.config.databasePath=":memory:",this.driver=new se,await this.driver.init(this.config)}const s=Ye(this.config.databasePath,this.config.clientKey);this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${s})`),this.reinitChannel.onmessage=n=>{const i=n.data;if(this.config.clientKey!==i.clientKey)switch(i.type){case"reinit":this.init(i.reason);break;case"close":this.driver.destroy();break}},this.config.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${s})`),this.driver.onWrite(async n=>{this.dirtyTables.add(n.table),await this.transactionMutex.lock(),this.emitEffectsDebounced(),await this.transactionMutex.unlock()})),await Promise.all(Array.from(this.userFunctions.values()).map(n=>this.initUserFunction(n))),await this.execInitStatements(),this.emitMessage({type:"event",event:"connect",reason:o})}catch(s){this.emitMessage({type:"error",error:s,queryKey:null}),await this.destroy()}finally{await this.initMutex.unlock()}}}}),Object.defineProperty(this,"postMessage",{enumerable:!0,configurable:!0,writable:!0,value:async(o,s)=>{const n=o instanceof MessageEvent?o.data:o;switch(await this.initMutex.lock(),n.type){case"config":this.editConfig(n);break;case"query":case"batch":case"transaction":this.exec(n);break;case"function":this.createUserFunction(n);break;case"getinfo":this.getDatabaseInfo(n);break;case"import":this.importDb(n);break;case"export":this.exportDb(n);break;case"delete":this.deleteDb(n);break;case"destroy":this.destroy(n);break}await this.initMutex.unlock()}}),Object.defineProperty(this,"emitMessage",{enumerable:!0,configurable:!0,writable:!0,value:(o,s=[])=>{this.onmessage&&this.onmessage(o,s)}}),Object.defineProperty(this,"emitEffects",{enumerable:!0,configurable:!0,writable:!0,value:()=>{!this.effectsChannel||this.dirtyTables.size===0||(this.effectsChannel.postMessage({type:"effects",tables:[...this.dirtyTables]}),this.dirtyTables.clear())}}),Object.defineProperty(this,"emitEffectsDebounced",{enumerable:!0,configurable:!0,writable:!0,value:Dt(()=>this.emitEffects(),32,{maxWait:180})}),Object.defineProperty(this,"editConfig",{enumerable:!0,configurable:!0,writable:!0,value:o=>{this.config=o.config,this.init("initial")}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{try{const s={type:"data",queryKey:o.queryKey,data:[]};switch(o.type){case"query":const n=this.transactionKey!==null&&this.transactionKey===o.transactionKey;try{n||await this.transactionMutex.lock();const i=await this.driver.exec(o);s.data.push(i)}finally{n||await this.transactionMutex.unlock()}break;case"batch":try{await this.transactionMutex.lock();const i=await this.driver.execBatch(o.statements);s.data.push(...i)}finally{await this.transactionMutex.unlock()}break;case"transaction":if(o.action==="begin"&&(await this.transactionMutex.lock(),this.transactionKey=o.transactionKey,await this.driver.exec({sql:"BEGIN"})),(o.action==="commit"||o.action==="rollback")&&this.transactionKey!==null&&this.transactionKey===o.transactionKey){const i=o.action==="commit"?"COMMIT":"ROLLBACK";await this.driver.exec({sql:i}),this.transactionKey=null,await this.transactionMutex.unlock()}break}this.emitMessage(s)}catch(s){this.emitMessage({type:"error",error:s,queryKey:o.queryKey})}}}),Object.defineProperty(this,"execInitStatements",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{if(this.config.onInitStatements)for(let o of this.config.onInitStatements)await this.driver.exec(o)}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{try{this.emitMessage({type:"info",queryKey:o.queryKey,info:{databasePath:this.config.databasePath,storageType:this.driver.storageType,databaseSizeBytes:await this.driver.getDatabaseSizeBytes(),persisted:await this.driver.isDatabasePersisted()}})}catch(s){this.emitMessage({type:"error",queryKey:o.queryKey,error:s})}}}),Object.defineProperty(this,"createUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{functionName:s,functionType:n,queryKey:i}=o;let l;if(this.userFunctions.has(s)){this.emitMessage({type:"error",error:new Error(`A user-defined function with the name "${s}" has already been created for this SQLocal instance.`),queryKey:i});return}switch(n){case"callback":l={type:n,name:s,func:(...c)=>{this.emitMessage({type:"callback",name:s,args:c})}};break;case"scalar":l={type:n,name:s,func:this.proxy[`_sqlocal_func_${s}`]};break;case"aggregate":l={type:n,name:s,func:{step:this.proxy[`_sqlocal_func_${s}_step`],final:this.proxy[`_sqlocal_func_${s}_final`]}};break}try{await this.initUserFunction(l),this.emitMessage({type:"success",queryKey:i})}catch(c){this.emitMessage({type:"error",error:c,queryKey:i})}}}),Object.defineProperty(this,"initUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{await this.driver.createFunction(o),this.userFunctions.set(o.name,o)}}),Object.defineProperty(this,"importDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:s,database:n}=o;let i=!1;try{await this.driver.import(n),this.driver.storageType==="memory"&&await this.execInitStatements()}catch(l){this.emitMessage({type:"error",error:l,queryKey:s}),i=!0}finally{this.driver.storageType!=="memory"&&await this.init("overwrite")}i||this.emitMessage({type:"success",queryKey:s})}}),Object.defineProperty(this,"exportDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:s}=o;try{const{name:n,data:i}=await this.driver.export();this.emitMessage({type:"buffer",queryKey:s,bufferName:n,buffer:i},[i])}catch(n){this.emitMessage({type:"error",error:n,queryKey:s})}}}),Object.defineProperty(this,"deleteDb",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{const{queryKey:s}=o;let n=!1;try{await this.driver.clear()}catch(i){this.emitMessage({type:"error",error:i,queryKey:s}),n=!0}finally{await this.init("delete")}n||this.emitMessage({type:"success",queryKey:s})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async o=>{await this.driver.exec({sql:"PRAGMA optimize"}),await this.driver.destroy(),this.effectsChannel&&(this.emitEffectsDebounced.flush(),this.effectsChannel.close(),this.effectsChannel=void 0),this.reinitChannel&&(this.reinitChannel.close(),this.reinitChannel=void 0),o&&this.emitMessage({type:"success",queryKey:o.queryKey})}});const r=typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope?Se(globalThis):globalThis;this.proxy=r,this.driver=e}}function ie(a,...e){return{sql:a.join("?"),params:e}}function Ft(a){return!a.some(e=>!Array.isArray(e))}function he(a,e){let t;return Ft(a)?t=a:t=[a],t.map(r=>{const o={};return e.forEach((s,n)=>{o[s]=r[n]}),o})}function Rt(a){return typeof a=="object"&&a!==null&&"getSQL"in a&&typeof a.getSQL=="function"}function Nt(a){return typeof a=="object"&&a!==null&&"sql"in a&&typeof a.sql=="string"&&"params"in a}function Ne(a){if(typeof a=="function"&&(a=a(ie)),Rt(a))try{if(!("toSQL"in a&&typeof a.toSQL=="function"))throw 1;const r=a.toSQL();if(!Nt(r))throw 2;const o="all"in a&&typeof a.all=="function"?a.all:void 0;return{...r,exec:o?()=>o():void 0}}catch{throw new Error("The passed statement could not be parsed.")}const e=a.sql;let t=[];return"params"in a?t=a.params:"parameters"in a&&(t=a.parameters),{sql:e,params:t}}function je(a,e){let t;return typeof a=="string"?t={sql:a,params:e}:t=ie(a,...e),t}async function ee(a,e,t,r){return!e&&"locks"in navigator?navigator.locks.request(`_sqlocal_mutation_(${t.databasePath})`,{mode:a},r):r()}class Be extends se{constructor(e,t){super(t),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:e})}async init(e){const t=this.getFlags(e);if(e.readOnly)throw new Error(`SQLite storage type "${this.storageType}" does not support read-only mode.`);if(!this.sqlite3InitModule){const{default:r}=await We(async()=>{const{default:o}=await import("./index-DTMgZTfd.js");return{default:o}},[]);this.sqlite3InitModule=r}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.JsStorageDb({filename:this.storageType,flags:t}),this.config=e,this.initWriteHook()}async isDatabasePersisted(){return navigator.storage?.persisted()}async getDatabaseSizeBytes(){if(!this.db)throw new Error("Driver not initialized");return this.db.storageSize()}async import(e){const t=new se;await t.init({}),await t.import(e),await this.clear(),await t.exec({sql:`VACUUM INTO 'file:${this.storageType}?vfs=kvvfs'`}),await t.destroy()}async clear(){if(!this.db)throw new Error("Driver not initialized");this.db.clearStorage()}async destroy(){this.closeDb(),this.writeCallbacks.clear()}}var Xe,Ze;class jt{constructor(e){Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"clientKey",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processor",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"isDestroyed",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"bypassMutationLock",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"transactionQueryKeyQueue",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"userCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"queriesInProgress",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processMessageEvent",{enumerable:!0,configurable:!0,writable:!0,value:c=>{const u=c instanceof MessageEvent?c.data:c,p=this.queriesInProgress;switch(u.type){case"success":case"data":case"buffer":case"info":case"error":if(u.queryKey&&p.has(u.queryKey)){const[f,y]=p.get(u.queryKey);u.type==="error"?y(u.error):f(u),p.delete(u.queryKey)}else if(u.type==="error")throw u.error;break;case"callback":const d=this.userCallbacks.get(u.name);d&&d(...u.args??[]);break;case"event":this.config.onConnect?.(u.reason);break}}}),Object.defineProperty(this,"createQuery",{enumerable:!0,configurable:!0,writable:!0,value:async c=>ee("shared",this.bypassMutationLock||c.type==="import"||c.type==="delete",this.config,async()=>{if(this.isDestroyed===!0)throw new Error("This SQLocal client has been destroyed. You will need to initialize a new client in order to make further queries.");const u=oe();switch(c.type){case"import":this.processor.postMessage({...c,queryKey:u},[c.database]);break;default:this.processor.postMessage({...c,queryKey:u});break}return new Promise((p,d)=>{this.queriesInProgress.set(u,[p,d])})})}),Object.defineProperty(this,"broadcast",{enumerable:!0,configurable:!0,writable:!0,value:c=>{this.reinitChannel.postMessage(c)}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async(c,u,p="all",d)=>{const f=await this.createQuery({type:"query",transactionKey:d,sql:c,params:u,method:p}),y={rows:[],columns:[]};return f.type==="data"&&(y.rows=f.data[0]?.rows??[],y.columns=f.data[0]?.columns??[]),y}}),Object.defineProperty(this,"execBatch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const u=await this.createQuery({type:"batch",statements:c}),p=new Array(c.length).fill({rows:[],columns:[]});return u.type==="data"&&u.data.forEach((d,f)=>{p[f]=d}),p}}),Object.defineProperty(this,"sql",{enumerable:!0,configurable:!0,writable:!0,value:async(c,...u)=>{const p=je(c,u),{rows:d,columns:f}=await this.exec(p.sql,p.params,"all");return he(d,f)}}),Object.defineProperty(this,"batch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const u=c(ie);return(await this.execBatch(u)).map(({rows:d,columns:f})=>he(d,f))}}),Object.defineProperty(this,"beginTransaction",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=oe();await this.createQuery({type:"transaction",transactionKey:c,action:"begin"});const u=async y=>{const b=Ne(y);if(b.exec)return this.transactionQueryKeyQueue.push(c),b.exec();const{rows:x,columns:E}=await this.exec(b.sql,b.params,"all",c);return he(x,E)};return{query:u,sql:async(y,...b)=>{const x=je(y,b);return await u(x)},commit:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"commit"})},rollback:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"rollback"})}}}}),Object.defineProperty(this,"transaction",{enumerable:!0,configurable:!0,writable:!0,value:async c=>ee("exclusive",!1,this.config,async()=>{let u;this.bypassMutationLock=!0;try{u=await this.beginTransaction();const p=await c({sql:u.sql,query:u.query});return await u.commit(),p}catch(p){throw await u?.rollback(),p}finally{this.bypassMutationLock=!1}})}),Object.defineProperty(this,"reactiveQuery",{enumerable:!0,configurable:!0,writable:!0,value:c=>{let u=[],p=!1,d=!1,f=0;const y=Ne(c),b=new Set,x=new Set,E=new Set,w=async()=>{try{const v=++f;if(b.size===0){const m=await this.sql("SELECT name, wr FROM tables_used(?) WHERE type = 'table'",y.sql),T=new Set,P=new Set;if(m.forEach(k=>{typeof k.name=="string"&&(k.wr?P.add(k.name):T.add(k.name))}),T.size===0)throw new Error("The passed SQL does not read any tables.");if(Array.from(P).some(k=>T.has(k)))throw new Error("The passed SQL would mutate one or more of the tables that it reads. Doing this in a reactive query would create an infinite loop.");T.forEach(k=>b.add(k))}const S=y.exec?await y.exec():await this.sql(y.sql,...y.params);v===f&&(u=S,p=!0,x.forEach(m=>m(u)))}catch(v){E.forEach(S=>{S(v instanceof Error?v:new Error(String(v)))})}},L=v=>{v.data.tables.some(S=>b.has(S))&&w()};return{get value(){return u},subscribe:(v,S)=>{if(!this.effectsChannel)throw new Error('This SQLocal instance is not configured for reactive queries. Set the "reactive" option to enable them.');return S||(S=m=>{throw m}),x.add(v),E.add(S),d?p&&v(u):(this.effectsChannel.addEventListener("message",L),d=!0,w()),{unsubscribe:()=>{x.delete(v),E.delete(S),x.size===0&&(this.effectsChannel?.removeEventListener("message",L),d=!1)}}}}}}),Object.defineProperty(this,"createCallbackFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,u)=>{await this.createQuery({type:"function",functionName:c,functionType:"callback"}),this.userCallbacks.set(c,u)}}),Object.defineProperty(this,"createScalarFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,u)=>{const p=`_sqlocal_func_${c}`,d=()=>{this.proxy[p]=u};this.proxy===globalThis&&d(),await this.createQuery({type:"function",functionName:c,functionType:"scalar"}),this.proxy!==globalThis&&d()}}),Object.defineProperty(this,"createAggregateFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,u)=>{const p=`_sqlocal_func_${c}`,d=()=>{this.proxy[`${p}_step`]=u.step,this.proxy[`${p}_final`]=u.final};this.proxy===globalThis&&d(),await this.createQuery({type:"function",functionName:c,functionType:"aggregate"}),this.proxy!==globalThis&&d()}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"getinfo"});if(c.type==="info")return c.info;throw new Error("The database failed to return valid information.")}}),Object.defineProperty(this,"getDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"export"});if(c.type==="buffer")return new File([c.buffer],c.bufferName,{type:"application/x-sqlite3"});throw new Error("The database failed to export.")}}),Object.defineProperty(this,"overwriteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async(c,u)=>{await ee("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey});const p=await Qe(c,"buffer");await this.createQuery({type:"import",database:p}),typeof u=="function"&&(this.bypassMutationLock=!0,await u()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"overwrite"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"deleteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{await ee("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey}),await this.createQuery({type:"delete"}),typeof c=="function"&&(this.bypassMutationLock=!0,await c()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"delete"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.createQuery({type:"destroy"}),typeof globalThis.Worker<"u"&&this.processor instanceof Worker&&(this.processor.removeEventListener("message",this.processMessageEvent),this.processor.terminate()),this.queriesInProgress.clear(),this.userCallbacks.clear(),this.reinitChannel.close(),this.effectsChannel?.close(),this.isDestroyed=!0}}),Object.defineProperty(this,Xe,{enumerable:!0,configurable:!0,writable:!0,value:()=>{this.destroy()}}),Object.defineProperty(this,Ze,{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.destroy()}});const t=typeof e=="string"?{databasePath:e}:e,{onInit:r,onConnect:o,processor:s,...n}=t,{databasePath:i}=n;this.config=t,this.clientKey=oe();const l=Ye(i,this.clientKey);if(this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${l})`),n.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${l})`)),typeof s<"u")this.processor=s;else if(i==="local"||i===":localStorage:"){const c=new Be("local");this.processor=new Z(c)}else if(i==="session"||i===":sessionStorage:"){const c=new Be("session");this.processor=new Z(c)}else if(typeof globalThis.Worker<"u"&&i!==":memory:")this.processor=new Worker(new URL("/assets/worker-CuIBOSaM.js",import.meta.url),{type:"module"});else{const c=new se;this.processor=new Z(c)}this.processor instanceof Z?(this.processor.onmessage=c=>this.processMessageEvent(c),this.proxy=globalThis):(this.processor.addEventListener("message",this.processMessageEvent),this.proxy=Se(this.processor)),this.processor.postMessage({type:"config",config:{...n,clientKey:this.clientKey,onInitStatements:r?.(ie)??[]}})}}Xe=Symbol.dispose,Ze=Symbol.asyncDispose;const ke="lupmis2.db",Bt="lupmis-db-sync",et=new jt(ke),{sql:h}=et;console.log("[Database] SQLocal instance created for:",ke);const tt=new BroadcastChannel(Bt);let ot=!1,rt,at;new Promise((a,e)=>{rt=a,at=e});const le=new Set;function $t(a){return le.add(a),()=>le.delete(a)}tt.onmessage=a=>{const{type:e,payload:t}=a.data;if(e==="DB_CHANGE")for(const r of le)try{r(t)}catch(o){console.error("[Database] Change listener error:",o)}};function Pe(a,e,t=null){tt.postMessage({type:"DB_CHANGE",payload:{table:a,action:e,id:t,timestamp:Date.now()}});for(const r of le)try{r({table:a,action:e,id:t,timestamp:Date.now(),local:!0})}catch(o){console.error("[Database] Change listener error:",o)}}async function qt(){try{console.log("[Database] Initializing schema...");const a=await h`SELECT sqlite_version() as version`;console.log("[Database] SQLite version:",a[0]?.version),console.log("[Database] Creating locations table..."),await h`
CREATE TABLE IF NOT EXISTS locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
longitude REAL NOT NULL,
latitude REAL NOT NULL,
description TEXT,
category TEXT DEFAULT 'default',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
synced INTEGER DEFAULT 0
)
`;const e=await h`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;console.log("[Database] Locations table exists:",e.length>0),console.log("[Database] Creating sync_log table..."),await h`
CREATE TABLE IF NOT EXISTS sync_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_name TEXT NOT NULL,
record_id INTEGER NOT NULL,
action TEXT NOT NULL,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
synced INTEGER DEFAULT 0
)
`,console.log("[Database] Creating remote_data table..."),await h`
CREATE TABLE IF NOT EXISTS remote_data (
key TEXT PRIMARY KEY,
data TEXT NOT NULL,
fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`,console.log("[Database] Creating collector_zones table..."),await h`
CREATE TABLE IF NOT EXISTS collector_zones (
id INTEGER PRIMARY KEY,
zone_name TEXT,
geometry_wkt TEXT,
properties TEXT,
fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`,console.log("[Database] Creating parcels table..."),await h`
CREATE TABLE IF NOT EXISTS parcels (
id INTEGER PRIMARY KEY,
geometry_wkt TEXT,
properties TEXT,
status TEXT DEFAULT 'verified',
fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`;try{await h`SELECT status FROM parcels LIMIT 1`}catch{console.log("[Database] Adding status column to parcels table..."),await h`ALTER TABLE parcels ADD COLUMN status TEXT DEFAULT 'verified'`}console.log("[Database] Creating building_footprints table..."),await h`
CREATE TABLE IF NOT EXISTS building_footprints (
id INTEGER PRIMARY KEY,
geometry_wkt TEXT,
properties TEXT,
fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`,await h`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`,await h`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`;const t=await h`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;console.log("[Database] All tables:",t.map(r=>r.name)),ot=!0,rt(!0),console.log("[Database] ✓ Schema initialized")}catch(a){throw console.error("[Database] ✗ Schema init failed:",a),at(a),a}}async function zt(a,e,t,r={}){const{description:o=null,category:s="default"}=r;console.log("[Database] Adding location:",a,e,t,s);try{const n=await h`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] Table check before insert:",n),n.length===0)throw console.error("[Database] ✗ locations table does not exist!"),new Error("locations table does not exist");console.log("[Database] Executing INSERT..."),await h`
INSERT INTO locations (name, longitude, latitude, description, category)
VALUES (${a}, ${e}, ${t}, ${o}, ${s})
`,console.log("[Database] INSERT completed");const l=(await h`SELECT last_insert_rowid() as id`)[0]?.id;console.log("[Database] New ID:",l);const c=await h`SELECT * FROM locations WHERE id = ${l}`;if(console.log("[Database] Verify insert:",c),c.length===0)throw console.error("[Database] ✗ Insert verification failed - row not found!"),new Error("Insert verification failed");return await h`
INSERT INTO sync_log (table_name, record_id, action)
VALUES ('locations', ${l}, 'INSERT')
`,Pe("locations","INSERT",l),console.log("[Database] ✓ Location added:",l),{id:l}}catch(n){throw console.error("[Database] ✗ Failed to add location:",n),n}}async function nt(a={}){const{category:e=null,limit:t=1e3}=a;try{const r=await h`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] getLocations - table exists:",r.length>0),r.length===0)return console.warn("[Database] locations table does not exist yet"),[];let o;return e?o=await h`
SELECT * FROM locations
WHERE category = ${e}
ORDER BY created_at DESC
LIMIT ${t}
`:o=await h`
SELECT * FROM locations
ORDER BY created_at DESC
LIMIT ${t}
`,console.log("[Database] getLocations returned",o.length,"rows"),o}catch(r){return console.error("[Database] getLocations error:",r),[]}}async function Gt(){try{return(await h`SELECT COUNT(*) as count FROM locations`)[0]?.count??0}catch(a){return console.error("[Database] getLocationCount error:",a),0}}async function st(a,e){try{const t=JSON.stringify(e);await h`
INSERT OR REPLACE INTO remote_data (key, data, fetched_at)
VALUES (${a}, ${t}, CURRENT_TIMESTAMP)
`,console.log("[Database] ✓ Remote data cached:",a)}catch(t){throw console.error("[Database] ✗ Failed to cache remote data:",a,t),t}}async function it(a){try{const e=await h`SELECT data, fetched_at FROM remote_data WHERE key = ${a}`;if(e.length===0)return null;const t=JSON.parse(e[0].data);return console.log("[Database] ✓ Remote data loaded from cache:",a,"(fetched",e[0].fetched_at+")"),t}catch(e){return console.error("[Database] ✗ Failed to read cached remote data:",a,e),null}}async function Ut(a){try{await h`DELETE FROM collector_zones`;for(const e of a){const t=JSON.stringify(e);await h`
INSERT INTO collector_zones (id, zone_name, geometry_wkt, properties, fetched_at)
VALUES (${e.colzonenr||e.id}, ${e.colzonename||e.zone_name||""}, ${e.polygon||e.boundary||""}, ${t}, CURRENT_TIMESTAMP)
`}console.log("[Database] ✓ Saved",a.length,"collector zones")}catch(e){throw console.error("[Database] ✗ Failed to save collector zones:",e),e}}async function Kt(){try{const a=await h`SELECT properties FROM collector_zones ORDER BY id`;return a.length===0?null:a.map(e=>JSON.parse(e.properties))}catch(a){return console.error("[Database] ✗ Failed to read local collector zones:",a),null}}async function Wt(a){try{await h`DELETE FROM parcels`;for(const e of a){const t=JSON.stringify(e),r=e.polygon||e.boundary||e.geom||e.wkt||"",o=e.id||e.parcelid||e.parcel_id||null;await h`
INSERT INTO parcels (id, geometry_wkt, properties, fetched_at)
VALUES (${o}, ${r}, ${t}, CURRENT_TIMESTAMP)
`}console.log("[Database] ✓ Saved",a.length,"parcels")}catch(e){throw console.error("[Database] ✗ Failed to save parcels:",e),e}}async function Ht(){try{const a=await h`SELECT properties FROM parcels ORDER BY id`;return a.length===0?null:a.map(e=>JSON.parse(e.properties))}catch(a){return console.error("[Database] ✗ Failed to read local parcels:",a),null}}async function Vt(a,e){try{const t=JSON.stringify(e);await h`UPDATE parcels SET properties = ${t} WHERE id = ${a}`,console.log("[Database] ✓ Parcel updated:",a),Pe("parcels","UPDATE",a)}catch(t){throw console.error("[Database] ✗ Failed to update parcel:",a,t),t}}async function Jt(a,e){try{const t=JSON.stringify(e);await h`
INSERT INTO parcels (id, geometry_wkt, properties, status, fetched_at)
VALUES (NULL, ${a}, ${t}, 'new', CURRENT_TIMESTAMP)
`;const o=(await h`SELECT last_insert_rowid() as id`)[0]?.id;return console.log("[Database] ✓ New parcel inserted:",o,"(status: new)"),Pe("parcels","INSERT",o),{id:o}}catch(t){throw console.error("[Database] ✗ Failed to insert new parcel:",t),t}}async function Qt(a){try{if(a.length>0){const e=a[0],t={};for(const[r,o]of Object.entries(e))t[r]=o===null?"null":typeof o;console.log("[Database] First footprint field types:",t)}await h`DELETE FROM building_footprints`;for(const e of a){const t=JSON.stringify(e);let r=e.polygon||e.boundary||e.geom||e.wkt||e.footprint||"";const o=typeof r=="object"?JSON.stringify(r):String(r);let s=e.id||e.footprint_id||e.building_id||null;await h`
INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at)
VALUES (${s!==null&&typeof s=="object"?null:s}, ${o}, ${t}, CURRENT_TIMESTAMP)
`}console.log("[Database] ✓ Saved",a.length,"building footprints")}catch(e){throw console.error("[Database] ✗ Failed to save building footprints:",e),e}}async function Yt(){try{const a=await h`SELECT properties FROM building_footprints ORDER BY id`;return a.length===0?null:a.map(e=>JSON.parse(e.properties))}catch(a){return console.error("[Database] ✗ Failed to read local building footprints:",a),null}}async function Xt(){return et.getDatabaseFile()}async function Zt(a="lupmis-backup.sqlite3"){const e=await Xt(),t=new Blob([e],{type:"application/x-sqlite3"}),r=URL.createObjectURL(t),o=document.createElement("a");o.href=r,o.download=a,o.click(),URL.revokeObjectURL(r)}async function eo(){return{type:"FeatureCollection",features:(await nt()).map(e=>({type:"Feature",properties:{id:e.id,name:e.name,category:e.category,notes:e.notes,created_at:e.created_at},geometry:{type:"Point",coordinates:[e.lon,e.lat]}}))}}async function Ce(){try{const a=await h`
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name
`,e=await Gt();return{ready:ot,databasePath:ke,tables:a.map(t=>t.name),locationCount:e}}catch(a){return{ready:!1,error:a.message}}}async function to(){const a=await h`
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name
`;if(a.length===0)return[];const e=a.map(t=>`SELECT '${t.name}' AS name, COUNT(*) AS count FROM "${t.name}"`).join(" UNION ALL ");return h(e)}async function oo(a,e=200){if((await h`
SELECT name FROM sqlite_master
WHERE type='table' AND name = ${a}
`).length===0)throw new Error(`Table "${a}" does not exist`);const r=await h(`SELECT * FROM "${a}" LIMIT ${e}`);return{columns:r.length>0?Object.keys(r[0]):[],rows:r}}async function ro(){console.log("=== DATABASE TEST ===");try{const a=await h`SELECT sqlite_version() as v`;console.log("1. SQLite version:",a[0].v);const e=await h`SELECT name FROM sqlite_master WHERE type='table'`;console.log("2. Tables:",e.map(o=>o.name)),console.log("3. Inserting test row..."),await h`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;const t=await h`SELECT * FROM locations WHERE name = 'TEST'`;console.log("4. Test row:",t);const r=await h`SELECT COUNT(*) as c FROM locations`;return console.log("5. Total rows:",r[0].c),await h`DELETE FROM locations WHERE name = 'TEST'`,console.log("6. Test row deleted"),console.log("=== TEST PASSED ==="),!0}catch(a){return console.error("=== TEST FAILED ===",a),!1}}typeof window<"u"&&(window.testDatabase=ro,window.dbStatus=Ce);class ao{constructor(e,t={}){this.options=t,this.markerSource=new Q,this.clickCallbacks=[],this.categoryEmojis={default:{emoji:"📍",label:"Default"},water:{emoji:"💧",label:"Water Point"},school:{emoji:"🏫",label:"School"},health:{emoji:"🏥",label:"Health Facility"},market:{emoji:"🏪",label:"Market"},other:{emoji:"📌",label:"Other"}},this.getEmoji=i=>{const l=this.categoryEmojis[i];return l?l.emoji:"📍"},this.getCategoryOptionsHtml=()=>Object.entries(this.categoryEmojis).map(([i,{emoji:l,label:c}])=>`<option value="${i}">${l} ${c}</option>`).join(`
`),this.createEmojiStyle=(i,l=24)=>new O({text:new Ae({text:i,font:`${l}px sans-serif`,textBaseline:"bottom",textAlign:"center",offsetY:-5})}),this.defaultStyle=this.createEmojiStyle("📍",32),this.selectedStyle=this.createEmojiStyle("📍",42),this.categoryStyles={};for(const[i,{emoji:l}]of Object.entries(this.categoryEmojis))this.categoryStyles[i]=this.createEmojiStyle(l,32);const r=this.createBaseLayers(t.basemap||"osm");this.markersLayer=new F({title:"Markers",source:this.markerSource,style:i=>this.getFeatureStyle(i)}),this.overlayGroup=new q({title:"Overlays"}),this.map=new ut({target:e,layers:[r,this.markersLayer,this.overlayGroup],view:new dt({center:N(t.center||[0,0]),zoom:t.zoom||2,minZoom:t.minZoom||2,maxZoom:t.maxZoom||19})});const o=new wt({collapsed:!0,mouseover:!0,extent:!1,trash:!1,oninfo:null});this.map.addControl(o),this.map.addControl(new pt);const s=new Et({title:"My Location",delay:3e3,zoom:16});this.map.addControl(s),this.geolocationButton=s;const n=new vt({placeholder:"Search location...",typing:300,minLength:3,maxItems:10,collapsed:!0});this.map.addControl(n),n.on("select",i=>{const l=i.search;if(l){const c=parseFloat(l.lon),u=parseFloat(l.lat),p=[c,u],d=N(p);this.navigateTo(c,u,14);const f={coordinate:d,lonLat:p,name:l.display_name||l.name||"Unknown",searchResult:l};this.searchSelectCallbacks.forEach(y=>y(f))}}),this.searchNominatim=n,this.searchSelectCallbacks=[],this.selectedFeature=null,this.createPopup(),this.createInfoPopup(),this.createAddLocationPopup(),this.createParcelEditPopup(),this.createDrawnPolygonPopup(),this.dblClickCallbacks=[],this.editBar=null,this.drawingsSource=null,this.drawingsLayer=null,this.touchCursor=null,this._editBarActive=!1}initEditBar(){this.drawingsSource=new Q,this.drawingsLayer=new F({title:"sketches",source:this.drawingsSource,style:new O({stroke:new A({color:"#f59e0b",width:2.5}),fill:new _({color:"rgba(245,158,11,0.15)"}),image:new H({radius:6,fill:new _({color:"#f59e0b"}),stroke:new A({color:"#fff",width:1.5})})})}),this._drawingsGroup=new q({title:"Drawings",layers:[this.drawingsLayer]});const e=this.map.getLayers(),t=e.getLength()-1;e.insertAt(t,this._drawingsGroup),this._selectInteraction=new ft({condition:ht,filter:(n,i)=>!!i,layers:n=>n instanceof F}),this._selectInteraction.setActive(!1),this.map.addInteraction(this._selectInteraction),this._modifyInteraction=new Tt({features:this._selectInteraction.getFeatures()}),this._modifyInteraction.setActive(!1),this._undoRedo=new xt,this.map.addInteraction(this._undoRedo),this.editBar=new we({source:this.drawingsSource,interactions:{Select:this._selectInteraction,ModifySelect:this._modifyInteraction,DrawPoint:!0,DrawLine:!0,DrawPolygon:!0,DrawRegular:!0,DrawHole:!0,Delete:!0,Info:!0,Transform:!0,Split:!0,Offset:!0}}),this.map.addControl(this.editBar),this._fillColor="#f59e0b";const r=document.createElement("input");r.type="color",r.value=this._fillColor,r.title="Fill colour",r.style.cssText="width:28px;height:28px;border:none;padding:0;cursor:pointer;background:transparent;",r.addEventListener("input",n=>{this._fillColor=n.target.value;const i=this._fillColor,l=parseInt(i.slice(1,3),16),c=parseInt(i.slice(3,5),16),u=parseInt(i.slice(5,7),16);this.drawingsLayer.setStyle(new O({stroke:new A({color:i,width:2.5}),fill:new _({color:`rgba(${l},${c},${u},0.15)`}),image:new H({radius:6,fill:new _({color:i}),stroke:new A({color:"#fff",width:1.5})})})),console.log("[MapView] Fill colour changed to",i)});const o=new Lt({group:!0,controls:[new J({html:'<i class="bi bi-arrow-counterclockwise"></i>',className:"ol-undo",title:"Undo",handleClick:()=>{this._undoRedo.hasUndo()&&this._undoRedo.undo()}}),new J({html:'<i class="bi bi-arrow-clockwise"></i>',className:"ol-redo",title:"Redo",handleClick:()=>{this._undoRedo.hasRedo()&&this._undoRedo.redo()}}),new J({html:r,className:"ol-colorpicker",title:"Fill colour"}),new J({html:'<i class="bi bi-floppy"></i>',className:"ol-save",title:"Save drawings",handleClick:()=>{this.dispatchEditEvent("save")}})]});this.editBar.addControl(o),this.setEditMode(!1),this._drawingsGroup.on("change:visible",()=>{const n=this._drawingsGroup.getVisible();this.setEditMode(n)}),("ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0)&&(this.touchCursor=new St({className:"ol-editbar-cursor"}),this.map.addInteraction(this.touchCursor),this.touchCursor.setActive(!1),console.log("[MapView] Touch device detected — TouchCursor added")),this.drawingsSource.on("addfeature",n=>{const i=n.feature,l=i.getGeometry();if(!l||l.getType()!=="Polygon")return;const c=l.getInteriorPoint().getCoordinates();this.showDrawnPolygonPopup(i,c)}),console.log("[MapView] EditBar initialised with Drawings group, UndoRedo, colour picker")}dispatchEditEvent(e){if(!this._editEventListeners)return;const t=this._editEventListeners[e];t&&t.forEach(r=>r())}onEditEvent(e,t){this._editEventListeners||(this._editEventListeners={}),this._editEventListeners[e]||(this._editEventListeners[e]=[]),this._editEventListeners[e].push(t)}setEditMode(e){this._editBarActive=!!e,this.editBar&&(this.editBar.setVisible(this._editBarActive),this._editBarActive||this.editBar.deactivateControls()),this._selectInteraction&&(this._editBarActive||this._selectInteraction.getFeatures().clear(),this._selectInteraction.setActive(this._editBarActive)),this._modifyInteraction&&this._modifyInteraction.setActive(this._editBarActive),this.touchCursor&&this.touchCursor.setActive(this._editBarActive),console.log("[MapView] Edit mode:",this._editBarActive?"ON":"OFF")}isEditMode(){return this._editBarActive}getDrawingsLayer(){return this.drawingsLayer}getDrawingsSource(){return this.drawingsSource}getEditBar(){return this.editBar}createPopup(){this.popupElement=document.createElement("div"),this.popupElement.className="map-popup",this.popupElement.style.cssText=`
position: absolute;
background: white;
border-radius: 8px;
padding: 10px 14px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 13px;
min-width: 150px;
max-width: 280px;
pointer-events: none;
z-index: 1000;
border: 1px solid var(--border, #1e1a4b1f);
`,this.popup=new K({element:this.popupElement,positioning:"bottom-center",offset:[0,-15],stopEvent:!1}),this.map.addOverlay(this.popup),this.setupHoverPopup()}setupHoverPopup(){let e=null;this.map.on("pointermove",t=>{if(t.dragging){this.hidePopup();return}const r=this.map.forEachFeatureAtPixel(t.pixel,o=>o.get("name")?o:null);r&&r!==e?(e=r,this.showPopup(r,t.coordinate)):!r&&e&&(e=null,this.hidePopup()),this.map.getTargetElement().style.cursor=r?"pointer":""}),this.map.getTargetElement().addEventListener("mouseleave",()=>{this.hidePopup(),e=null})}showPopup(e,t){const r=e.get("name")||"Unnamed",o=e.get("category")||"default",s=e.get("description"),n=e.get("lon"),i=e.get("lat");let c=`
<div style="font-weight: 600; font-size: 14px; margin-bottom: 6px;">
${this.getEmoji(o)} ${this.escapeHtml(r)}
</div>
`;const p={water:"#3b82f6",school:"#f59e0b",health:"#ef4444",market:"#8b5cf6",default:"#2d5016",other:"#6b7280"}[o]||"#6b7280";c+=`
<div style="margin-bottom: 6px;">
<span style="
background: ${p}20;
color: ${p};
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
">${o}</span>
</div>
`,s&&(c+=`
<div style="color: var(--muted-foreground, #7a7a7a); font-size: 12px; margin-bottom: 6px; line-height: 1.4;">
${this.escapeHtml(s)}
</div>
`),n!==void 0&&i!==void 0&&(c+=`
<div style="color: var(--muted-foreground, #7a7a7a); font-size: 11px; font-family: monospace;">
${Number(n).toFixed(5)}, ${Number(i).toFixed(5)}
</div>
`),this.popupElement.innerHTML=c,this.popup.setPosition(t)}hidePopup(){this.popup.setPosition(void 0)}createInfoPopup(){this.infoPopupElement=document.createElement("div"),this.infoPopupElement.className="map-info-popup",this.infoPopupElement.style.cssText=`
position: absolute;
background: white;
border-radius: 10px;
padding: 0;
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 13px;
min-width: 220px;
max-width: 320px;
z-index: 1001;
border: 1px solid var(--border, #1e1a4b1f);
overflow: hidden;
`,this.infoPopup=new K({element:this.infoPopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.infoPopup)}showInfoPopup(e,t,r={}){const{title:o="Feature Info",color:s="#e11d48"}=r,n=e.getProperties(),i=e.getGeometry(),l=i.getType(),c=["geometry","_layerType"];let u="";for(const[d,f]of Object.entries(n))c.includes(d)||f===void 0||f===null||(u+=`
<tr>
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">${this.escapeHtml(d)}</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${this.escapeHtml(String(f))}</td>
</tr>
`);if(l==="Polygon"||l==="MultiPolygon"){const d=re(i,{projection:"EPSG:3857"}),f=d>1e6?`${(d/1e6).toFixed(2)} km² (${d.toLocaleString("en",{maximumFractionDigits:0})} m²)`:`${d.toLocaleString("en",{maximumFractionDigits:0})} m²`;u+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">area</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${f}</td>
</tr>
`}else if(l==="LineString"||l==="MultiLineString"){const d=me(i,{projection:"EPSG:3857"}),f=d>=1e3?`${(d/1e3).toFixed(2)} km (${d.toLocaleString("en",{maximumFractionDigits:0})} m)`:`${d.toLocaleString("en",{maximumFractionDigits:1})} m`;u+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">length</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${f}</td>
</tr>
`}else if(l==="Point"){const d=z(i.getCoordinates()),f=d[0].toFixed(6),y=d[1].toFixed(6);u+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">longitude</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${f}</td>
</tr>
<tr>
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">latitude</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${y}</td>
</tr>
`}const p=`
<div style="background:${s};color:#fff;padding:8px 12px;font-weight:600;display:flex;justify-content:space-between;align-items:center;">
<span>${this.escapeHtml(o)}</span>
<button id="info-popup-close" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px;line-height:1;">&times;</button>
</div>
<div style="padding:8px 4px;">
<table style="width:100%;border-collapse:collapse;font-size:13px;">
${u}
</table>
</div>
`;this.infoPopupElement.innerHTML=p,this.infoPopup.setPosition(t),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()})}hideInfoPopup(){this.infoPopup.setPosition(void 0)}showCircleIntersectionPopup(e,t){const r=e.getGeometry();if(!r||typeof r.getCenter!="function")return;const o=gt(r,64),s=o.getExtent(),n=e.get("_radius")||r.getRadius(),i=[],l=[],c={},u=E=>{const w=E.getGeometry();if(!w)return!1;const L=w.getExtent();return L[2]<s[0]||L[0]>s[2]||L[3]<s[1]||L[1]>s[3]?!1:o.intersectsExtent(L)&&this._geometriesIntersect(o,w)},p=(E,w)=>{E.getLayers().forEach(L=>{if(L instanceof q)p(L,L.get("title")||w);else if(L instanceof F&&L.getVisible()){const v=L.get("title")||w||"Unknown",S=L.getSource();if(!S)return;const m=S.getFeaturesInExtent(s);for(const T of m){const P=T.get("_layerType");P==="measure_circle"||P==="measure_circle_radius"||u(T)&&(P==="parcel"?i.push(T):P==="collector_zone"?l.push(T):(c[v]||(c[v]=[]),c[v].push(T)))}}})};p(this.overlayGroup,"Overlays");const d=n>=1e3?`${(n/1e3).toFixed(2)} km`:`${Math.round(n)} m`,f=Math.PI*n*n,y=f>1e6?`${(f/1e6).toFixed(2)} km²`:`${f.toLocaleString("en",{maximumFractionDigits:0})} m²`;let b=`
<tr>
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">radius</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${d}</td>
</tr>
<tr>
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">area</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${y}</td>
</tr>
`;if(i.length>0&&(b+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:#0ea5e9;white-space:nowrap;">parcels</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${i.length}</td>
</tr>
`),l.length>0){const E=l.map(w=>w.get("colzonename")||w.get("zone_name")||w.get("name")||"unnamed");b+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:#7c3aed;white-space:nowrap;">zones</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${l.length}</td>
</tr>
<tr>
<td style="padding:4px 8px;font-weight:600;color:#7c3aed;white-space:nowrap;">zone names</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${E.map(w=>this.escapeHtml(w)).join(", ")}</td>
</tr>
`}for(const[E,w]of Object.entries(c))b+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td style="padding:4px 8px;font-weight:600;color:var(--muted-foreground, #7a7a7a);white-space:nowrap;">${this.escapeHtml(E)}</td>
<td style="padding:4px 8px;color:var(--foreground, #1e1a4b);">${w.length} feature(s)</td>
</tr>
`;i.length===0&&l.length===0&&Object.keys(c).length===0&&(b+=`
<tr style="border-top:1px solid var(--border, #1e1a4b1f);">
<td colspan="2" style="padding:8px;color:#999;text-align:center;font-style:italic;">No intersecting features found</td>
</tr>
`);const x=`
<div style="background:var(--brand-navy, #1e1a4b);color:#fff;padding:8px 12px;font-weight:600;display:flex;justify-content:space-between;align-items:center;">
<span>⭕ Circle Analysis</span>
<button id="info-popup-close" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px;line-height:1;">&times;</button>
</div>
<div style="padding:8px 4px;">
<table style="width:100%;border-collapse:collapse;font-size:13px;">
${b}
</table>
</div>
`;this.infoPopupElement.innerHTML=x,this.infoPopup.setPosition(t),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()})}_geometriesIntersect(e,t){const r=t.getType();if(r==="Polygon"||r==="MultiPolygon"){const o=t.getFlatCoordinates(),s=t.getStride();for(let l=0;l<o.length;l+=s)if(e.intersectsCoordinate([o[l],o[l+1]]))return!0;const n=e.getFlatCoordinates(),i=e.getStride();for(let l=0;l<n.length;l+=i)if(t.intersectsCoordinate([n[l],n[l+1]]))return!0;return!1}if(r==="Point")return e.intersectsCoordinate(t.getCoordinates());if(r==="LineString"||r==="MultiLineString"){const o=t.getFlatCoordinates(),s=t.getStride();for(let n=0;n<o.length;n+=s)if(e.intersectsCoordinate([o[n],o[n+1]]))return!0;return!1}return!0}createParcelEditPopup(){this.parcelEditElement=document.createElement("div"),this.parcelEditElement.className="map-parcel-edit-popup",this.parcelEditElement.style.cssText=`
position: absolute;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 13px;
min-width: 280px;
max-width: 360px;
max-height: 420px;
z-index: 1002;
border: 2px solid var(--primary, #005eb8);
overflow: hidden;
display: flex;
flex-direction: column;
`,this.parcelEditPopup=new K({element:this.parcelEditElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.parcelEditPopup),this._parcelEditCallbacks=[],this._parcelEditFeature=null}showParcelEditPopup(e,t){this._parcelEditFeature=e;const r=e.getProperties(),o=["geometry","_layerType"];let s="";for(const[l,c]of Object.entries(r)){if(o.includes(l))continue;const u=c==null?"":String(c),p=this.escapeHtml(l),d=this.escapeHtml(u);s+=`
<div style="margin-bottom:8px;">
<label style="display:block;font-size:11px;font-weight:600;color:var(--muted-foreground, #7a7a7a);margin-bottom:2px;">${p}</label>
<input type="text" name="${p}" value="${d}"
style="width:100%;padding:6px 8px;border:1px solid var(--border, #1e1a4b1f);border-radius:4px;font-size:13px;color:var(--foreground, #1e1a4b);min-height:34px;"
/>
</div>
`}const n=`
<div style="background:var(--primary, #005eb8);color:#fff;padding:8px 12px;font-weight:600;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
<span>✏️ Edit Parcel</span>
<button class="parcel-edit-close" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;padding:0 4px;line-height:1;">&times;</button>
</div>
<form class="parcel-edit-form" style="padding:10px 12px;overflow-y:auto;flex:1;">
${s}
<div style="display:flex;gap:8px;margin-top:10px;">
<button type="submit" style="flex:1;padding:8px 12px;background:var(--primary, #005eb8);color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;min-height:38px;">
💾 Save
</button>
<button type="button" class="parcel-edit-cancel" style="flex:1;padding:8px 12px;background:var(--muted-foreground, #7a7a7a);color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;min-height:38px;">
Cancel
</button>
</div>
</form>
`;this.parcelEditElement.innerHTML=n,this.parcelEditPopup.setPosition(t),this.parcelEditElement.querySelector(".parcel-edit-close").addEventListener("click",()=>{this.hideParcelEditPopup()}),this.parcelEditElement.querySelector(".parcel-edit-cancel").addEventListener("click",()=>{this.hideParcelEditPopup()});const i=this.parcelEditElement.querySelector(".parcel-edit-form");i.addEventListener("submit",l=>{l.preventDefault();const c=new FormData(i),u={};for(const[p,d]of c.entries())u[p]=d;u._layerType="parcel";for(const[p,d]of Object.entries(u))this._parcelEditFeature.set(p,d);for(const p of this._parcelEditCallbacks)p(this._parcelEditFeature,u);this.hideParcelEditPopup()})}hideParcelEditPopup(){this.parcelEditPopup.setPosition(void 0),this._parcelEditFeature=null}onParcelEdit(e){this._parcelEditCallbacks.push(e)}createDrawnPolygonPopup(){this.drawnPolygonElement=document.createElement("div"),this.drawnPolygonElement.className="map-drawn-polygon-popup",this.drawnPolygonElement.style.cssText=`
position: absolute;
background: var(--card, #fff);
border-radius: var(--radius-xl, 0.75rem);
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 13px;
min-width: 280px;
max-width: 360px;
max-height: 420px;
z-index: 1002;
border: 2px solid var(--success, #006b3f);
overflow: hidden;
display: flex;
flex-direction: column;
`,this.drawnPolygonPopup=new K({element:this.drawnPolygonElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.drawnPolygonPopup),this._drawnPolygonCallbacks=[],this._drawnPolygonFeature=null}getParcelAttributeKeys(){const e=["geometry","_layerType"],t=[],r=o=>{t.length>0||o.getLayers().forEach(s=>{if(!(t.length>0)){if(s instanceof q)r(s);else if(s instanceof F){const n=s.getSource();if(!n)return;for(const i of n.getFeatures()){if(i.get("_layerType")!=="parcel")continue;const l=i.getProperties();for(const c of Object.keys(l))e.includes(c)||t.push(c);return}}}})};return r(this.overlayGroup),t}showDrawnPolygonPopup(e,t){this._drawnPolygonFeature=e;const r=this.getParcelAttributeKeys();if(r.length===0){console.warn("[MapView] No parcel attributes found — cannot build form");return}let o="";for(const u of r){const p=this.escapeHtml(u);o+=`
<div style="margin-bottom:8px;">
<label style="display:block;font-size:11px;font-weight:600;color:var(--muted-foreground, #7a7a7a);margin-bottom:2px;">${p}</label>
<input type="text" name="${p}" value=""
style="width:100%;padding:6px 8px;border:1px solid var(--border, #1e1a4b1f);border-radius:4px;font-size:13px;color:var(--foreground, #1e1a4b);min-height:34px;"
/>
</div>
`}const s=e.getGeometry(),n=re(s,{projection:"EPSG:3857"}),l=`
<div style="background:var(--success, #006b3f);color:var(--success-foreground, #fff);padding:8px 12px;font-weight:600;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;">
<span>📐 Polygon Attributes</span>
<button class="drawn-polygon-close" style="background:none;border:none;color:var(--success-foreground, #fff);font-size:18px;cursor:pointer;padding:0 4px;line-height:1;">&times;</button>
</div>
<div style="padding:8px 12px;background:var(--muted, #f2f4f7);border-bottom:1px solid var(--border, #1e1a4b1f);font-size:12px;color:var(--muted-foreground, #7a7a7a);flex-shrink:0;">
Area: <strong>${n>1e6?`${(n/1e6).toFixed(2)} km²`:`${n.toLocaleString("en",{maximumFractionDigits:0})} m²`}</strong>
</div>
<form class="drawn-polygon-form" style="padding:10px 12px;overflow-y:auto;flex:1;">
${o}
<div style="display:flex;gap:8px;margin-top:10px;">
<button type="submit" style="flex:1;padding:8px 12px;background:var(--success, #006b3f);color:var(--success-foreground, #fff);border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;min-height:38px;">
💾 Save
</button>
<button type="button" class="drawn-polygon-cancel" style="flex:1;padding:8px 12px;background:var(--muted-foreground, #7a7a7a);color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;min-height:38px;">
Cancel
</button>
</div>
</form>
`;this.drawnPolygonElement.innerHTML=l,this.drawnPolygonPopup.setPosition(t),this.drawnPolygonElement.querySelector(".drawn-polygon-close").addEventListener("click",()=>{this.hideDrawnPolygonPopup()}),this.drawnPolygonElement.querySelector(".drawn-polygon-cancel").addEventListener("click",()=>{this.hideDrawnPolygonPopup()});const c=this.drawnPolygonElement.querySelector(".drawn-polygon-form");c.addEventListener("submit",u=>{u.preventDefault();const p=new FormData(c),d={};for(const[f,y]of p.entries())d[f]=y;for(const[f,y]of Object.entries(d))this._drawnPolygonFeature.set(f,y);this._drawnPolygonFeature.set("_layerType","parcel");for(const f of this._drawnPolygonCallbacks)f(this._drawnPolygonFeature,d);this.hideDrawnPolygonPopup()})}hideDrawnPolygonPopup(){this.drawnPolygonPopup.setPosition(void 0),this._drawnPolygonFeature=null}onDrawnPolygonSave(e){this._drawnPolygonCallbacks.push(e)}onDblClick(e){return this.dblClickCallbacks.push(e),this.dblClickCallbacks.length===1&&this.map.on("dblclick",t=>{const[r,o]=z(t.coordinate);let s=null;this.map.forEachFeatureAtPixel(t.pixel,n=>(s=n,!0)),s&&(t.preventDefault(),t.stopPropagation());for(const n of this.dblClickCallbacks)n(r,o,s,t);if(s)return!1}),()=>{const t=this.dblClickCallbacks.indexOf(e);t>-1&&this.dblClickCallbacks.splice(t,1)}}escapeHtml(e){if(!e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}createAddLocationPopup(){this.addLocationPopupElement=document.createElement("div"),this.addLocationPopupElement.className="map-add-location-popup",this.addLocationPopupElement.innerHTML=`
<div class="add-location-popup-header">
<span> Add Location</span>
<button type="button" class="add-location-popup-close" aria-label="Close">&times;</button>
</div>
<form id="map-add-location-form">
<div class="add-location-popup-field">
<label for="map-location-name">Name <span class="text-danger">*</span></label>
<input type="text" id="map-location-name" name="name" required placeholder="e.g., Water Point A">
</div>
<div class="add-location-popup-field">
<label for="map-location-category">Category</label>
<select id="map-location-category" name="category">
${this.getCategoryOptionsHtml()}
</select>
</div>
<div class="add-location-popup-field">
<label for="map-location-description">Description</label>
<textarea id="map-location-description" name="description" rows="2" placeholder="Optional notes..."></textarea>
</div>
<div class="add-location-popup-coords">
<small>📍 <span id="map-location-coords"></span></small>
</div>
<button type="submit" class="add-location-popup-submit"> Add Location</button>
</form>
`,this.addLocationPopup=new K({element:this.addLocationPopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.addLocationPopup),this.addLocationCoords=null,this.addLocationPopupElement.querySelector(".add-location-popup-close").addEventListener("click",()=>{this.hideAddLocationPopup()}),this.addLocationCallbacks=[]}showAddLocationPopup(e){const[t,r]=z(e);this.addLocationCoords={lon:t,lat:r};const o=this.addLocationPopupElement.querySelector("#map-location-coords");o.textContent=`${t.toFixed(6)}, ${r.toFixed(6)}`,this.addLocationPopupElement.querySelector("#map-add-location-form").reset(),this.addLocationPopup.setPosition(e)}hideAddLocationPopup(){this.addLocationPopup.setPosition(void 0),this.addLocationCoords=null}onAddLocation(e){if(this.addLocationCallbacks.push(e),this.addLocationCallbacks.length===1){const t=this.addLocationPopupElement.querySelector("#map-add-location-form");t.addEventListener("submit",r=>{if(r.preventDefault(),!this.addLocationCoords)return;const o=new FormData(t),s={name:o.get("name"),category:o.get("category"),description:o.get("description"),lon:this.addLocationCoords.lon,lat:this.addLocationCoords.lat};this.addLocationCallbacks.forEach(n=>n(s)),this.hideAddLocationPopup()})}}createBaseLayers(e){const t=new j({title:"Topographic",type:"base",visible:e==="topo",source:new V({url:"https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png",attributions:"Map data: © OpenTopoMap",maxZoom:17,crossOrigin:"anonymous"})}),r=new j({title:"Carto Light",type:"base",visible:e==="carto-light",source:new V({url:"https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",attributions:"© CARTO",maxZoom:19,crossOrigin:"anonymous"})}),o=new j({title:"Carto Dark",type:"base",visible:e==="carto-dark",source:new V({url:"https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",attributions:"© CARTO",maxZoom:19,crossOrigin:"anonymous"})}),s=new j({title:"OSM Cycle map",type:"base",visible:!1,source:new Me({url:"https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=ae1339c46dd3446b9c491e7336d38760"})}),n=new j({title:"Satellite",type:"base",visible:e==="satellite",source:new V({url:"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",attributions:"Tiles © Esri",maxZoom:19,crossOrigin:"anonymous"})}),i=new j({title:"Google Sat",type:"base",visible:e==="googlesat",source:new V({url:"http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga",attributions:"Tiles © Google",maxZoom:19,crossOrigin:"anonymous"})}),l=new j({title:"OpenStreetMap",type:"base",visible:e==="osm",source:new Me});return new q({title:"Base Maps",layers:[r,o,n,s,t,i,l]})}getFeatureStyle(e){const t=e.get("category")||"default",r=this.getEmoji(t);if(e===this.selectedFeature)return[new O({image:new H({radius:22,fill:new _({color:"rgba(220, 38, 38, 0.25)"}),stroke:new A({color:"#dc2626",width:3})})}),new O({text:new Ae({text:r,font:"40px sans-serif",textBaseline:"bottom",textAlign:"center",offsetY:-5})})];const o=e.get("style");return o||(this.categoryStyles[t]?this.categoryStyles[t]:this.defaultStyle)}setCategoryStyles(e){for(const[t,r]of Object.entries(e)){r.emoji&&(this.categoryEmojis[t]?(this.categoryEmojis[t].emoji=r.emoji,r.label&&(this.categoryEmojis[t].label=r.label)):this.categoryEmojis[t]={emoji:r.emoji,label:r.label||t});const o=this.getEmoji(t),s=r.fontSize||28;this.categoryStyles[t]=this.createEmojiStyle(o,s)}this.markerSource.changed()}addMarker(e,t,r={}){console.log("[MapView] Adding marker at",e,t,"with properties:",r);const o=new be({geometry:new Ie(N([e,t])),...r});return o.set("lon",e),o.set("lat",t),this.markerSource.addFeature(o),console.log("[MapView] Marker added, total features:",this.markerSource.getFeatures().length),o}addMarkers(e){console.log("[MapView] Adding",e.length,"markers");const t=e.map(r=>new be({geometry:new Ie(N([r.longitude,r.latitude])),id:r.id,name:r.name,description:r.description,category:r.category,lon:r.longitude,lat:r.latitude}));return this.markerSource.addFeatures(t),console.log("[MapView] Markers added, total features:",this.markerSource.getFeatures().length),t}clearMarkers(){this.markerSource.clear(),this.selectedFeature=null}removeMarker(e){if(typeof e=="object")this.markerSource.removeFeature(e);else{const t=this.markerSource.getFeatures().find(r=>r.get("id")===e);t&&this.markerSource.removeFeature(t)}}getMarkers(){return this.markerSource.getFeatures()}findMarker(e){return this.markerSource.getFeatures().find(t=>t.get("id")===e)}selectMarker(e){return typeof e=="object"?this.selectedFeature=e:this.selectedFeature=this.findMarker(e),this.markerSource.changed(),this.selectedFeature}clearSelection(){this.selectedFeature=null,this.markerSource.changed()}zoomTo(e,t,r=15){this.map.getView().animate({center:N([e,t]),zoom:r,duration:500})}fitToMarkers(e=50){const t=this.markerSource.getExtent();t&&t[0]!==1/0&&this.map.getView().fit(t,{padding:[e,e,e,e],duration:500,maxZoom:16})}getCenter(){const e=this.map.getView().getCenter();return z(e)}getZoom(){return this.map.getView().getZoom()}setCenter(e,t){this.map.getView().setCenter(N([e,t]))}setZoom(e){this.map.getView().setZoom(e)}onClick(e){return this.clickCallbacks.push(e),this.clickCallbacks.length===1&&(this._clickTimer=null,this.map.on("dblclick",()=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null)}),this.map.on("click",t=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null),!this._editBarActive&&this._selectInteraction&&this._selectInteraction.getFeatures().clear();let r=!1,o=!1,s=null;if(this.map.forEachFeatureAtPixel(t.pixel,l=>{l.get("_layerType")==="parcel"&&(o=!0),l.get("name")&&(s=l),r=!0}),r&&!o&&!s)return;const[n,i]=z(t.coordinate);this._clickTimer=setTimeout(()=>{this._clickTimer=null;let l=null;this.map.forEachFeatureAtPixel(t.pixel,c=>{if(c.get("name"))return l=c,!0});for(const c of this.clickCallbacks)c(n,i,l,t)},300)})),()=>{const t=this.clickCallbacks.indexOf(e);t>-1&&this.clickCallbacks.splice(t,1)}}onPointerMove(e){this.map.on("pointermove",t=>{if(t.dragging)return;const[r,o]=z(t.coordinate);let s=null;this.map.forEachFeatureAtPixel(t.pixel,n=>{if(n.get("name"))return s=n,!0}),this.map.getTargetElement().style.cursor=s?"pointer":"",e(r,o,s,t)})}enableHoverCursor(){}addGeoJSONLayer(e,t,r={},o=null){const{strokeColor:s="#3b82f6",strokeWidth:n=2,fillColor:i="rgba(59,130,246,0.1)"}=r,l=new Q({features:new ce().readFeatures(e,{featureProjection:"EPSG:3857"})}),c=new F({title:t,source:l,style:new O({stroke:new A({color:s,width:n}),fill:new _({color:i})})});return(o||this.overlayGroup).getLayers().push(c),console.log("[MapView] GeoJSON layer added:",t,"→",l.getFeatures().length,"features",o?`(in group "${o.get("title")}")`:""),c}addLayerGroup(e,t,r=""){const o=new q({title:t.trim()});return o.set("layerId",e),o.set("description",r),this.overlayGroup.getLayers().push(o),console.log("[MapView] Layer group added:",t.trim(),"(id:",e+")"),o}getLayerGroup(e){let t=null;return this.overlayGroup.getLayers().forEach(r=>{r.get("layerId")===e&&(t=r)}),t}getLayerGroupByTitle(e){let t=null;return this.overlayGroup.getLayers().forEach(r=>{r.get("title")===e&&(t=r)}),t}getOverlayGroup(){return this.overlayGroup}getMap(){return this.map}getMarkerSource(){return this.markerSource}getMarkersLayer(){return this.markersLayer}updateSize(){this.map.updateSize()}onSearchSelect(e){this.searchSelectCallbacks.push(e)}navigateTo(e,t,r=14,o=500){const s=N([e,t]);this.map.getView().animate({center:s,zoom:r,duration:o})}}class no{constructor(e,t={}){this.map=e,this.options=t,this.measureSource=new Q,this.measureLayer=new F({source:this.measureSource,style:this.getMeasureStyle(),title:"Measurements",zIndex:100}),this.drawSource=new Q,this.drawLayer=new F({source:this.drawSource,style:this.getDrawStyle(),title:"Draw sketches",displayInLayerSwitcher:!1,zIndex:99});const r=this.map.getLayers(),o=r.getLength()-1;r.insertAt(o,this.drawLayer),r.insertAt(o,this.measureLayer),this.activeInteraction=null,this.measureTooltip=null,this.measureTooltipElement=null,this.onMeasureCompleteCallbacks=[],this.onDrawCompleteCallbacks=[]}getMeasureStyle(){return new O({fill:new _({color:"rgba(255, 233, 106, 0.2)"}),stroke:new A({color:"#8B008B",lineDash:[10,10],width:2}),image:new H({radius:5,stroke:new A({color:"#8B008B"}),fill:new _({color:"rgba(255, 233, 106, 0.5)"})})})}getDrawStyle(){return new O({fill:new _({color:"rgba(255, 233, 106, 0.3)"}),stroke:new A({color:"#8B008B",width:2}),image:new H({radius:6,stroke:new A({color:"#8B008B",width:2}),fill:new _({color:"#FFE96A"})})})}formatLength(e){return e>1e3?Math.round(e/1e3*100)/100+" km":Math.round(e*100)/100+" m"}formatArea(e){return e>1e6?Math.round(e/1e6*100)/100+" km²":Math.round(e*100)/100+" m²"}formatCircleExtent(e){const t=Math.PI*e*e;return t>1e6?Math.round(t/1e6*100)/100+" km²":Math.round(t*100)/100+" m²"}createMeasureTooltip(){this.measureTooltipElement&&this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=document.createElement("div"),this.measureTooltipElement.className="measure-tooltip",this.measureTooltip=new K({element:this.measureTooltipElement,offset:[15,0],positioning:"center-left",stopEvent:!1}),this.map.addOverlay(this.measureTooltip)}deactivate(){this.activeInteraction&&(this.map.removeInteraction(this.activeInteraction),this.activeInteraction=null),this.measureTooltip&&(this.map.removeOverlay(this.measureTooltip),this.measureTooltip=null),this.measureTooltipElement&&this.measureTooltipElement.parentNode&&(this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=null)}startCircleMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new G({source:this.measureSource,type:"Circle",style:new O({fill:new _({color:"rgba(255, 233, 106, 0.2)"}),stroke:new A({color:"rgba(139, 0, 139, 0.7)",lineDash:[10,10],width:2}),image:new H({radius:5,stroke:new A({color:"rgba(139, 0, 139, 0.7)"}),fill:new _({color:"rgba(255, 233, 106, 0.5)"})})})});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const n=s.target;if(n instanceof yt){const i=n.getRadius(),l=this.formatCircleExtent(i),u=`<strong>${this.formatLength(i)}</strong><br><small>${l}</small>`;this.measureTooltipElement.innerHTML=u,this.measureTooltip.setPosition(n.getLastCoordinate())}})}),e.on("drawend",r=>{const o=r.feature,s=o.getGeometry(),n=s.getCenter(),i=s.getRadius();o.set("_layerType","measure_circle"),o.set("_radius",i),o.set("_center",n);const l=new be({geometry:new mt([n,[n[0]+i,n[1]]])});l.set("_layerType","measure_circle_radius"),this.measureSource.addFeature(l),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltip.setOffset([0,-7]),this.measureTooltipElement=null,this.createMeasureTooltip(),de(t);const c={type:"circle",center:n,radius:i,area:Math.PI*i*i,feature:o};this.onMeasureCompleteCallbacks.forEach(u=>u(c))}),e}startLineMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new G({source:this.measureSource,type:"LineString",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const n=s.target,i=me(n),l=this.formatLength(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(n.getLastCoordinate())})}),e.on("drawend",r=>{const o=r.feature,s=o.getGeometry(),n=me(s);this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),de(t);const i={type:"line",length:n,feature:o};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startAreaMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new G({source:this.measureSource,type:"Polygon",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const n=s.target,i=re(n),l=this.formatArea(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(n.getInteriorPoint().getCoordinates())})}),e.on("drawend",r=>{const o=r.feature,s=o.getGeometry(),n=re(s);this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),de(t);const i={type:"polygon",area:n,feature:o,coordinate:s.getInteriorPoint().getCoordinates()};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startDrawPoint(){this.deactivate();const e=new G({source:this.drawSource,type:"Point",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"point",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(r))}),e}startDrawLine(){this.deactivate();const e=new G({source:this.drawSource,type:"LineString",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"line",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(r))}),e}startDrawPolygon(){this.deactivate();const e=new G({source:this.drawSource,type:"Polygon",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"polygon",feature:t.feature};this.onDrawCompleteCallbacks.forEach(o=>o(r))}),e}clearMeasurements(){this.measureSource.clear(),document.querySelectorAll(".measure-tooltip-static").forEach(t=>t.parentNode.removeChild(t))}clearDrawings(){this.drawSource.clear()}clearAll(){this.clearMeasurements(),this.clearDrawings()}onMeasureComplete(e){this.onMeasureCompleteCallbacks.push(e)}onDrawComplete(e){this.onDrawCompleteCallbacks.push(e)}createControlBar(e={}){e.position;const t=new we({group:!0,className:"map-tools-bar"}),r=new we({toggleOne:!0,group:!0}),o=new pe({html:'<span class="tool-icon">⭕</span>',title:"Measure Circle (radius & area)",className:"measure-circle-btn",onToggle:l=>{l?this.startCircleMeasure():this.deactivate()}});r.addControl(o);const s=new pe({html:'<span class="tool-icon">📏</span>',title:"Measure Distance",className:"measure-line-btn",onToggle:l=>{l?this.startLineMeasure():this.deactivate()}});r.addControl(s);const n=new pe({html:'<span class="tool-icon">⬛</span>',title:"Measure Area",className:"measure-area-btn",onToggle:l=>{l?this.startAreaMeasure():this.deactivate()}});r.addControl(n);const i=new J({html:'<span class="tool-icon">🗑️</span>',title:"Clear Measurements",className:"clear-measure-btn",handleClick:()=>{this.clearMeasurements(),o.setActive(!1),s.setActive(!1),n.setActive(!1)}});return r.addControl(i),t.addControl(r),t}getMeasureLayer(){return this.measureLayer}getDrawLayer(){return this.drawLayer}getMeasureSource(){return this.measureSource}getDrawSource(){return this.drawSource}isActive(){return this.activeInteraction!==null}}let B=null;async function so(){if(!("serviceWorker"in navigator))return console.warn("[PWA] Service Workers not supported"),null;try{return B=await navigator.serviceWorker.register("/sw.js",{scope:"/"}),console.log("[PWA] Service Worker registered:",B.scope),B.addEventListener("updatefound",()=>{const a=B.installing;a.addEventListener("statechange",()=>{a.state==="installed"&&navigator.serviceWorker.controller&&(console.log("[PWA] New version available"),po())})}),B}catch(a){return console.error("[PWA] Service Worker registration failed:",a),null}}let U=null,D=null;function io(a="#install-btn"){if(D=typeof a=="string"?document.querySelector(a):a,!D){console.warn("[PWA] Install button not found:",a);return}D.style.display="none",window.addEventListener("beforeinstallprompt",e=>{e.preventDefault(),U=e,D.style.display="block",console.log("[PWA] Install prompt ready")}),D.addEventListener("click",async()=>{if(!U){lo();return}U.prompt();const{outcome:e}=await U.userChoice;console.log("[PWA] Install prompt outcome:",e),U=null,D.style.display="none"}),window.addEventListener("appinstalled",()=>{console.log("[PWA] App installed"),U=null,D.style.display="none"}),window.matchMedia("(display-mode: standalone)").matches&&(D.style.display="none")}function lo(){const a=/iPad|iPhone|iPod/.test(navigator.userAgent),e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let t=`To install this app:
`;a?(t+=`1. Tap the Share button (square with arrow)
`,t+='2. Scroll down and tap "Add to Home Screen"'):e?(t+=`1. Click File menu
`,t+='2. Click "Add to Dock"'):(t+=`1. Click the menu button (three dots)
`,t+='2. Click "Install" or "Add to Home Screen"'),alert(t)}let xe=null;const Le=new Set;function co(a="#offline-indicator"){xe=typeof a=="string"?document.querySelector(a):a,ge(!navigator.onLine),window.addEventListener("online",()=>{console.log("[PWA] Back online"),ge(!1),$e(!1)}),window.addEventListener("offline",()=>{console.log("[PWA] Gone offline"),ge(!0),$e(!0)})}function ge(a){xe&&(xe.style.display=a?"block":"none"),document.body.classList.toggle("is-offline",a)}function uo(a){return Le.add(a),a(!navigator.onLine),()=>Le.delete(a)}function $e(a){for(const e of Le)try{e(a)}catch(t){console.error("[PWA] Offline listener error:",t)}}function R(){return navigator.onLine}function po(){confirm("A new version is available. Reload now?")&&fo()}function fo(){B?.waiting&&B.waiting.postMessage({type:"SKIP_WAITING"}),window.location.reload()}async function ho(a={}){const{installButton:e="#install-btn",offlineIndicator:t="#offline-indicator",autoRegisterSW:r=!0}=a;r&&await so(),io(e),co(t),console.log("[PWA] Initialized")}const go="https://api.lupmis4luspa.org/api/spatial_planning",yo={district_id:"1",api_token:"1c46538c712e9b5b"};async function X(a,e={},t={}){const r=`${go}/${a}`,o={...yo,...e};console.log("[RemoteDB] POST",r);try{const s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(o),...t});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const n=await s.json();return console.log("[RemoteDB] POST response:",a,"→",typeof n=="object"?`${Array.isArray(n)?n.length+" items":"object"}`:n),n}catch(s){throw console.error("[RemoteDB] POST failed:",a,s),s}}async function mo(){return X("get_district_boundary.php")}async function bo(){return X("get_layers.php")}async function wo(){return X("get_all_collector_zone_per_district.php")}async function Eo(){return X("get_parcels_per_district.php")}async function vo(){return X("get_all_footprint_per_district.php")}let g=null,M=null,C="addLocation";async function qe(){console.log("[App] Initializing..."),await ho({installButton:"#install-btn",offlineIndicator:"#offline-indicator",autoRegisterSW:!0}),g=new ao("map",{center:[-1.5,7.5],zoom:7,basemap:"osm"}),M=new no(g.getMap()),M.onMeasureComplete(e=>{console.log("[MapTools] Measurement complete:",e),e.type==="polygon"&&e.coordinate&&g?.showDrawnPolygonPopup(e.feature,e.coordinate)}),g.onClick((e,t,r,o)=>{if(console.log("[MapClick] Clicked at:",e.toFixed(4),t.toFixed(4)),console.log("[MapClick] currentMode =",C),C==="draw"){console.log("[MapClick] In draw mode, Select interaction handles clicks");return}let s=null;if(g.getMap().forEachFeatureAtPixel(o.pixel,n=>{if(n.get("_layerType")==="parcel")return s=n,!0}),s){console.log("[MapClick] Clicked on parcel → Edit Attributes"),g.showParcelEditPopup(s,o.coordinate);return}C==="addLocation"&&(r?(console.log("[MapClick] Clicked on marker:",r.getId()),g.selectMarker(r),xo(r)):(console.log("[MapClick] Empty space → Add Location popup"),g.clearSelection(),g.showAddLocationPopup(o.coordinate)))}),g.onDblClick((e,t,r,o)=>{if(!r)return;const s=r.get("_layerType");if(console.log("[App] Double-click on feature, _layerType:",s||"none"),s==="measure_circle")g.showCircleIntersectionPopup(r,o.coordinate);else{if(s==="measure_circle_radius")return;s==="collector_zone"?g.showInfoPopup(r,o.coordinate,{title:"Zone Info",color:"#7c3aed"}):s==="parcel"?g.showInfoPopup(r,o.coordinate,{title:"Parcel Info",color:"#0ea5e9"}):g.showInfoPopup(r,o.coordinate,{title:"Feature Info",color:"#e11d48"})}}),g.onAddLocation(async e=>{console.log("[App] Add location from map popup:",e);try{const t=await zt(e.name,e.lon,e.lat,{description:e.description||null,category:e.category||"default"});console.log("[App] Location added:",e.name,"id:",t.id),await ye(),g?.zoomTo(e.lon,e.lat,14),t.id&&g?.selectMarker(t.id),Y("Location added successfully")}catch(t){console.error("[App] Failed to add location:",t),I("Failed to add location: "+t.message)}}),g.onParcelEdit(async(e,t)=>{const r=t.id||t.parcelid||t.parcel_id;if(console.log("[App] Parcel edit saved:",r,t),!r){console.warn("[App] No parcel ID found in updated properties — skipping local save");return}try{await Vt(r,t),Y("Parcel updated locally")}catch(o){console.error("[App] Failed to save parcel update:",o),I("Failed to save parcel: "+o.message)}});const a=new bt;g.onDrawnPolygonSave(async(e,t)=>{console.log("[App] Drawn polygon attributes saved:",t);try{const r=a.writeGeometry(e.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"}),o=await Jt(r,t);console.log("[App] New parcel inserted with id:",o.id),Y("New parcel saved (pending verification)")}catch(r){console.error("[App] Failed to save new parcel:",r),I("Failed to save parcel: "+r.message)}});try{console.log("[App] Initializing database..."),await qt(),console.log("[App] Database ready");const e=await Ce();console.log("[App] Database status:",e),await Ro(),g?.initEditBar(),Io(),Oo(),Do(),Fo()}catch(e){console.error("[App] Database initialization failed:",e),I("Failed to initialize database. Please refresh the page.");return}To(),await ye(),$t(e=>{if(console.log("[App] Database change:",e),e.table==="locations"&&!e.local&&ye(),e.table==="parcels"){const t=document.getElementById("local-data-stats");t&&!t.classList.contains("d-none")&&lt()}}),uo(e=>{e?console.log("[App] Working offline - data will sync when back online"):(console.log("[App] Back online - syncing data..."),No())}),console.log("[App] Initialized successfully")}function To(){console.log("[initUI] Starting UI initialization...");const a=document.getElementById("export-btn");a&&a.addEventListener("click",ko);const e=document.getElementById("local-data-btn");e&&e.addEventListener("click",()=>lt());const t=document.getElementById("exportGeoJSON-btn");t&&t.addEventListener("click",Po);const r=document.getElementById("status-btn");r&&r.addEventListener("click",Co);const o=document.getElementById("fit-btn");o&&o.addEventListener("click",()=>g?.fitToMarkers());const s=document.getElementById("dock-btn-add-location"),n=document.getElementById("dock-btn-measure-circle"),i=document.getElementById("dock-btn-measure-line"),l=document.getElementById("dock-btn-measure-area"),c=document.getElementById("dock-btn-draw"),u=document.getElementById("dock-btn-clear");console.log("[initUI] Buttons found:",{addLocation:!!s,measureCircle:!!n,measureLine:!!i,measureArea:!!l,draw:!!c,clear:!!u});const p=[s,n,i,l,c],d=(f,y)=>{switch(console.log("[setMode] Changing mode from",C,"to",f),C=f,console.log("[setMode] currentMode is now:",C),p.forEach(b=>{b&&b.classList.toggle("active",b===y)}),M?.deactivate(),f!=="draw"&&g?.setEditMode(!1),f!=="addLocation"&&g?.hideAddLocationPopup(),f){case"measureCircle":M?.startCircleMeasure();break;case"measureLine":M?.startLineMeasure();break;case"measureArea":M?.startAreaMeasure();break;case"draw":g?.setEditMode(!0);break}};s&&s.addEventListener("click",()=>{console.log("[Button] Add Location clicked"),d("addLocation",s)}),n&&n.addEventListener("click",()=>{console.log("[Button] Circle clicked, currentMode is:",C),C==="measureCircle"?d("addLocation",s):d("measureCircle",n)}),i&&i.addEventListener("click",()=>{console.log("[Button] Line clicked, currentMode is:",C),C==="measureLine"?d("addLocation",s):d("measureLine",i)}),l&&l.addEventListener("click",()=>{console.log("[Button] Area clicked, currentMode is:",C),C==="measureArea"?d("addLocation",s):d("measureArea",l)}),c&&c.addEventListener("click",()=>{console.log("[Button] Draw clicked, currentMode is:",C),C==="draw"?d("addLocation",s):d("draw",c)}),u&&u.addEventListener("click",()=>{if(M?.clearMeasurements(),C.startsWith("measure"))switch(M?.deactivate(),C){case"measureCircle":M?.startCircleMeasure();break;case"measureLine":M?.startLineMeasure();break;case"measureArea":M?.startAreaMeasure();break}})}async function ye(){try{console.log("[App] Loading locations...");const a=await nt();console.log("[App] Locations loaded:",a),Lo(a),g&&(g.clearMarkers(),a.length>0&&(g.addMarkers(a),console.log("[App] Added",a.length,"markers to map")));const e=document.getElementById("location-count");e&&(e.textContent=a.length)}catch(a){console.error("[App] Failed to load locations:",a)}}function xo(a){const e=a.get("name"),t=a.get("description"),r=a.get("category"),o=a.get("lon")||a.get("longitude"),s=a.get("lat")||a.get("latitude");console.log("[App] Selected location:",{name:e,description:t,category:r,lon:o,lat:s})}function Lo(a){const e=document.getElementById("locations-list");if(!e)return;const t=document.getElementById("location-count-mobile");if(t&&(t.textContent=a.length),a.length===0){e.innerHTML=`
<div class="text-center text-muted py-4">
<p class="mb-0">No locations yet.</p>
<small>Click the map or fill the form above!</small>
</div>
`;return}const r={water:"💧",school:"🏫",health:"🏥",market:"🏪",default:"📍",other:"📌"};e.innerHTML=a.map(o=>{const s=r[o.category]||"📍";return`
<a href="#" class="list-group-item list-group-item-action location-item py-2"
data-id="${o.id}" data-lon="${o.longitude}" data-lat="${o.latitude}">
<div class="d-flex w-100 justify-content-between align-items-start">
<div>
<h6 class="mb-1">${s} ${$(o.name)}</h6>
<small class="text-muted font-monospace">${o.latitude.toFixed(5)}, ${o.longitude.toFixed(5)}</small>
</div>
<span class="badge badge-${o.category}">${o.category}</span>
</div>
${o.description?`<small class="text-secondary d-block mt-1">${$(o.description)}</small>`:""}
</a>
`}).join(""),e.querySelectorAll(".location-item").forEach(o=>{o.addEventListener("click",s=>{s.preventDefault();const n=parseFloat(o.dataset.lon),i=parseFloat(o.dataset.lat),l=parseInt(o.dataset.id);g?.zoomTo(n,i,14),g?.selectMarker(l)})})}async function lt(){const a=document.getElementById("local-data-stats"),e=document.getElementById("local-data-tbody");if(!(!a||!e))try{const t=await to();e.innerHTML=t.map(r=>`
<tr>
<td class="ps-3">
<a href="#" class="table-name-link" data-table="${$(r.name)}">${$(r.name)}</a>
</td>
<td class="text-end pe-3"><span class="badge bg-secondary">${r.count}</span></td>
</tr>
`).join(""),a.classList.remove("d-none"),e.querySelectorAll(".table-name-link").forEach(r=>{r.addEventListener("click",o=>{o.preventDefault(),So(r.dataset.table)})})}catch(t){console.error("[App] Failed to load table stats:",t),e.innerHTML='<tr><td colspan="2" class="text-danger ps-3">Failed to load</td></tr>',a.classList.remove("d-none")}}async function So(a){const e=document.getElementById("tableContentModalLabel"),t=document.getElementById("table-content-body"),r=document.getElementById("table-content-info");e.textContent=`Table: ${a}`,t.innerHTML=`
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
`,r.textContent="",new Ke(document.getElementById("tableContentModal")).show();try{const{columns:s,rows:n}=await oo(a);if(n.length===0){t.innerHTML='<div class="text-center text-muted py-4">Table is empty</div>',r.textContent="0 rows";return}const i=s.map(c=>`<th class="text-nowrap">${$(c)}</th>`).join(""),l=n.map(c=>`<tr>${s.map(p=>{let d=c[p];if(d==null)return'<td class="text-muted fst-italic">NULL</td>';d=String(d);const f=d.length>120?d.substring(0,120)+"...":d;return`<td>${$(f)}</td>`}).join("")}</tr>`).join("");t.innerHTML=`
<div class="table-responsive">
<table class="table table-sm table-striped table-hover mb-0" style="font-size:12px;">
<thead class="table-light">
<tr>${i}</tr>
</thead>
<tbody>${l}</tbody>
</table>
</div>
`,r.textContent=`${n.length}${n.length>=200?"+":""} row(s), ${s.length} column(s)`}catch(s){console.error("[App] Failed to load table content:",s),t.innerHTML=`<div class="text-danger text-center py-4">Failed to load: ${$(s.message)}</div>`}}async function ko(){try{await Zt("lupmis-backup.sqlite3"),Y("Database exported successfully")}catch(a){console.error("[App] Export failed:",a),I("Export failed: "+a.message)}}async function Po(){try{const a=await eo(),e=new Blob([JSON.stringify(a,null,2)],{type:"application/json"}),t=URL.createObjectURL(e),r=document.createElement("a");r.href=t,r.download="locations.geojson",r.click(),URL.revokeObjectURL(t),Y(`Exported ${a.features.length} location(s)`)}catch(a){console.error("[App] GeoJSON Export failed:",a),I("GeoJSON Export failed: "+a.message)}}async function Co(){try{const a=await Ce(),e=document.getElementById("status-content");e&&(e.innerHTML=`
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="fw-semibold">Ready:</td>
<td><span class="badge ${a.ready?"bg-success":"bg-danger"}">${a.ready?"Yes":"No"}</span></td>
</tr>
<tr>
<td class="fw-semibold">Online:</td>
<td><span class="badge ${R()?"bg-success":"bg-warning"}">${R()?"Yes":"Offline"}</span></td>
</tr>
<tr>
<td class="fw-semibold">Database:</td>
<td><code>${a.databasePath||"N/A"}</code></td>
</tr>
<tr>
<td class="fw-semibold">Tables:</td>
<td>${a.tables.map(r=>`<span class="badge bg-secondary me-1">${r}</span>`).join("")}</td>
</tr>
<tr>
<td class="fw-semibold">Locations:</td>
<td><span class="badge bg-primary">${a.locationCount}</span></td>
</tr>
</tbody>
</table>
`),new Ke(document.getElementById("statusModal")).show()}catch(a){console.error("[App] Failed to get status:",a),I("Failed to get status")}}function ct(a){return a.replace(/^\(+/,"").replace(/\)+$/,"").split(",").map(e=>{const[t,r]=e.trim().split(/\s+/).map(Number);return[t,r]})}function _o(a){return{type:"Polygon",coordinates:a.trim().replace(/^POLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split("),(").map(ct)}}function Ao(a){return{type:"MultiPolygon",coordinates:a.trim().replace(/^MULTIPOLYGON\s*\(\s*/i,"").replace(/\s*\)$/,"").split(")),((").map(o=>o.replace(/^\(+/,"").replace(/\)+$/,"").split("),(").map(ct))}}function ue(a){if(!a)return null;const e=a.trim().toUpperCase();return e.startsWith("MULTIPOLYGON")?Ao(a):e.startsWith("POLYGON")?_o(a):(console.warn("[App] Unsupported WKT type:",e.substring(0,30)),null)}function Mo(a){if(!a?.success||!a?.data?.boundary)return console.warn("[App] API response missing success or boundary data"),null;const{boundary:e,districtid:t,district_name:r}=a.data,o=ue(e);return{type:"FeatureCollection",features:[{type:"Feature",properties:{districtid:t,district_name:r},geometry:o}]}}function ze(a){if(!Array.isArray(a)||a.length===0)return null;const e=[];for(const t of a){const r=t.polygon||t.boundary,o=ue(r);if(!o)continue;const s={_layerType:"collector_zone"};for(const[n,i]of Object.entries(t))n==="polygon"||n==="boundary"||(s[n]=i);e.push({type:"Feature",properties:s,geometry:o})}return e.length===0?null:{type:"FeatureCollection",features:e}}async function Io(){const a="district_boundary",t={strokeColor:"#e11d48",strokeWidth:2.5,fillColor:"rgba(225,29,72,0.08)"},r=g?.getLayerGroup(1)||null;function o(n){if(!n)return;const i=n.getLayers(),l=[];i.forEach(c=>{c.get("title")==="District Boundary"&&l.push(c)}),l.forEach(c=>i.remove(c))}function s(n){if(!n||!g)return;const i=n.getSource().getExtent();i&&i[0]!==1/0&&g.getMap().getView().fit(i,{padding:[40,40,40,40],duration:600})}try{const n=await it(a);if(n){console.log("[App] District boundary loaded from local cache");const i=g?.addGeoJSONLayer(n,"District Boundary",t,r);s(i)}if(R()){console.log("[App] Fetching district boundary from API...");const i=await mo(),l=Mo(i);if(!l){console.warn("[App] Could not convert API response to GeoJSON");return}console.log("[App] District boundary:",l.features[0]?.properties?.district_name,"→",l.features[0]?.geometry?.coordinates?.length,"polygon(s)"),await st(a,l),n&&o(r||g?.getOverlayGroup());const c=g?.addGeoJSONLayer(l,"District Boundary",t,r);s(c),console.log("[App] District boundary loaded from API")}else n||console.log("[App] District boundary not available — offline and no local cache")}catch(n){console.error("[App] Failed to load district boundary:",n)}}async function Oo(){const e={strokeColor:"#7c3aed",strokeWidth:1.5,fillColor:"rgba(124,58,237,0.12)"},t=g?.getLayerGroup(1)||null;console.log("[App] loadCollectorZones — adminGroup:",t?t.get("title"):"null");const r={type:"FeatureCollection",features:[]},o=g?.addGeoJSONLayer(r,"Zones",e,t);if(!o){console.warn("[App] Could not create Zones layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&I("No collector zones available locally. Connect to the internet to download zone data.")});function s(n){const i=new ce().readFeatures(n,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(i)}try{const n=await Kt();if(n){const i=ze(n);i&&(console.log("[App] Collector zones loaded from local cache:",i.features.length,"zones"),s(i))}if(R()){console.log("[App] Fetching collector zones from API...");const i=await wo();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getCollectorZones API response invalid:",i);return}const l=i.data;console.log("[App] Collector zones from API:",l.length,"entries"),await Ut(l);const c=ze(l);if(!c){console.warn("[App] Could not convert zones to GeoJSON");return}s(c),console.log("[App] Collector zones updated from API:",c.features.length,"zones")}else n||console.log("[App] Collector zones not available — offline and no local cache")}catch(n){console.error("[App] Failed to load collector zones:",n)}}function Ge(a){if(!Array.isArray(a)||a.length===0)return null;const e=[];for(const t of a){const r=t.polygon||t.boundary||t.geom||t.wkt,o=ue(r);if(!o)continue;const s={_layerType:"parcel"};for(const[n,i]of Object.entries(t))n==="polygon"||n==="boundary"||n==="geom"||n==="wkt"||(s[n]=i);e.push({type:"Feature",properties:s,geometry:o})}return e.length===0?null:{type:"FeatureCollection",features:e}}async function Do(){const e={strokeColor:"#0ea5e9",strokeWidth:1.5,fillColor:"rgba(14,165,233,0.12)"},t=g?.getLayerGroup(4)||null;console.log("[App] loadParcels — landUseGroup:",t?t.get("title"):"null");const r={type:"FeatureCollection",features:[]},o=g?.addGeoJSONLayer(r,"Parcels",e,t);if(!o){console.warn("[App] Could not create Parcels layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&I("No parcels available locally. Connect to the internet to download parcel data.")});function s(n){const i=new ce().readFeatures(n,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(i)}try{const n=await Ht();if(n){const i=Ge(n);i&&(console.log("[App] Parcels loaded from local cache:",i.features.length,"parcels"),s(i))}if(R()){console.log("[App] Fetching parcels from API...");const i=await Eo();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getDistrictParcels API response invalid:",i);return}const l=i.data;console.log("[App] Parcels from API:",l.length,"entries"),l.length>0&&console.log("[App] First parcel keys:",Object.keys(l[0])),await Wt(l);const c=Ge(l);if(!c){console.warn("[App] Could not convert parcels to GeoJSON");return}s(c),console.log("[App] Parcels updated from API:",c.features.length,"parcels")}else n||console.log("[App] Parcels not available — offline and no local cache")}catch(n){console.error("[App] Failed to load parcels:",n)}}function Ue(a){if(!Array.isArray(a)||a.length===0)return null;const e=["polygon","boundary","geom","wkt","footprint"],t=[];for(const r of a){const o=r.polygon||r.boundary||r.geom||r.wkt||r.footprint;let s;if(typeof o=="object"&&o!==null&&o.type?s=o:s=ue(o),!s)continue;const n={_layerType:"building_footprint"};for(const[i,l]of Object.entries(r))e.includes(i)||typeof l=="object"&&l!==null||(n[i]=l);t.push({type:"Feature",properties:n,geometry:s})}return t.length===0?null:{type:"FeatureCollection",features:t}}async function Fo(){const e={strokeColor:"#8b6f47",strokeWidth:1,fillColor:"rgba(139,111,71,0.18)"},t=g?.getLayerGroup(5)||null;console.log("[App] loadBuildingFootprints — physInfraGroup:",t?t.get("title"):"null");const r={type:"FeatureCollection",features:[]},o=g?.addGeoJSONLayer(r,"Building footprints",e,t);if(!o){console.warn("[App] Could not create Building footprints layer");return}o.setVisible(!1),o.on("change:visible",()=>{o.getVisible()&&o.getSource().getFeatures().length===0&&I("No building footprints available locally. Connect to the internet to download footprint data.")});function s(n){const i=new ce().readFeatures(n,{featureProjection:"EPSG:3857"});o.getSource().clear(),o.getSource().addFeatures(i)}try{const n=await Yt();if(n){const i=Ue(n);i&&(console.log("[App] Building footprints loaded from local cache:",i.features.length,"footprints"),s(i))}if(R()){console.log("[App] Fetching building footprints from API...");const i=await vo();if(!i?.success||!Array.isArray(i?.data)){console.warn("[App] getBuildingFootprints API response invalid:",i);return}const l=i.data;console.log("[App] Building footprints from API:",l.length,"entries"),l.length>0&&console.log("[App] First footprint keys:",Object.keys(l[0])),await Qt(l);const c=Ue(l);if(!c){console.warn("[App] Could not convert building footprints to GeoJSON");return}s(c),console.log("[App] Building footprints updated from API:",c.features.length,"footprints")}else n||console.log("[App] Building footprints not available — offline and no local cache")}catch(n){console.error("[App] Failed to load building footprints:",n)}}async function Ro(){const a="layer_categories";function e(t){const r=[...t].sort((o,s)=>s.id-o.id);for(const o of r)g?.addLayerGroup(o.id,o.name,o.description||"");console.log("[App] Created",t.length,"layer groups on map")}try{const t=await it(a);if(t&&(console.log("[App] Layer categories loaded from local cache:",t.length,"entries"),e(t)),R()){console.log("[App] Fetching layer categories from API...");const r=await bo();if(!r?.success||!Array.isArray(r?.data)){console.warn("[App] getLayers API response invalid:",r);return}const o=r.data;if(console.log("[App] Layer categories from API:",o.length,"entries"),await st(a,o),t){const s=g?.getOverlayGroup()?.getLayers();if(s){const n=[];s.forEach(i=>{i.get("layerId")!==void 0&&n.push(i)}),n.forEach(i=>s.remove(i))}}e(o),console.log("[App] Layer categories refreshed from API")}else t||console.log("[App] Layer categories not available — offline and no local cache")}catch(t){console.error("[App] Failed to load layer categories:",t)}}async function No(){if(!R()){console.log("[App] Cannot sync - offline");return}console.log("[App] Sync placeholder - implement based on your backend")}function $(a){const e=document.createElement("div");return e.textContent=a,e.innerHTML}function I(a){const e=document.getElementById("error-message");e?(e.querySelector(".message-text").textContent=a,e.classList.remove("d-none"),setTimeout(()=>{e.classList.add("d-none")},5e3)):console.error(a)}function Y(a){const e=document.getElementById("success-message");e?(e.querySelector(".message-text").textContent=a,e.classList.remove("d-none"),setTimeout(()=>{e.classList.add("d-none")},3e3)):console.log(a)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",qe):qe();
//# sourceMappingURL=index-2WHoRhxp.js.map