ticket-parser / app.js
stat2025's picture
Update app.js
40ef213 verified
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"&&regionEl)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();