Spaces:
Running
Running
| const ACCESS_PASSWORDS=["12345","2030"]; | |
| const EXPORT_COLUMNS=[ | |
| "التصنيف","نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة", | |
| "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة","اسم الدعم الفني","الحالة" | |
| ]; | |
| const DISPLAY_COLUMNS=[ | |
| "التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشتغل", | |
| "رقم الجوال","رقم الهوية ID","رقم الجهاز","تاريخ اليوم بالميلادي","الحالة","اسم الدعم الفني" | |
| ]; | |
| const DISPLAY_TO_BASE={ | |
| "التصنيف":"التصنيف", | |
| "نوع المشكلة":"نوع المشكلة", | |
| "المنطقة":"المنطقة", | |
| "اسم المسح":"المسح", | |
| "اسم المشتغل":"اسم صاحب المشكلة", | |
| "رقم الجوال":"رقم الجوال", | |
| "رقم الهوية ID":"رقم الهوية", | |
| "رقم الجهاز":"رقم الجهاز", | |
| "تاريخ اليوم بالميلادي":"تاريخ اليوم بالميلادي", | |
| "الحالة":"الحالة", | |
| "اسم الدعم الفني":"اسم الدعم الفني" | |
| }; | |
| const FIELD_ALIASES={ | |
| "نوع المشكلة":["نوع المشكله","نوع المشكلة","المشكلة","نوع-المشكلة","نوع المشكلة"], | |
| "وقت حدوث المشكلة":["وقت حدوث المشكله","وقت حدوث المشكلة","وقت المشكلة","وقت حدوث","وقت حدوث المشكله:","وقت حدوث المشكله :"], | |
| "اسم صاحب المشكلة":["اسم صاحب المشكله","اسم صاحب المشكلة","اسم صاحب البلاغ","الاسم"], | |
| "رقم الهوية":["رقم الهويه","رقم الهوية","الهوية","هوية"], | |
| "رقم الجهاز":["رقم الجهاز","الجهاز"], | |
| "رقم الجوال":["رقم الجوال","الجوال","الهاتف","جوال"], | |
| "المسح":["المسح","اسم المسح"], | |
| "المنطقة":["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"] | |
| }; | |
| const START_LABELS=Array.from(new Set(Object.values(FIELD_ALIASES).flat())); | |
| const CLASS_RULES={ | |
| "استفسار":["استفسار","سؤال","استعلام","معلومة","استفسارات"], | |
| "إضافة أجهزة":["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"], | |
| "الاستمارة":["الاستمارة","استمارة","النموذج","نموذج","الفورم","تعليق الاستمارة","لا استطيع اكمال الاستمارة","التعبئة"], | |
| "التقييم":["التقييم","تقييم","feedback","survey","رضا","نجوم"], | |
| "الخرائط":["الخرائط","خرائط","map","gps","تحديد الموقع","احداثيات","إحداثيات","الموقع الجغرافي"], | |
| "السوتي":["السوتي","سوتي","soti","soti assist","mobicontrol","soti mobicontrol"], | |
| "الشبكة":["الشبكة","شبكة","نت","انترنت","إنترنت","wifi","واي فاي","4g","5g","ضعف الشبكة","stc","mobily","زين","weak signal","no signal"], | |
| "النسخة":["النسخة","نسخة","الإصدار","اصدار","version","build","release","تحديث نسخة","ترقية النسخة"], | |
| "النظام المكتبي":["النظام المكتبي","نسخة ويندوز","ويندوز","windows","pc app","برنامج المكتب","التطبيق على الكمبيوتر","الديسكتوب"], | |
| "تسجيل دخول":["تسجيل دخول","تسجيل الدخول","login","signin","رفض تسجيل الدخول","لا يقبل الدخول","اسم المستخدم","كلمة المرور","نسيت كلمة السر","إعادة تعيين"], | |
| "تفعيل حساب":["تفعيل حساب","تفعيل","activation","activate","رمز التفعيل","كود التفعيل"], | |
| "تناقل البيانات":["تناقل البيانات","ترحيل البيانات","مزامنة","sync","مزامنه","نقل البيانات","رفع البيانات","sync failed","المزامنة"], | |
| "صيانة وتحديث الأجهزة":["صيانة","تحديث الأجهزة","تحديث جهاز","ترقية الجهاز","اعطال الجهاز","تصليح","صيانة وتحديث الأجهزة","صيانة الجهاز"] | |
| }; | |
| const CLASS_PRIORITY=[ | |
| "صيانة وتحديث الأجهزة","إضافة أجهزة","تسجيل دخول","تفعيل حساب","الاستمارة","التقييم", | |
| "الخرائط","السوتي","الشبكة","النسخة","النظام المكتبي","تناقل البيانات","استفسار" | |
| ]; | |
| const TICKET_SEP=/\n\s*(?:\n{2,}|—+|-{3,}|={3,}|🔴+)\s*\n/; | |
| const arabicDigitsMap={"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"}; | |
| function normalizeText(s){ | |
| if(typeof s!=="string")return""; | |
| return s.replace(/\r\n/g,"\n") | |
| .replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ") | |
| .replace(/[٠-٩]/g,d=>arabicDigitsMap[d]) | |
| .replace(/[ــ]+/g,"") | |
| .replace(/[ \t]+\n/g,"\n") | |
| .replace(/\n{3,}/g,"\n\n") | |
| .trim(); | |
| } | |
| function lettersOnly(ar){return(ar||"").replace(/[^A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s]/g,"").replace(/\s{2,}/g," ").trim()} | |
| function alnumAr(s){return(s||"").replace(/[^0-9A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s\-\._/]/g,"").replace(/\s{2,}/g," ").trim()} | |
| function digitsOnly(s){return(s||"").replace(/\D+/g,"")} | |
| const LABEL_FIXES=[ | |
| [/(^|\n)\s*نوع\s*المشكله/gi,"$1نوع المشكلة"], | |
| [/(^|\n)\s*وقت\s*حدوث\s*المشكله/gi,"$1وقت حدوث المشكلة"], | |
| [/(^|\n)\s*اسم\s*صاحب\s*المشكله/gi,"$1اسم صاحب المشكلة"], | |
| [/(^|\n)\s*رقم\s*الهويه/gi,"$1رقم الهوية"], | |
| [/(^|\n)\s*المنطقه/gi,"$1المنطقة"], | |
| [/(^|\n)\s*اسم\s*المنطقة/gi,"$1المنطقة"], | |
| [/(^|\n)\s*اسم\s*المسح/gi,"$1المسح"], | |
| [/(^|\n)\s*الهاتف/gi,"$1رقم الجوال"], | |
| [/(^|\n)\s*جوال/gi,"$1رقم الجوال"] | |
| ]; | |
| function fixLabels(s){let t=s;LABEL_FIXES.forEach(([re,rep])=>t=t.replace(re,rep));return t} | |
| const H_MONTHS=["محرم","صفر","ربيع الأول","ربيع الاول","ربيع الآخر","ربيع الاخر","جمادى الأولى","جمادى الاولى","جمادى الآخرة","جمادى الاخرة","رجب","شعبان","رمضان","شوال","ذو القعدة","ذو القعده","ذو الحجة","ذو الحجه"]; | |
| function monthIndexHijri(name){ | |
| const i=H_MONTHS.findIndex(m=>new RegExp("^"+m+"$","i").test(name.trim())); | |
| if(i<0)return-1; | |
| const map={0:1,1:2,2:3,3:3,4:4,5:4,6:5,7:5,8:6,9:6,10:7,11:8,12:9,13:10,14:11,15:11,16:12,17:12}; | |
| return map[i]||-1; | |
| } | |
| function hijriToGregorian(hy,hm,hd){ | |
| const jd=Math.floor((11*hy+3)/30)+354*hy+30*hm-Math.floor((hm-1)/2)+hd+1948440-385; | |
| let l=jd+68569; | |
| let n=Math.floor(4*l/146097);l=l-Math.floor((146097*n+3)/4); | |
| let i=Math.floor(4000*(l+1)/1461001);l=l-Math.floor(1461*i/4)+31; | |
| let j=Math.floor(80*l/2447);const d=l-Math.floor(2447*j/80); | |
| l=Math.floor(j/11);const m=j+2-12*l;const y=100*(ن-49)+i+l; | |
| return[y,m,d]; | |
| } | |
| function isTimeOnly(t){ | |
| const a=/(^|\s)\d{1,2}\s*(?:[:٫\.\-]\d{2})\s*(?:ص|صباح(?:اً|ا)?|am|م|مساء|pm)?($|\s)/i.test(t); | |
| const b=/(^|\s)\d{1,2}\s*(?:ص|صباح(?:اً|ا)?|am|م|مساء|pm)($|\s)/i.test(t); | |
| const c=/الساعة\s*\d{1,2}(?:[:٫\.\-]\d{2})?/i.test(t); | |
| return a||b||c; | |
| } | |
| function isRelativeDatePhrase(t){ | |
| return /(أمس|من\s*أمس|من\s*امس|اليوم|غدًا|غدا|بكرة|بعد\s*بكرة|قبل\s*\d+\s*(?:دقيقة|دقايق|ساع(?:ة|ات)|يوم|أيام)|الآن|الحين|قبل\s*شوي)/i.test(t); | |
| } | |
| function detectHijriDate(str){ | |
| const t=normalizeText(str); | |
| let m=t.match(/(\d{1,2})\s+([^\s]+)\s+(\d{3,4})\s*(هـ|ه|هجري)?/i); | |
| if(m){const d=+m[1];const hm=monthIndexHijri(m[2]);const y=+m[3];if(hm>=1&&hm<=12)return{hy:y,hm:hm,hd:d}} | |
| m=t.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{3,4})\s*(هـ|ه|هجري)/i); | |
| if(m)return{hy:+m[3],hm:+m[2],hd:+m[1]}; | |
| return null; | |
| } | |
| function normalizeDateOnly(raw){ | |
| const t=normalizeText(raw); | |
| if(isTimeOnly(t)||isRelativeDatePhrase(t))return""; | |
| const hj=detectHijriDate(t); | |
| if(hj){const[gy,gm,gd]=hijriToGregorian(hj.hy,hj.hm,hj.hd);return`${String(gy).padStart(4,"0")}-${String(gm).padStart(2,"0")}-${String(gd).padStart(2,"0")}`} | |
| const m=t.match(/(\d{1,4})[\/\-](\d{1,2})[\/\-](\d{1,4})/); | |
| if(m){ | |
| let a=+m[1],b=+m[2],c=+m[3],y,mo,d; | |
| if(String(m[1]).length===4){y=a;mo=b;d=c} | |
| else if(String(m[3]).length===4){y=c;mo=b;d=a} | |
| else if(a>31){y=a;mo=b;d=c} | |
| else if(c>31){y=c;mo=b;d=a} | |
| else{y=c;mo=b;d=a} | |
| if(y<100)y+=2000; | |
| if(mo>12&&d<=12){const tmp=mo;mo=d;d=tmp} | |
| if(mo<1||mo>12||d<1||d>31)return""; | |
| return`${String(y).padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).padStart(2,"0")}` | |
| } | |
| return""; | |
| } | |
| function findStartsByLabels(text,labels){ | |
| const lblRe=labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|"); | |
| const re=new RegExp(`(^|\\n)\\s*(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:${lblRe})(?:\\s*[::]|\\s+)`,"gi"); | |
| const idxs=[];let m;while((m=re.exec(text))){idxs.push(m.index+(m[1]?m[1].length:0))}return idxs; | |
| } | |
| function findAfterLabel(text,labels){ | |
| const hay="\n"+normalizeText(text)+"\n"; | |
| for(const rawLbl of labels){ | |
| const lbl=rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); | |
| let m=hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`,"i"));if(m)return m[1].trim(); | |
| m=hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`,"i"));if(m)return m[1].trim(); | |
| } | |
| return""; | |
| } | |
| function findBlockAfterLabel(text,labels,allLabels=START_LABELS){ | |
| const hay="\n"+normalizeText(text)+"\n"; | |
| const esc=s=>s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); | |
| const lblAlt=labels.map(esc).join("|"); | |
| const allAlt=allLabels.map(esc).join("|"); | |
| const re=new RegExp(`(?:^|\\n)\\s*(?:${lblAlt})\\s*(?::|:|\\s)\\s*([\\s\\S]*?)(?=\\n\\s*(?:${allAlt})\\s*(?::|:|\\s)|$)`,"i"); | |
| const m=hay.match(re); | |
| return m?m[1].trim():""; | |
| } | |
| function splitTickets(raw){ | |
| const text=normalizeText(fixLabels(raw)); | |
| if(!text)return[]; | |
| if(TICKET_SEP.test(text))return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean); | |
| const niu=findStartsByLabels(text,["نوع المشكلة","نوع المشكله"]).sort((a,b)=>a-b); | |
| if(niu.length>=2){ | |
| const parts=[]; | |
| for(let i=0;i<niu.length;i++){ | |
| const s=niu[i]; | |
| const e=i+1<niu.length?niu[i+1]:text.length; | |
| const slice=text.slice(s,e).trim(); | |
| if(slice)parts.push(slice); | |
| } | |
| if(parts.length)return parts; | |
| } | |
| return[text]; | |
| } | |
| function extractFields(ticketText){ | |
| const text=normalizeText(fixLabels(ticketText)); | |
| const out={"نوع المشكلة":"","وقت حدوث المشكلة":"","اسم صاحب المشكلة":"","رقم الهوية":"","رقم الجهاز":"","رقم الجوال":"","المسح":"","المنطقة":""}; | |
| let v=findBlockAfterLabel(text,FIELD_ALIASES["نوع المشكلة"],START_LABELS); | |
| if(!v)v=findAfterLabel(text,FIELD_ALIASES["نوع المشكلة"]); | |
| if(v)out["نوع المشكلة"]=normalizeText(v); | |
| v=findAfterLabel(text,FIELD_ALIASES["وقت حدوث المشكلة"]); | |
| if(v)out["وقت حدوث المشكلة"]=normalizeDateOnly(v); | |
| v=findAfterLabel(text,FIELD_ALIASES["اسم صاحب المشكلة"]); | |
| if(v)out["اسم صاحب المشكلة"]=v; | |
| v=findAfterLabel(text,FIELD_ALIASES["رقم الهوية"]); | |
| if(v)out["رقم الهوية"]=digitsOnly(v); | |
| if(!out["رقم الهوية"]){const m=text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/);if(m)out["رقم الهوية"]=m[1]} | |
| v=findAfterLabel(text,FIELD_ALIASES["رقم الجهاز"]); | |
| if(v)out["رقم الجهاز"]=digitsOnly(v); | |
| if(!out["رقم الجهاز"]){const m=text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/);if(m)out["رقم الجهاز"]=m[1]} | |
| v=findAfterLabel(text,FIELD_ALIASES["رقم الجوال"]); | |
| if(v)out["رقم الجوال"]=digitsOnly(v); | |
| if(!out["رقم الجوال"]){const m=text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/);if(m)out["رقم الجوال"]=m[1]} | |
| v=findAfterLabel(text,FIELD_ALIASES["المسح"]); | |
| if(v)out["المسح"]=alnumAr(v); | |
| v=findAfterLabel(text,FIELD_ALIASES["المنطقة"]); | |
| if(v)out["المنطقة"]=lettersOnly(v); | |
| return out; | |
| } | |
| function classifyTicket(text,fields){ | |
| const hay=normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase(); | |
| for(const label of CLASS_PRIORITY){ | |
| const kws=CLASS_RULES[label]||[]; | |
| for(const kw of kws){ | |
| const needle=normalizeText(kw).toLowerCase(); | |
| if(needle&&hay.includes(needle))return label; | |
| } | |
| } | |
| return"استفسار"; | |
| } | |
| function catClass(label){ | |
| if(/تسجيل دخول/.test(label))return"login"; | |
| if(/الشبكة/.test(label))return"network"; | |
| if(/الاستمارة/.test(label))return"form"; | |
| if(/النسخة|تحديث/.test(label))return"update"; | |
| if(/أجهزة/.test(label))return"device"; | |
| return"default"; | |
| } | |
| function isValidNationalId(d){const x=(d||"").replace(/\D/g,"");return/^[12]\d{9}$/.test(x)} | |
| function isPhoneNumber(d){const x=(d||"").replace(/\D/g,"");return/^05\d{8}$/.test(x)} | |
| function parseTicketsWithExtras(raw,agentName,defaultRegion){ | |
| const regionChosen=(defaultRegion||"").toString(); | |
| return splitTickets(raw||"").map(t=>{ | |
| const f=extractFields(t); | |
| const cls=classifyTicket(t,f); | |
| const region=regionChosen?regionChosen:(f["المنطقة"]||""); | |
| let survey=f["المسح"]||region; | |
| let id=(f["رقم الهوية"]||"").replace(/\D/g,""); | |
| let dev=(f["رقم الجهاز"]||"").replace(/\D/g,""); | |
| let phone=(f["رقم الجوال"]||"").replace(/\D/g,""); | |
| if(!isValidNationalId(id))id=""; | |
| if(!isPhoneNumber(phone))phone=""; | |
| if(!dev&&isValidNationalId(id))dev=id; | |
| if(!id&&isValidNationalId(dev))id=dev; | |
| const dateOnly=f["وقت حدوث المشكلة"]||""; | |
| return{ | |
| "التصنيف":cls, | |
| "نوع المشكلة":f["نوع المشكلة"]||"", | |
| "وقت حدوث المشكلة":dateOnly, | |
| "اسم صاحب المشكلة":f["اسم صاحب المشكلة"]||"", | |
| "رقم الهوية":id, | |
| "رقم الجهاز":dev, | |
| "رقم الجوال":phone, | |
| "المسح":survey||region, | |
| "المنطقة":region, | |
| "اسم الدعم الفني":agentName||"", | |
| "الحالة":"تم الحل", | |
| "تاريخ اليوم بالميلادي":dateOnly | |
| }; | |
| }); | |
| } | |
| function buildTable(rows){ | |
| const theadRow=document.getElementById("theadRow"); | |
| const tbody=document.getElementById("tbody"); | |
| theadRow.innerHTML=""; | |
| DISPLAY_COLUMNS.forEach(col=>{const th=document.createElement("th");th.textContent=col;theadRow.appendChild(th)}); | |
| tbody.innerHTML=""; | |
| rows.forEach(r=>{ | |
| const tr=document.createElement("tr"); | |
| DISPLAY_COLUMNS.forEach(col=>{ | |
| const td=document.createElement("td"); | |
| const base=DISPLAY_TO_BASE[col]; | |
| td.dataset.base=base; | |
| let val=(base?(r[base]||""):""); | |
| if(col==="التصنيف"){ | |
| const span=document.createElement("span"); | |
| span.className=`cat ${catClass(val||"")}`; | |
| span.textContent=val||""; | |
| td.appendChild(span); | |
| }else{ | |
| td.contentEditable="true"; | |
| td.textContent=val; | |
| } | |
| tr.appendChild(td); | |
| }); | |
| tbody.appendChild(tr); | |
| }); | |
| } | |
| function readTable(){ | |
| const tbody=document.getElementById("tbody"); | |
| const rows=[]; | |
| [...tbody.querySelectorAll("tr")].forEach(tr=>{ | |
| const obj={}; | |
| [...tr.children].forEach(td=>{ | |
| const base=td.dataset.base; | |
| if(!base)return; | |
| if(base==="التصنيف"){obj[base]=(td.querySelector("span")?.textContent||td.textContent||"").trim()} | |
| else{obj[base]=(td.textContent||"").trim()} | |
| }); | |
| obj["وقت حدوث المشكلة"]=obj["تاريخ اليوم بالميلادي"]||""; | |
| rows.push(obj); | |
| }); | |
| return rows; | |
| } | |
| function updateBadge(n){ | |
| const b=document.getElementById("countBadge"); | |
| if(!b)return; | |
| b.textContent=n; | |
| b.hidden=(n===0); | |
| } | |
| function setButtonsEnabled(hasRows){ | |
| document.getElementById("btn-export").disabled=!hasRows; | |
| document.getElementById("btn-copy").disabled=!hasRows; | |
| } | |
| function validateCells(){ | |
| const tbody=document.getElementById("tbody"); | |
| const required=new Set(["نوع المشكلة","اسم صاحب المشكلة","رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة"]); | |
| let missing=0; | |
| [...tbody.rows].forEach(tr=>{ | |
| [...tr.children].forEach(td=>{ | |
| const base=td.dataset.base||""; | |
| const val=(td.textContent||"").trim(); | |
| let invalid=false,reason=""; | |
| if(required.has(base)&&!val){invalid=true;reason="required";missing++} | |
| if(base==="رقم الهوية"){ | |
| const digits=val.replace(/\D/g,""); | |
| if(val&&!/^[12]\d{9}$/.test(digits)){invalid=true;if(!reason)reason="id"} | |
| } | |
| if(base==="رقم الجوال"){ | |
| const digits=val.replace(/\D/g,""); | |
| if(val&&!/^05\d{8}$/.test(digits)){invalid=true;if(!reason)reason="phone"} | |
| } | |
| if(base==="تاريخ اليوم بالميلادي"){ | |
| if(val&&!/^\d{4}-\d{2}-\d{2}$/.test(val)){invalid=true;reason="date"} | |
| } | |
| //td.classList.toggle("invalid",invalid); | |
| if(invalid){ | |
| const msg=reason==="required"?"الحقل مطلوب":reason==="id"?"رقم الهوية يجب أن يبدأ بـ 1 أو 2 وطوله 10 خانات":reason==="phone"?"رقم الجوال يجب أن يبدأ بـ 05 وطوله 10 خانات":"أدخل تاريخ بصيغة YYYY-MM-DD"; | |
| td.setAttribute("title",msg); | |
| td.dataset.reason=reason; | |
| }else{ | |
| td.removeAttribute("title"); | |
| delete td.dataset.reason; | |
| } | |
| }); | |
| }); | |
| const warn=document.getElementById("warn"); | |
| if(missing>0){warn.hidden=false;warn.textContent=`هناك ${missing} حقول مطلوبة فارغة أو غير صحيحة. يرجى إكمالها.`} | |
| else{warn.hidden=true;warn.textContent=""} | |
| } | |
| document.addEventListener("input",e=>{ | |
| if(e.target&&e.target.closest&&e.target.closest("#tbody")){ | |
| validateCells(); | |
| saveState(); | |
| } | |
| }); | |
| function toast(msg){ | |
| const t=document.getElementById("toast"); | |
| if(!t)return; | |
| t.textContent=msg; | |
| t.hidden=false; | |
| t.classList.remove("show");void t.offsetWidth;t.classList.add("show"); | |
| setTimeout(()=>{t.hidden=true},2000); | |
| } | |
| async function exportExcel(){ | |
| const TEMPLATE_HEADERS=["التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشغل","رقم الجوال","رقم الهوية ID","رقم الجهاز","وقت حدوث المشكلة","الحالة","اسم الدعم الفني"]; | |
| const rows=readTable(); | |
| if(!rows.length){toast("لا يوجد بيانات لتصديرها.");return} | |
| const mapRow=r=>({ | |
| "التصنيف":r["التصنيف"]||"", | |
| "نوع المشكلة":r["نوع المشكلة"]||"", | |
| "المنطقة":r["المنطقة"]||"", | |
| "اسم المسح":r["المسح"]||"", | |
| "اسم المشغل":r["اسم صاحب المشكلة"]||"", | |
| "رقم الجوال":(r["رقم الجوال"]||"").toString(), | |
| "رقم الهوية ID":(r["رقم الهوية"]||"").toString(), | |
| "رقم الجهاز":(r["رقم الجهاز"]||"").toString(), | |
| "وقت حدوث المشكلة":r["وقت حدوث المشكلة"]||"", | |
| "الحالة":r["الحالة"]||"تم الحل", | |
| "اسم الدعم الفني":r["اسم الدعم الفني"]||"" | |
| }); | |
| const wb=new ExcelJS.Workbook(); | |
| const ws=wb.addWorksheet("التذاكر",{views:[{rightToLeft:true}]}); | |
| const colWidths=[16,18,16,18,20,18,18,18,20,14,18]; | |
| TEMPLATE_HEADERS.forEach((h,i)=>ws.getColumn(i+1).width=colWidths[i]||18); | |
| ws.addRow(TEMPLATE_HEADERS); | |
| const headerRow=ws.getRow(1); | |
| headerRow.height=24; | |
| headerRow.eachCell(cell=>{ | |
| cell.font={bold:true,color:{argb:"FFFFFFFF"}}; | |
| cell.alignment={horizontal:"center",vertical:"middle"}; | |
| cell.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FF4137A8"}}; | |
| cell.border={top:{style:"thin",color:{argb:"FFCDD2E1"}},bottom:{style:"thin",color:{argb:"FFCDD2E1"}},left:{style:"thin",color:{argb:"FFE5E7EB"}},right:{style:"thin",color:{argb:"FFE5E7EB"}}}; | |
| }); | |
| const toTextCols=new Set(["رقم الجوال","رقم الهوية ID","رقم الجهاز"]); | |
| rows.forEach((r,idx)=>{ | |
| const m=mapRow(r); | |
| const vals=TEMPLATE_HEADERS.map(h=>(m[h]??"")); | |
| const row=ws.addRow(vals); | |
| row.alignment={horizontal:"center",vertical:"middle"}; | |
| const even=(idx%2)===1; | |
| row.eachCell((cell,colNumber)=>{ | |
| cell.border={top:{style:"thin",color:{argb:"FFE5E7EB"}},bottom:{style:"thin",color:{argb:"FFE5E7EB"}},left:{style:"thin",color:{argb:"FFE5E7EB"}},right:{style:"thin",color:{argb:"FFE5E7EB"}}}; | |
| if(even)cell.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FFF5F8FF"}}; | |
| const header=TEMPLATE_HEADERS[colNumber-1]; | |
| if(toTextCols.has(header))cell.value=String(cell.value??""); | |
| }); | |
| }); | |
| const ts=new Date().toISOString().replace(/\D/g,"").slice(0,14); | |
| const filename=`Ticket_${ts}.xlsx`; | |
| const buffer=await wb.xlsx.writeBuffer(); | |
| const blob=new Blob([buffer],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}); | |
| const file=new File([blob],filename,{type:blob.type}); | |
| if(navigator.canShare&&navigator.canShare({files:[file]})){try{await navigator.share({files:[file],title:"ملف التذاكر"});toast("تمت المشاركة/الحفظ.");return}catch(e){}} | |
| const url=URL.createObjectURL(blob); | |
| const a=document.createElement("a");a.href=url;a.download=filename;document.body.appendChild(a);a.click();a.remove(); | |
| setTimeout(()=>URL.revokeObjectURL(url),1000); | |
| toast("تم تنزيل الملف بتنسيق القالب."); | |
| } | |
| async function copyToClipboardTSV(){ | |
| const rows=readTable(); | |
| if(!rows.length){toast("لا يوجد بيانات لنسخها.");return} | |
| const textCols=new Set(["رقم الهوية ID","رقم الجهاز","رقم الجوال"]); | |
| const body=rows.map(r=>DISPLAY_COLUMNS.map(c=>{ | |
| const base=DISPLAY_TO_BASE[c]; | |
| let v=(base?(r[base]??""):"").toString().replace(/\t/g," "); | |
| if(textCols.has(c)&&v&&/^[0-9]+$/.test(v))v="'"+v; | |
| return v; | |
| }).join("\t")).join("\r\n"); | |
| const tsv="\uFEFF"+body; | |
| try{await navigator.clipboard.writeText(tsv);toast("تم النسخ — بدون عناوين.")}catch(e){ | |
| const ta=document.createElement("textarea");ta.value=tsv;document.body.appendChild(ta);ta.select();document.execCommand("copy");document.body.removeChild(ta);toast("تم النسخ — بدون عناوين.") | |
| } | |
| } | |
| const SAMPLE=`نوع المشكلة: لا استطيع اكمال الاستمارة بسبب تعليق عند الحفظ | |
| وقت حدوث المشكلة: 1446/09/10 هـ | |
| اسم صاحب المشكلة: نوف الناصر | |
| رقم الهوية: 1234567890 | |
| رقم الجهاز: 01234 | |
| رقم الجوال: 0558174717 | |
| المسح: الخبر | |
| المنطقة: الشرقية`; | |
| function saveState(){ | |
| try{ | |
| const raw=document.getElementById("raw")?.value||""; | |
| const agent=document.getElementById("agentName")?.value||""; | |
| const region=document.getElementById("regionDefault")?.value||""; | |
| const rows=readTable(); | |
| localStorage.setItem("ticketParserState_latest",JSON.stringify({raw,agent,region,rows,theme:document.body.classList.contains('dark')?'dark':'light'})); | |
| }catch{} | |
| } | |
| function loadState(){ | |
| try{ | |
| const s=localStorage.getItem("ticketParserState_latest"); | |
| if(!s)return false; | |
| let{raw,agent,region,rows,theme}=JSON.parse(s); | |
| const rawEl=document.getElementById("raw");if(typeof raw==="string"&&rawEl)rawEl.value=raw; | |
| const agentEl=document.getElementById("agentName");if(typeof agent==="string"&&agentEl)agentEl.value=agent; | |
| const regionEl=document.getElementById("regionDefault");if(typeof region==="string"&®ionEl)regionEl.value=region; | |
| if(theme==='dark')document.body.classList.add('dark'); | |
| updateThemeLabel(); | |
| if(Array.isArray(rows)&&rows.length){buildTable(rows);validateCells();updateBadge(rows.length);setButtonsEnabled(true)} | |
| return true; | |
| }catch{return false} | |
| } | |
| function clearAll(){ | |
| const rawEl=document.getElementById("raw"); | |
| const tbody=document.getElementById("tbody"); | |
| const agentEl=document.getElementById("agentName"); | |
| const regionEl=document.getElementById("regionDefault"); | |
| if(rawEl)rawEl.value=""; | |
| if(tbody)tbody.innerHTML=""; | |
| if(agentEl)agentEl.value=""; | |
| if(regionEl)regionEl.value=""; | |
| updateBadge(0);setButtonsEnabled(false); | |
| document.getElementById("warn").hidden=true; | |
| try{localStorage.removeItem("ticketParserState_latest")}catch{} | |
| toast("تم مسح كل البيانات والتخزين."); | |
| } | |
| function mergeDuplicatesRows(rows){ | |
| if(!rows.length)return rows; | |
| const map=new Map(); | |
| rows.forEach(r=>{ | |
| const key=[r["رقم الهوية"]||"",r["رقم الجهاز"]||"",r["رقم الجوال"]||"",r["وقت حدوث المشكلة"]||"",(r["نوع المشكلة"]||"").slice(0,40)].join("|"); | |
| if(!map.has(key))map.set(key,r); | |
| }); | |
| return[...map.values()]; | |
| } | |
| function updateThemeLabel(){ | |
| const btn=document.getElementById("btn-theme"); | |
| if(!btn)return; | |
| btn.textContent=document.body.classList.contains("dark")?"☀️ وضع نهار":"🌙 وضع ليلي"; | |
| } | |
| function isUnlocked(){try{return localStorage.getItem("ticketParser_unlocked")==="1"}catch{return false}} | |
| function markUnlocked(){try{localStorage.setItem("ticketParser_unlocked","1")}catch{}} | |
| function getLockBtn(){return document.getElementById("lockBtn")||document.getElementById("unlockBtn")||document.querySelector('[data-action="unlock"]')} | |
| function getLockInput(){return document.getElementById("lockPass")||document.querySelector('input[type="password"][name="lock"]')} | |
| function showGate(){ | |
| if(isUnlocked()){hideGate();return} | |
| const ov=document.getElementById("lockOverlay"); | |
| if(ov){ov.style.display="flex";setTimeout(()=>{const p=getLockInput();if(p)p.focus()},0)} | |
| } | |
| function hideGate(){ | |
| const ov=document.getElementById("lockOverlay"); | |
| if(ov)ov.style.display="none"; | |
| } | |
| function tryUnlock(){ | |
| const inp=getLockInput(); | |
| const raw=inp?(inp.value||""):""; | |
| const normalized=raw.replace(/[٠-٩]/g,d=>arabicDigitsMap[d]).trim(); | |
| if(ACCESS_PASSWORDS.includes(normalized)){markUnlocked();hideGate()} | |
| else{ | |
| const m=document.getElementById("lockMsg"); | |
| if(m){m.hidden=false;setTimeout(()=>m.hidden=true,1500)} | |
| } | |
| } | |
| function wireLock(){ | |
| const btn=getLockBtn(); | |
| const inp=getLockInput(); | |
| if(btn)btn.addEventListener("click",tryUnlock); | |
| if(inp)inp.addEventListener("keydown",e=>{if(e.key==="Enter")tryUnlock()}); | |
| } | |
| function init(){ | |
| const parseBtn=document.getElementById("btn-parse"); | |
| const exportBtn=document.getElementById("btn-export"); | |
| const copyBtn=document.getElementById("btn-copy"); | |
| const clearBtn=document.getElementById("btn-clear"); | |
| const themeBtn=document.getElementById("btn-theme"); | |
| const rawEl=document.getElementById("raw"); | |
| const agentEl=document.getElementById("agentName"); | |
| const regionEl=document.getElementById("regionDefault"); | |
| rawEl.placeholder=SAMPLE; | |
| if(!isUnlocked())showGate();else hideGate(); | |
| wireLock(); | |
| loadState(); | |
| parseBtn.addEventListener("click",()=>{ | |
| const raw=(rawEl.value||"").trim(); | |
| if(!raw){toast("فضلاً الصق/ي تذاكر أولاً.");return} | |
| const cleaned=normalizeText(fixLabels(raw)); | |
| const agent=agentEl.value||""; | |
| const defRegion=regionEl.value||""; | |
| let rows=parseTicketsWithExtras(cleaned,agent,defRegion); | |
| rows=mergeDuplicatesRows(rows); | |
| buildTable(rows);validateCells(); | |
| updateBadge(rows.length);setButtonsEnabled(rows.length>0); | |
| saveState(); | |
| toast(`تم استخراج ${rows.length} تذكرة.`); | |
| }); | |
| exportBtn.addEventListener("click",exportExcel); | |
| copyBtn.addEventListener("click",copyToClipboardTSV); | |
| clearBtn.addEventListener("click",clearAll); | |
| themeBtn.addEventListener("click",()=>{document.body.classList.toggle("dark");updateThemeLabel();saveState()}); | |
| rawEl.addEventListener("input",saveState); | |
| agentEl.addEventListener("input",saveState); | |
| regionEl.addEventListener("change",saveState); | |
| document.addEventListener("keydown",e=>{ | |
| const ctrl=e.ctrlKey||e.metaKey; | |
| if(ctrl&&e.key==="Enter"){e.preventDefault();parseBtn.click()} | |
| else if(ctrl&&e.key.toLowerCase()==="e"){e.preventDefault();exportBtn.click()} | |
| else if(ctrl&&e.shiftKey&&e.key.toLowerCase()==="c"){e.preventDefault();copyBtn.click()} | |
| else if(e.key==="Escape"){e.preventDefault();clearAll()} | |
| }); | |
| setButtonsEnabled(!!document.getElementById("tbody")?.children.length); | |
| updateThemeLabel(); | |
| } | |
| init(); | |