stat2025 commited on
Commit
26e04ff
·
verified ·
1 Parent(s): 63efdf3

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +70 -108
app.js CHANGED
@@ -1,16 +1,16 @@
1
- const ACCESS_PASSWORDS = ["12345","12345678"];
2
 
3
- const EXPORT_COLUMNS = [
4
  "التصنيف","نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
5
  "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة","اسم الدعم الفني","الحالة"
6
  ];
7
 
8
- const DISPLAY_COLUMNS = [
9
  "التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشتغل",
10
  "رقم الجوال","رقم الهوية ID","رقم الجهاز","تاريخ اليوم بالميلادي","الحالة","اسم الدعم الفني"
11
  ];
12
 
13
- const DISPLAY_TO_BASE = {
14
  "التصنيف":"التصنيف",
15
  "نوع المشكلة":"نوع المشكلة",
16
  "المنطقة":"المنطقة",
@@ -24,57 +24,51 @@ const DISPLAY_TO_BASE = {
24
  "اسم الدعم الفني":"اسم الدعم الفني"
25
  };
26
 
27
- const FIELD_ALIASES = {
28
- "نوع المشكلة": ["نوع المشكله","نوع المشكلة","المشكلة","نوع-المشكلة","نوع المشكلة"],
29
- "وقت حدوث المشكلة": ["وقت حدوث المشكله","وقت حدوث المشكلة","وقت المشكلة","وقت حدوث","وقت حدوث المشكله:","وقت حدوث المشكله :"],
30
- "اسم صاحب المشكلة": ["اسم صاحب المشكله","اسم صاحب المشكلة","اسم صاحب البلاغ","الاسم"],
31
- "رقم الهوية": ["رقم الهويه","رقم الهوية","الهوية","هوية"],
32
- "رقم الجهاز": ["رقم الجهاز","الجهاز"],
33
- "رقم الجوال": ["رقم الجوال","الجوال","الهاتف","جوال"],
34
- "المسح": ["المسح","اسم المسح"],
35
- "المنطقة": ["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"]
36
  };
37
- const START_LABELS = Array.from(new Set(Object.values(FIELD_ALIASES).flat()));
38
-
39
- const CLASS_RULES = {
40
- "استفسار": ["استفسار","سؤال","استعلام","معلومة","استفسارات"],
41
- "إضافة أجهزة": ["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"],
42
- "الاستمارة": ["الاستمارة","استمارة","النموذج","نموذج","الفورم","تعليق الاستمارة","لا استطيع اكمال الاستمارة","التعبئة"],
43
- "التقييم": ["التقييم","تقييم","feedback","survey","رضا","نجوم"],
44
- "الخرائط": ["الخرائط","خرائط","map","gps","تحديد الموقع","احداثيات","إحداثيات","الموقع الجغرافي"],
45
- "السوتي": ["السوتي","سوتي","soti","soti assist","mobicontrol","soti mobicontrol"],
46
- "الشبكة": ["الشبكة","شبكة","نت","انترنت","إنترنت","wifi","واي فاي","4g","5g","ضعف الشبكة","stc","mobily","زين","weak signal","no signal"],
47
- "النسخة": ["النسخة","نسخة","الإصدار","اصدار","version","build","release","تحديث نسخة","ترقية النسخة"],
48
- "النظام المكتبي": ["النظام المكتبي","نسخة ويندوز","ويندوز","windows","pc app","برنامج المكتب","التطبيق على الكمبيوتر","الديسكتوب"],
49
- "تسجيل دخول": ["تسجيل دخول","تسجيل الدخول","login","signin","رفض تسجيل الدخول","لا يقبل الدخول","اسم المستخدم","كلمة المرور","نسيت كلمة السر","إعادة تعيين"],
50
- "تفعيل حساب": ["تفعيل حساب","تفعيل","activation","activate","رمز التفعيل","كود التفعيل"],
51
- "تناقل البيانات": ["تناقل البيانات","ترحيل البيانات","مزامنة","sync","مزامنه","نقل البيانات","رفع البيانات","sync failed","المزامنة"],
52
- "صيانة وتحديث الأجهزة": ["صيانة","تحديث الأجهزة","تحديث جهاز","ترقية الجهاز","اعطال الجهاز","تصليح","صيانة وتحديث الأجهزة","صيانة الجهاز"]
53
  };
54
- const CLASS_PRIORITY = [
55
  "صيانة وتحديث الأجهزة","إضافة أجهزة","تسجيل دخول","تفعيل حساب","الاستمارة","التقييم",
56
  "الخرائط","السوتي","الشبكة","النسخة","النظام المكتبي","تناقل البيانات","استفسار"
57
  ];
58
 
59
- const TICKET_SEP = /\n\s*(?:\n{2,}|—+|-{3,}|={3,}|🔴+)\s*\n/;
60
- const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
61
 
62
  function normalizeText(s){
63
- if(typeof s!=="string") return "";
64
- return s.replace(/\r\n/g,"\n")
65
- .replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ")
66
- .replace(/[٠-٩]/g,d=>arabicDigitsMap[d])
67
- .replace(/[ــ]+/g,"")
68
- .replace(/[ \t]+\n/g,"\n")
69
- .replace(/\n{3,}/g,"\n\n")
70
- .trim();
71
  }
72
  function lettersOnly(ar){return(ar||"").replace(/[^A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s]/g,"").replace(/\s{2,}/g," ").trim()}
73
  function alnumAr(s){return(s||"").replace(/[^0-9A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s\-\._/]/g,"").replace(/\s{2,}/g," ").trim()}
74
  function digitsOnly(s){return(s||"").replace(/\D+/g,"")}
75
- function todayYMD(){const d=new Date();const y=d.getFullYear();const m=String(d.getMonth()+1).padStart(2,"0");const dd=String(d.getDate()).padStart(2,"0");return `${y}-${m}-${dd}`;}
76
 
77
- const LABEL_FIXES = [
78
  [/(^|\n)\s*نوع\s*المشكله/gi,"$1نوع المشكلة"],
79
  [/(^|\n)\s*وقت\s*حدوث\s*المشكله/gi,"$1وقت حدوث المشكلة"],
80
  [/(^|\n)\s*اسم\s*صاحب\s*المشكله/gi,"$1اسم صاحب المشكلة"],
@@ -110,7 +104,7 @@ function isTimeOnly(t){
110
  return a||b||c;
111
  }
112
  function isRelativeDatePhrase(t){
113
- return /(أمس|من\s*أمس|من\s*امس|اليوم|غدًا|غدا|بكرة|بعد\s*بكرة|قبل\s*\d+\s*(?:دقيقة|دقايق|ساع(?:ة|ات)|يوم|أيام)|الآن|الحين|قبل\s*شوي)/i.test(t);
114
  }
115
  function detectHijriDate(str){
116
  const t=normalizeText(str);
@@ -125,7 +119,7 @@ function normalizeDateOnly(raw){
125
  if(isTimeOnly(t)||isRelativeDatePhrase(t))return"";
126
  const hj=detectHijriDate(t);
127
  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")}`}
128
- const m=t.match(/(\d{1,4})[\/\-](\d{1,2})[\/\-](\d{1,4})/);
129
  if(m){
130
  let a=+m[1],b=+m[2],c=+m[3],y,mo,d;
131
  if(String(m[1]).length===4){y=a;mo=b;d=c}
@@ -180,35 +174,26 @@ function splitTickets(raw){
180
  function extractFields(ticketText){
181
  const text=normalizeText(fixLabels(ticketText));
182
  const out={"نوع المشكلة":"","وقت حدوث المشكلة":"","اسم صاحب المشكلة":"","رقم الهوية":"","رقم الجهاز":"","رقم الجوال":"","المسح":"","المنطقة":""};
183
-
184
  let v=findBlockAfterLabel(text,FIELD_ALIASES["نوع المشكلة"],START_LABELS);
185
  if(!v)v=findAfterLabel(text,FIELD_ALIASES["نوع المشكلة"]);
186
  if(v)out["نوع المشكلة"]=normalizeText(v);
187
-
188
  v=findAfterLabel(text,FIELD_ALIASES["وقت حدوث المشكلة"]);
189
  if(v)out["وقت حدوث المشكلة"]=normalizeDateOnly(v);
190
-
191
  v=findAfterLabel(text,FIELD_ALIASES["اسم صاحب المشكلة"]);
192
  if(v)out["اسم صاحب المشكلة"]=v;
193
-
194
  v=findAfterLabel(text,FIELD_ALIASES["رقم الهوية"]);
195
  if(v)out["رقم الهوية"]=digitsOnly(v);
196
  if(!out["رقم الهوية"]){const m=text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/);if(m)out["رقم الهوية"]=m[1]}
197
-
198
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجهاز"]);
199
  if(v)out["رقم الجهاز"]=digitsOnly(v);
200
  if(!out["رقم الجهاز"]){const m=text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/);if(m)out["رقم الجهاز"]=m[1]}
201
-
202
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجوال"]);
203
  if(v)out["رقم الجوال"]=digitsOnly(v);
204
  if(!out["رقم الجوال"]){const m=text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/);if(m)out["رقم الجوال"]=m[1]}
205
-
206
  v=findAfterLabel(text,FIELD_ALIASES["المسح"]);
207
  if(v)out["المسح"]=alnumAr(v);
208
-
209
  v=findAfterLabel(text,FIELD_ALIASES["المنطقة"]);
210
  if(v)out["المنطقة"]=lettersOnly(v);
211
-
212
  return out;
213
  }
214
 
@@ -232,35 +217,24 @@ function catClass(label){
232
  return"default";
233
  }
234
 
235
- function isValidNationalId(d){
236
- const x=(d||"").replace(/\D/g,"");
237
- return /^[12]\d{9}$/.test(x);
238
- }
239
- function isPhoneNumber(d){
240
- const x=(d||"").replace(/\D/g,"");
241
- return /^05\d{8}$/.test(x);
242
- }
243
 
244
  function parseTicketsWithExtras(raw,agentName,defaultRegion){
245
  const regionChosen=(defaultRegion||"").toString();
246
  return splitTickets(raw||"").map(t=>{
247
  const f=extractFields(t);
248
  const cls=classifyTicket(t,f);
249
-
250
  const region=regionChosen?regionChosen:(f["المنطقة"]||"");
251
  let survey=f["المسح"]||region;
252
-
253
  let id=(f["رقم الهوية"]||"").replace(/\D/g,"");
254
  let dev=(f["رقم الجهاز"]||"").replace(/\D/g,"");
255
  let phone=(f["رقم الجوال"]||"").replace(/\D/g,"");
256
-
257
- if(!isValidNationalId(id)) id="";
258
- if(!isPhoneNumber(phone)) phone="";
259
-
260
- if(!dev && isValidNationalId(id)) dev=id;
261
- if(!dev && !id && phone) dev=phone;
262
- if(!id && isValidNationalId(dev)) id=dev;
263
-
264
  return{
265
  "التصنيف":cls,
266
  "نوع المشكلة":f["نوع المشكلة"]||"",
@@ -273,7 +247,7 @@ function parseTicketsWithExtras(raw,agentName,defaultRegion){
273
  "المنطقة":region,
274
  "اسم الدعم الفني":agentName||"",
275
  "الحالة":"تم الحل",
276
- "تاريخ اليوم بالميلادي": todayYMD()
277
  };
278
  });
279
  }
@@ -314,13 +288,10 @@ function readTable(){
314
  [...tr.children].forEach(td=>{
315
  const base=td.dataset.base;
316
  if(!base)return;
317
- if(base==="التصنيف"){
318
- obj[base]=(td.querySelector("span")?.textContent||td.textContent||"").trim();
319
- }else{
320
- obj[base]=(td.textContent||"").trim();
321
- }
322
  });
323
- if(!obj["تاريخ اليوم بالميلادي"]) obj["تاريخ اليوم بالميلادي"]=todayYMD();
324
  rows.push(obj);
325
  });
326
  return rows;
@@ -345,16 +316,15 @@ function validateCells(){
345
  [...tr.children].forEach(td=>{
346
  const base=td.dataset.base||"";
347
  const val=(td.textContent||"").trim();
348
- let invalid=false;
349
- let reason="";
350
  if(required.has(base)&&!val){invalid=true;reason="required";missing++}
351
  if(base==="رقم الهوية"){
352
  const digits=val.replace(/\D/g,"");
353
- if(val && !/^[12]\d{9}$/.test(digits)){invalid=true;if(!reason)reason="id"}
354
  }
355
  if(base==="رقم الجوال"){
356
  const digits=val.replace(/\D/g,"");
357
- if(val && !/^05\d{8}$/.test(digits)){invalid=true;if(!reason)reason="phone"}
358
  }
359
  td.classList.toggle("invalid",invalid);
360
  if(invalid){
@@ -437,17 +407,15 @@ async function copyToClipboardTSV(){
437
  const rows=readTable();
438
  if(!rows.length){toast("لا يوجد بيانات لنسخها.");return}
439
  const textCols=new Set(["رقم الهوية ID","رقم الجهاز","رقم الجوال"]);
440
- const body=rows.map(r=>{
441
- return DISPLAY_COLUMNS.map(c=>{
442
- const base=DISPLAY_TO_BASE[c];
443
- let v=(base? (r[base]??"") : "").toString().replace(/\t/g," ");
444
- if(textCols.has(c)&&v&&/^[0-9]+$/.test(v))v="'"+v;
445
- return v;
446
- }).join("\t");
447
- }).join("\r\n");
448
  const tsv="\uFEFF"+body;
449
- try{await navigator.clipboard.writeText(tsv);toast("تم النسخ — الصق/ي مباشرة في Excel بدون عناوين.")}catch(e){
450
- const ta=document.createElement("textarea");ta.value=tsv;document.body.appendChild(ta);ta.select();document.execCommand("copy");document.body.removeChild(ta);toast("تم النسخ — الصق/ي مباشرة في Excel بدون عناوين.")
451
  }
452
  }
453
 
@@ -516,20 +484,18 @@ function updateThemeLabel(){
516
  btn.textContent=document.body.classList.contains("dark")?"☀️ وضع نهار":"🌙 وضع ليلي";
517
  }
518
 
 
 
519
  function showGate(){
520
- document.getElementById("lockOverlay").style.display="flex";
521
- setTimeout(()=>{document.getElementById("lockPass").focus()},0);
522
- }
523
- function hideGate(){
524
- document.getElementById("lockOverlay").style.display="none";
525
  }
 
526
  function tryUnlock(){
527
  const p=document.getElementById("lockPass").value||"";
528
- if(ACCESS_PASSWORDS.includes(p)){hideGate()}else{
529
- const m=document.getElementById("lockMsg");
530
- m.hidden=false;
531
- setTimeout(()=>m.hidden=true,1500);
532
- }
533
  }
534
 
535
  function init(){
@@ -546,7 +512,7 @@ function init(){
546
 
547
  rawEl.placeholder=SAMPLE;
548
 
549
- showGate();
550
 
551
  loadState();
552
 
@@ -568,11 +534,7 @@ function init(){
568
  copyBtn.addEventListener("click",copyToClipboardTSV);
569
  clearBtn.addEventListener("click",clearAll);
570
 
571
- themeBtn.addEventListener("click",()=>{
572
- document.body.classList.toggle("dark");
573
- updateThemeLabel();
574
- saveState();
575
- });
576
 
577
  rawEl.addEventListener("input",saveState);
578
  agentEl.addEventListener("input",saveState);
 
1
+ const ACCESS_PASSWORDS=["12345","12345678"];
2
 
3
+ const EXPORT_COLUMNS=[
4
  "التصنيف","نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
5
  "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة","اسم الدعم الفني","الحالة"
6
  ];
7
 
8
+ const DISPLAY_COLUMNS=[
9
  "التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشتغل",
10
  "رقم الجوال","رقم الهوية ID","رقم الجهاز","تاريخ اليوم بالميلادي","الحالة","اسم الدعم الفني"
11
  ];
12
 
13
+ const DISPLAY_TO_BASE={
14
  "التصنيف":"التصنيف",
15
  "نوع المشكلة":"نوع المشكلة",
16
  "المنطقة":"المنطقة",
 
24
  "اسم الدعم الفني":"اسم الدعم الفني"
25
  };
26
 
27
+ const FIELD_ALIASES={
28
+ "نوع المشكلة":["نوع المشكله","نوع المشكلة","المشكلة","نوع-المشكلة","نوع المشكلة"],
29
+ "وقت حدوث المشكلة":["وقت حدوث المشكله","وقت حدوث المشكلة","وقت المشكلة","وقت حدوث","وقت حدوث المشكله:","وقت حدوث المشكله :"],
30
+ "اسم صاحب المشكلة":["اسم صاحب المشكله","اسم صاحب المشكلة","اسم صاحب البلاغ","الاسم"],
31
+ "رقم الهوية":["رقم الهويه","رقم الهوية","الهوية","هوية"],
32
+ "رقم الجهاز":["رقم الجهاز","الجهاز"],
33
+ "رقم الجوال":["رقم الجوال","الجوال","الهاتف","جوال"],
34
+ "المسح":["المسح","اسم المسح"],
35
+ "المنطقة":["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"]
36
  };
37
+ const START_LABELS=Array.from(new Set(Object.values(FIELD_ALIASES).flat()));
38
+
39
+ const CLASS_RULES={
40
+ "استفسار":["استفسار","سؤال","استعلام","معلومة","استفسارات"],
41
+ "إضافة أجهزة":["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"],
42
+ "الاستمارة":["الاستمارة","استمارة","النموذج","نموذج","الفورم","تعليق الاستمارة","لا استطيع اكمال الاستمارة","التعبئة"],
43
+ "التقييم":["التقييم","تقييم","feedback","survey","رضا","نجوم"],
44
+ "الخرائط":["الخرائط","خرائط","map","gps","تحديد الموقع","احداثيات","إحداثيات","الموقع الجغرافي"],
45
+ "السوتي":["السوتي","سوتي","soti","soti assist","mobicontrol","soti mobicontrol"],
46
+ "الشبكة":["الشبكة","شبكة","نت","انترنت","إنترنت","wifi","واي فاي","4g","5g","ضعف الشبكة","stc","mobily","زين","weak signal","no signal"],
47
+ "النسخة":["النسخة","نسخة","الإصدار","اصدار","version","build","release","تحديث نسخة","ترقية النسخة"],
48
+ "النظام المكتبي":["النظام المكتبي","نسخة ويندوز","ويندوز","windows","pc app","برنامج المكتب","التطبيق على الكمبيوتر","الديسكتوب"],
49
+ "تسجيل دخول":["تسجيل دخول","تسجيل الدخول","login","signin","رفض تسجيل الدخول","لا يقبل الدخول","اسم المستخدم","كلمة المرور","نسيت كلمة السر","إعادة تعيين"],
50
+ "تفعيل حساب":["تفعيل حساب","تفعيل","activation","activate","رمز التفعيل","كود التفعيل"],
51
+ "تناقل البيانات":["تناقل البيانات","ترحيل البيانات","مزامنة","sync","مزامنه","نقل البيانات","رفع البيانات","sync failed","المزامنة"],
52
+ "صيانة وتحديث الأجهزة":["صيانة","تحديث الأجهزة","تحديث جهاز","ترقية الجهاز","اعطال الجهاز","تصليح","صيانة وتحديث الأجهزة","صيانة الجهاز"]
53
  };
54
+ const CLASS_PRIORITY=[
55
  "صيانة وتحديث الأجهزة","إضافة أجهزة","تسجيل دخول","تفعيل حساب","الاستمارة","التقييم",
56
  "الخرائط","السوتي","الشبكة","النسخة","النظام المكتبي","تناقل البيانات","استفسار"
57
  ];
58
 
59
+ const TICKET_SEP=/\n\s*(?:\n{2,}|—+|-{3,}|={3,}|🔴+)\s*\n/;
60
+ const arabicDigitsMap={"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
61
 
62
  function normalizeText(s){
63
+ if(typeof s!=="string")return"";
64
+ 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();
 
 
 
 
 
 
65
  }
66
  function lettersOnly(ar){return(ar||"").replace(/[^A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s]/g,"").replace(/\s{2,}/g," ").trim()}
67
  function alnumAr(s){return(s||"").replace(/[^0-9A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s\-\._/]/g,"").replace(/\s{2,}/g," ").trim()}
68
  function digitsOnly(s){return(s||"").replace(/\D+/g,"")}
69
+ function todayYMD(){const d=new Date();const y=d.getFullYear();const m=String(d.getMonth()+1).padStart(2,"0");const dd=String(d.getDate()).padStart(2,"0");return`${y}-${m}-${dd}`}
70
 
71
+ const LABEL_FIXES=[
72
  [/(^|\n)\s*نوع\s*المشكله/gi,"$1نوع المشكلة"],
73
  [/(^|\n)\s*وقت\s*حدوث\s*المشكله/gi,"$1وقت حدوث المشكلة"],
74
  [/(^|\n)\s*اسم\s*صاحب\s*المشكله/gi,"$1اسم صاحب المشكلة"],
 
104
  return a||b||c;
105
  }
106
  function isRelativeDatePhrase(t){
107
+ return/(أمس|من\s*أمس|من\s*امس|اليوم|غدًا|غدا|بكرة|بعد\s*بكرة|قبل\s*\d+\s*(?:دقيقة|دقايق|ساع(?:ة|ات)|يوم|أيام)|الآن|الحين|قبل\s*شوي)/i.test(t);
108
  }
109
  function detectHijriDate(str){
110
  const t=normalizeText(str);
 
119
  if(isTimeOnly(t)||isRelativeDatePhrase(t))return"";
120
  const hj=detectHijriDate(t);
121
  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")}`}
122
+ const m=t.match(/(\d{1,4})[\/\-](\d{1,2})[\/\-]({1,4})/);
123
  if(m){
124
  let a=+m[1],b=+m[2],c=+m[3],y,mo,d;
125
  if(String(m[1]).length===4){y=a;mo=b;d=c}
 
174
  function extractFields(ticketText){
175
  const text=normalizeText(fixLabels(ticketText));
176
  const out={"نوع المشكلة":"","وقت حدوث المشكلة":"","اسم صاحب المشكلة":"","رقم الهوية":"","رقم الجهاز":"","رقم الجوال":"","المسح":"","المنطقة":""};
 
177
  let v=findBlockAfterLabel(text,FIELD_ALIASES["نوع المشكلة"],START_LABELS);
178
  if(!v)v=findAfterLabel(text,FIELD_ALIASES["نوع المشكلة"]);
179
  if(v)out["نوع المشكلة"]=normalizeText(v);
 
180
  v=findAfterLabel(text,FIELD_ALIASES["وقت حدوث المشكلة"]);
181
  if(v)out["وقت حدوث المشكلة"]=normalizeDateOnly(v);
 
182
  v=findAfterLabel(text,FIELD_ALIASES["اسم صاحب المشكلة"]);
183
  if(v)out["اسم صاحب المشكلة"]=v;
 
184
  v=findAfterLabel(text,FIELD_ALIASES["رقم الهوية"]);
185
  if(v)out["رقم الهوية"]=digitsOnly(v);
186
  if(!out["رقم الهوية"]){const m=text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/);if(m)out["رقم الهوية"]=m[1]}
 
187
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجهاز"]);
188
  if(v)out["رقم الجهاز"]=digitsOnly(v);
189
  if(!out["رقم الجهاز"]){const m=text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/);if(m)out["رقم الجهاز"]=m[1]}
 
190
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجوال"]);
191
  if(v)out["رقم الجوال"]=digitsOnly(v);
192
  if(!out["رقم الجوال"]){const m=text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/);if(m)out["رقم الجوال"]=m[1]}
 
193
  v=findAfterLabel(text,FIELD_ALIASES["المسح"]);
194
  if(v)out["المسح"]=alnumAr(v);
 
195
  v=findAfterLabel(text,FIELD_ALIASES["المنطقة"]);
196
  if(v)out["المنطقة"]=lettersOnly(v);
 
197
  return out;
198
  }
199
 
 
217
  return"default";
218
  }
219
 
220
+ function isValidNationalId(d){const x=(d||"").replace(/\D/g,"");return/^[12]\d{9}$/.test(x)}
221
+ function isPhoneNumber(d){const x=(d||"").replace(/\D/g,"");return/^05\d{8}$/.test(x)}
 
 
 
 
 
 
222
 
223
  function parseTicketsWithExtras(raw,agentName,defaultRegion){
224
  const regionChosen=(defaultRegion||"").toString();
225
  return splitTickets(raw||"").map(t=>{
226
  const f=extractFields(t);
227
  const cls=classifyTicket(t,f);
 
228
  const region=regionChosen?regionChosen:(f["المنطقة"]||"");
229
  let survey=f["المسح"]||region;
 
230
  let id=(f["رقم الهوية"]||"").replace(/\D/g,"");
231
  let dev=(f["رقم الجهاز"]||"").replace(/\D/g,"");
232
  let phone=(f["رقم الجوال"]||"").replace(/\D/g,"");
233
+ if(!isValidNationalId(id))id="";
234
+ if(!isPhoneNumber(phone))phone="";
235
+ if(!dev&&isValidNationalId(id))dev=id;
236
+ if(!dev&&!id&&phone)dev=phone;
237
+ if(!id&&isValidNationalId(dev))id=dev;
 
 
 
238
  return{
239
  "التصنيف":cls,
240
  "نوع المشكلة":f["نوع المشكلة"]||"",
 
247
  "المنطقة":region,
248
  "اسم الدعم الفني":agentName||"",
249
  "الحالة":"تم الحل",
250
+ "تاريخ اليوم بالميلادي":todayYMD()
251
  };
252
  });
253
  }
 
288
  [...tr.children].forEach(td=>{
289
  const base=td.dataset.base;
290
  if(!base)return;
291
+ if(base==="التصنيف"){obj[base]=(td.querySelector("span")?.textContent||td.textContent||"").trim()}
292
+ else{obj[base]=(td.textContent||"").trim()}
 
 
 
293
  });
294
+ if(!obj["تاريخ اليوم بالميلادي"])obj["تاريخ اليوم بالميلادي"]=todayYMD();
295
  rows.push(obj);
296
  });
297
  return rows;
 
316
  [...tr.children].forEach(td=>{
317
  const base=td.dataset.base||"";
318
  const val=(td.textContent||"").trim();
319
+ let invalid=false,reason="";
 
320
  if(required.has(base)&&!val){invalid=true;reason="required";missing++}
321
  if(base==="رقم الهوية"){
322
  const digits=val.replace(/\D/g,"");
323
+ if(val&&!/^[12]\d{9}$/.test(digits)){invalid=true;if(!reason)reason="id"}
324
  }
325
  if(base==="رقم الجوال"){
326
  const digits=val.replace(/\D/g,"");
327
+ if(val&&!/^05\d{8}$/.test(digits)){invalid=true;if(!reason)reason="phone"}
328
  }
329
  td.classList.toggle("invalid",invalid);
330
  if(invalid){
 
407
  const rows=readTable();
408
  if(!rows.length){toast("لا يوجد بيانات لنسخها.");return}
409
  const textCols=new Set(["رقم الهوية ID","رقم الجهاز","رقم الجوال"]);
410
+ const body=rows.map(r=>DISPLAY_COLUMNS.map(c=>{
411
+ const base=DISPLAY_TO_BASE[c];
412
+ let v=(base?(r[base]??""):"").toString().replace(/\t/g," ");
413
+ if(textCols.has(c)&&v&&/^[0-9]+$/.test(v))v="'"+v;
414
+ return v;
415
+ }).join("\t")).join("\r\n");
 
 
416
  const tsv="\uFEFF"+body;
417
+ try{await navigator.clipboard.writeText(tsv);toast("تم النسخ — بدون عناوين.")}catch(e){
418
+ const ta=document.createElement("textarea");ta.value=tsv;document.body.appendChild(ta);ta.select();document.execCommand("copy");document.body.removeChild(ta);toast("تم النسخ — بدون عناوين.")
419
  }
420
  }
421
 
 
484
  btn.textContent=document.body.classList.contains("dark")?"☀️ وضع نهار":"🌙 وضع ليلي";
485
  }
486
 
487
+ function isUnlocked(){try{return localStorage.getItem("ticketParser_unlocked")==="1"}catch{return false}}
488
+ function markUnlocked(){try{localStorage.setItem("ticketParser_unlocked","1")}catch{}}
489
  function showGate(){
490
+ if(isUnlocked()){hideGate();return}
491
+ const ov=document.getElementById("lockOverlay");
492
+ if(ov){ov.style.display="flex";setTimeout(()=>{const p=document.getElementById("lockPass");if(p)p.focus()},0)}
 
 
493
  }
494
+ function hideGate(){const ov=document.getElementById("lockOverlay");if(ov)ov.style.display="none"}
495
  function tryUnlock(){
496
  const p=document.getElementById("lockPass").value||"";
497
+ if(ACCESS_PASSWORDS.includes(p)){markUnlocked();hideGate()}
498
+ else{const m=document.getElementById("lockMsg");if(m){m.hidden=false;setTimeout(()=>m.hidden=true,1500)}}
 
 
 
499
  }
500
 
501
  function init(){
 
512
 
513
  rawEl.placeholder=SAMPLE;
514
 
515
+ if(!isUnlocked())showGate();else hideGate();
516
 
517
  loadState();
518
 
 
534
  copyBtn.addEventListener("click",copyToClipboardTSV);
535
  clearBtn.addEventListener("click",clearAll);
536
 
537
+ themeBtn.addEventListener("click",()=>{document.body.classList.toggle("dark");updateThemeLabel();saveState()});
 
 
 
 
538
 
539
  rawEl.addEventListener("input",saveState);
540
  agentEl.addEventListener("input",saveState);