-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
71 lines (67 loc) · 5.75 KB
/
Copy pathapp.js
File metadata and controls
71 lines (67 loc) · 5.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const $=x=>document.getElementById(x);let db;
const fa='۰۱۲۳۴۵۶۷۸۹',ar='٠١٢٣٤٥٦٧٨٩';
function nd(s=''){return String(s).replace(/[۰-۹]/g,d=>fa.indexOf(d)).replace(/[٠-٩]/g,d=>ar.indexOf(d)).trim()}
function nl(s=''){return String(s).trim().replace('ي','ی').replace('ك','ک')}
function canonicalKey(){return [nd(p1.value),nl(pl.value),nd(p2.value),nd(pc.value)].join('|')}
function plateDisplay(r){return `${r.pc} | ${r.p2} ${r.pl} ${r.p1}`}
function odb(){return new Promise((ok,no)=>{let q=indexedDB.open('vehicleLog',1);q.onupgradeneeded=()=>{let s=q.result.createObjectStore('obs',{keyPath:'id',autoIncrement:true});s.createIndex('plate','plate')};q.onsuccess=()=>{db=q.result;ok()};q.onerror=()=>no(q.error)})}
function all(){return new Promise(ok=>{let q=db.transaction('obs').objectStore('obs').getAll();q.onsuccess=()=>ok(q.result)})}
function by(k){return new Promise(ok=>{let q=db.transaction('obs').objectStore('obs').index('plate').getAll(k);q.onsuccess=()=>ok(q.result)})}
function add(o){return new Promise(ok=>{let q=db.transaction('obs','readwrite').objectStore('obs').add(o);q.onsuccess=()=>ok(q.result)})}
function gps(){return new Promise((ok,no)=>navigator.geolocation.getCurrentPosition(p=>ok([p.coords.latitude,p.coords.longitude,p.coords.accuracy]),no,{enableHighAccuracy:true,timeout:12000}))}
tm.onchange=()=>dt.disabled=tm.value!=='manual';
lm.onchange=()=>loc.disabled=lm.value!=='manual';
parse.onclick=()=>{
let t=nd(quick.value).replace(/،/g,' ').replace(/\s+/g,' ');
let m=t.match(/پلاک\s*([0-9]{2})\s*[\|\-]?\s*([0-9]{3})\s*([آ-ی])\s*([0-9]{2})/);
if(m){pc.value=m[1];p2.value=m[2];pl.value=m[3];p1.value=m[4]}
else{
m=t.match(/پلاک\s*([0-9]{2})\s*([آ-ی])\s*([0-9]{3})\s*(?:(?:ایران\s*کد)|(?:کد\s*ایران)|ایران)?\s*([0-9]{2})/);
if(m){p1.value=m[1];pl.value=m[2];p2.value=m[3];pc.value=m[4]}
}
let cs=['سفید','مشکی','سیاه','نقرهای','خاکستری','قرمز','آبی','سبز','زرد','قهوهای'];
let c=cs.find(x=>t.includes(x));if(c)color.value=c;
if(/موتور/.test(t))type.value='motorcycle';
else if(/خودرو|ماشین|پژو|پراید|سمند|دنا|تیبا|کوییک/.test(t))type.value='car';
if(t.includes('پژو'))brand.value='Peugeot';
let mm=t.match(/\b(207|206|405|2008|125)\b/);if(mm)model.value=mm[1];
status.textContent=m?'پلاک استخراج شد؛ قبل از ثبت بررسی کن.':'پلاک کامل تشخیص داده نشد؛ دستی تکمیل کن.';
}
save.onclick=async()=>{
status.textContent='در حال ثبت...';
if(!/^\d{2}$/.test(nd(p1.value))||!nl(pl.value)||!/^\d{3}$/.test(nd(p2.value))||!/^\d{2}$/.test(nd(pc.value))){
status.textContent='فرمت پلاک باید به شکل 68 | 788 ج 78 باشد.';return;
}
let prior=await by(canonicalKey()),lat=null,lon=null,acc=null;
if(lm.value==='auto'){
try{[lat,lon,acc]=await gps()}catch(e){status.textContent='GPS در دسترس نیست؛ Location را اجازه بده یا Manual/None را انتخاب کن.';return}
}else if(lm.value==='manual'){
let a=nd(loc.value).split(',').map(Number);
if(a.length!==2||a.some(Number.isNaN)){status.textContent='مختصات را lat, lon وارد کن.';return}
[lat,lon]=a;
}
let observed=tm.value==='auto'?new Date().toISOString():new Date(dt.value).toISOString();
let o={type:type.value,brand:brand.value||null,model:model.value||null,variant:variant.value||null,color:color.value||null,p1:nd(p1.value),pl:nl(pl.value),p2:nd(p2.value),pc:nd(pc.value),plate:canonicalKey(),observedAt:observed,timeSource:tm.value,lat,lon,accuracy:acc,locationSource:lm.value,notes:notes.value||null,hasPhoto:!!photo.files[0],createdAt:new Date().toISOString()};
await add(o);
if(prior.length){
alertBox.classList.remove('hide');
let s=prior.sort((a,b)=>new Date(b.observedAt)-new Date(a.observedAt));
alertText.innerHTML=`<b>${prior.length} ثبت قبلی</b><br>آخرین: ${new Date(s[0].observedAt).toLocaleString('fa-IR')}<br>${s[0].lat!=null?'مکان: '+s[0].lat.toFixed(5)+', '+s[0].lon.toFixed(5):'بدون مکان'}`;
if(navigator.vibrate)navigator.vibrate([150,80,150]);
}else alertBox.classList.add('hide');
status.textContent=prior.length?'ثبت شد — پلاک تکراری است.':'ثبت شد — اولین مشاهده این پلاک.';
render();
}
async function render(){
let a=await all(),q=search.value.toLowerCase();
a.sort((x,y)=>new Date(y.observedAt)-new Date(x.observedAt));
if(q)a=a.filter(r=>plateDisplay(r).toLowerCase().includes(q)||(r.brand||'').toLowerCase().includes(q)||(r.model||'').toLowerCase().includes(q));
rows.innerHTML=a.map(r=>`<div class="row"><strong>${plateDisplay(r)}</strong>${r.type==='motorcycle'?'موتورسیکلت':'خودرو'} · ${[r.brand,r.model,r.color].filter(Boolean).join(' · ')}<div class="meta">${new Date(r.observedAt).toLocaleString('fa-IR')} ${r.lat!=null?'· '+r.lat.toFixed(5)+', '+r.lon.toFixed(5):''}</div></div>`).join('')||'<span class="meta">هنوز رکوردی نیست.</span>';
}
search.oninput=render;
exp.onclick=()=>dlg.showModal();
closeDlg.onclick=()=>dlg.close();
function dl(n,b){let a=document.createElement('a');a.href=URL.createObjectURL(b);a.download=n;a.click()}
json.onclick=async()=>{let a=await all();dl('vehicle-log.json',new Blob([JSON.stringify(a,null,2)],{type:'application/json'}))}
csv.onclick=async()=>{let a=await all(),c=['id','type','brand','model','variant','color','pc','p2','pl','p1','observedAt','timeSource','lat','lon','locationSource','notes','createdAt'],e=v=>`"${String(v??'').replaceAll('"','""')}"`;dl('vehicle-log.csv',new Blob(['\ufeff'+[c.join(','),...a.map(r=>c.map(k=>e(r[k])).join(','))].join('\n')],{type:'text/csv'}))}
(async()=>{await odb();render();if('serviceWorker'in navigator)navigator.serviceWorker.register('./sw.js')})()