stat2025 commited on
Commit
6a8cfdf
·
verified ·
1 Parent(s): 8ed5bb6

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +42 -66
app.js CHANGED
@@ -1,4 +1,4 @@
1
- /* app.js — v10.1: تقسيم ذكي جدًا + لصق متعدد + كل التحسينات السابقة */
2
 
3
  /* ======== أعمدة الجدول ======== */
4
  const EXPORT_COLUMNS = [
@@ -44,6 +44,8 @@ const CLASS_PRIORITY = [
44
 
45
  /* ======== أدوات نص وتطبيع ======== */
46
  const TICKET_SEP = /\n\s*(?:\n|—+|-{3,}|={3,}|🔴+)+\s*\n/;
 
 
47
  const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
48
  function normalizeText(s){
49
  if(typeof s!=="string") return "";
@@ -88,12 +90,12 @@ function normalizeDate(v){
88
  let m=v.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/);
89
  if(m){
90
  let d=+m[1], mo=+m[2], y=+m[3]; if(y<100) y+=2000;
91
- return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
92
  }
93
  m=v.match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/);
94
  if(m){
95
  let y=+m[1], mo=+m[2], d=+m[3];
96
- return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
97
  }
98
  return v;
99
  }
@@ -107,47 +109,41 @@ function parseDateTime(raw){
107
  return t;
108
  }
109
 
110
- /* ======== مساعدة: إيجاد بدايات على أساس أسماء الحقول ======== */
111
  function findStartsByLabels(text, labels){
112
- // يسمح بوجود أو عدم وجود النقطتين، ويقبل رموز تعداد قبل الحقل
113
  const lblRe = labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|");
114
  const re = new RegExp(
115
  `(^|\\n)\\s*(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:${lblRe})(?:\\s*[::]|\\s+)`,
116
  "gi"
117
  );
118
- const starts = [];
119
  let m;
120
  while((m = re.exec(text))){
121
- starts.push(m.index + (m[1] ? m[1].length : 0));
122
  }
123
- return starts;
124
  }
125
 
126
- /* ======== تقسيم تذاكر قوي جدًا ======== */
 
 
 
 
 
127
  function splitTickets(raw){
128
  const text = normalizeText(raw);
129
  if(!text) return [];
130
 
131
- // 1) فواصل واضحة (🔴/خطوط/سطرين فارغين)
132
  if(TICKET_SEP.test(text)){
133
  return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
134
  }
135
 
136
- // 2) اجمع بدايات من عدة مجموعات حقول
137
- const groups = [
138
- ["نوع المشكلة","نوع المشكله"],
139
- ["وقت حدوث المشكلة","وقت حدوث المشكله","وقت المشكلة","وقت حدوث"],
140
- ["اسم صاحب المشكلة","اسم صاحب المشكله","اسم صاحب البلاغ","الاسم"],
141
- ["رقم الهوية","رقم الهويه","رقم الجوال","رقم الجهاز"],
142
- ["المنطقة","المنطقه"],
143
- ["المسح","اسم المسح"],
144
- ];
145
-
146
- let starts = [];
147
- for(const g of groups){
148
- starts = [...starts, ...findStartsByLabels(text, g)];
149
- }
150
- starts = [...new Set(starts)].sort((a,b)=>a-b);
151
 
152
  if(starts.length >= 2){
153
  const parts=[];
@@ -160,24 +156,12 @@ function splitTickets(raw){
160
  return parts;
161
  }
162
 
163
- // 3) fallback: تجميع على أساس سطر يبدأ بأي حقل معروف
164
- const lines = text.split("\n").map(l=>l.trim()).filter(Boolean);
165
- if(lines.length > 1){
166
- const anyLabelRe = new RegExp(
167
- `^(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:نوع المشكلة|نوع المشكله|وقت حدوث|اسم صاحب|رقم الهوية|رقم الهويه|رقم الجوال|رقم الجهاز|المنطقة|المنطقه|المسح|اسم المسح)(?:\\s*[::]|\\s+)`,
168
- "i"
169
- );
170
- const parts=[]; let buf=[];
171
- for(const ln of lines){
172
- if(anyLabelRe.test(ln) && buf.length){ parts.push(buf.join("\n").trim()); buf=[]; }
173
- buf.push(ln);
174
- }
175
- if(buf.length) parts.push(buf.join("\n").trim());
176
- if(parts.length) return parts;
177
- }
178
 
179
- // 4) أخيرًا: أسطر فارغة
180
- return text.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean);
181
  }
182
 
183
  /* ======== استخراج الحقول ======== */
@@ -352,7 +336,7 @@ function toast(msg){
352
  setTimeout(()=>{ t.hidden = true; }, 2000);
353
  }
354
 
355
- /* ======== تصدير ExcelJS بتنسيق القالب ======== */
356
  async function exportExcel(){
357
  const rows = readTable();
358
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
@@ -465,7 +449,7 @@ async function copyToClipboardTSV(){
465
  }
466
  }
467
 
468
- /* ======== مثال جاهز للاختبار ======== */
469
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
470
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
471
  اسم صاحب المشكلة : نوف الناصر
@@ -473,26 +457,15 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
473
  رقم الجهاز: 01234
474
  رقم الجوال: 0558174717
475
  اسم المسح: الخبر 2025
476
- اسم المنطقة: الشرقية
477
- نوع المشكلة: مشكله بالدخول (حدث خطأ في التطبيق)
478
- وقت حدوث المشكله:5:00 م
479
- اسم صاحب المشكله: شيماء عبدالرحمن
480
- رقم الهوية:1075808053
481
- رقم الجهاز: 01426
482
- رقم الجوال:0562974417
483
- المنطقه: الخبر
484
- المسح: تحديث الاسعار
485
- نوع المشكلة: معلق على صفحه البدايه
486
- وقت حدوث المشكله: 10:00
487
- اسم صاحب المشكله: امال صالح العبدالعزيز
488
- رقم الهوية: 1084881448
489
- رقم الجهاز: 05337
490
- رقم الجوال: 0582347326
491
- المنطقه: الخبر
492
- المسح:اسعار الجمله`;
493
 
494
  /* ======== تخزين محلي ======== */
495
- const STATE_KEY = "ticketParserState_v10_1";
 
 
 
 
 
496
  function ensureColumns(rows, agentName, defaultRegion){
497
  if(!Array.isArray(rows)) return rows||[];
498
  return rows.map(r=>{
@@ -534,7 +507,7 @@ function loadState(){
534
  }catch{ return false; }
535
  }
536
 
537
- /* ======== مسح الكل (يشمل الاسم والمنطقة) ======== */
538
  function clearAll(){
539
  const rawEl = document.getElementById("raw");
540
  const tbody = document.getElementById("tbody");
@@ -547,8 +520,10 @@ function clearAll(){
547
  if(agentEl) agentEl.value = "";
548
  if(regionEl) regionEl.value = "";
549
  updateBadge(0); setButtonsEnabled(false);
550
- try{ localStorage.removeItem(STATE_KEY); }catch{}
551
- toast("تم مسح كل البيانات.");
 
 
552
  }
553
 
554
  /* ======== لصق مُنظَّم (يمكن الضغط عدة مرات — يضيف ولا يستبدل) ======== */
@@ -592,7 +567,8 @@ function init(){
592
  smartPaste.addEventListener("click", ()=> smartPasteInto(rawEl));
593
 
594
  parseBtn.addEventListener("click", ()=>{
595
- const raw = (rawEl.value || SAMPLE);
 
596
  const agent = agentEl.value || "";
597
  const defRegion = regionEl.value || "";
598
  const rows = parseTicketsWithExtras(raw, agent, defRegion);
 
1
+ /* app.js — v10.2: fix over-splitting & hard clear */
2
 
3
  /* ======== أعمدة الجدول ======== */
4
  const EXPORT_COLUMNS = [
 
44
 
45
  /* ======== أدوات نص وتطبيع ======== */
46
  const TICKET_SEP = /\n\s*(?:\n|—+|-{3,}|={3,}|🔴+)+\s*\n/;
47
+ const MIN_SPLIT_SPAN = 80; // أصغر مسافة بين بدايتين لنعتبرها تذكرة جديدة
48
+
49
  const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
50
  function normalizeText(s){
51
  if(typeof s!=="string") return "";
 
90
  let m=v.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/);
91
  if(m){
92
  let d=+m[1], mo=+m[2], y=+m[3]; if(y<100) y+=2000;
93
+ return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).toString().padStart(2,"0")}`;
94
  }
95
  m=v.match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/);
96
  if(m){
97
  let y=+m[1], mo=+m[2], d=+m[3];
98
+ return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).toString().padStart(2,"0")}`;
99
  }
100
  return v;
101
  }
 
109
  return t;
110
  }
111
 
112
+ /* ======== مساعدة: إيجاد بدايات حسب مجموعة تسميات ======== */
113
  function findStartsByLabels(text, labels){
 
114
  const lblRe = labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|");
115
  const re = new RegExp(
116
  `(^|\\n)\\s*(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:${lblRe})(?:\\s*[::]|\\s+)`,
117
  "gi"
118
  );
119
+ const idxs = [];
120
  let m;
121
  while((m = re.exec(text))){
122
+ idxs.push(m.index + (m[1] ? m[1].length : 0));
123
  }
124
+ return idxs;
125
  }
126
 
127
+ /* ======== تقسيم التذاكر (إصلاح الجذر) ========
128
+ السياسة:
129
+ 1) لو فيه فواصل واضحة => نستخدمها.
130
+ 2) لو تكررت "نوع المشكلة" مرتين فأكثر => نقسم عليها فقط (أساس التقسيم).
131
+ 3) وإلا لا نقسم على الحقول الأخرى حتى لا تتفتت التذكرة الواحدة.
132
+ 4) أخيرًا: أسطر فارغة إن وجدت. غير ذلك: كله تذكرة واحدة. */
133
  function splitTickets(raw){
134
  const text = normalizeText(raw);
135
  if(!text) return [];
136
 
137
+ // (1) فواصل معروفة
138
  if(TICKET_SEP.test(text)){
139
  return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
140
  }
141
 
142
+ // (2) تقسيم على "نوع المشكلة" فقط
143
+ const starts = findStartsByLabels(text, ["نوع المشكلة","نوع المشكله"])
144
+ .sort((a,b)=>a-b)
145
+ // تجاهل بدايات قريبة جدًا (حماية)
146
+ .filter((pos,i,arr)=> i===0 || (pos - arr[i-1]) >= MIN_SPLIT_SPAN);
 
 
 
 
 
 
 
 
 
 
147
 
148
  if(starts.length >= 2){
149
  const parts=[];
 
156
  return parts;
157
  }
158
 
159
+ // (4) fallback: أسطر فارغة => كتل
160
+ const blocks = text.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean);
161
+ if(blocks.length>1) return blocks;
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
+ // خلاف ذلك: تذكرة واحدة
164
+ return [text];
165
  }
166
 
167
  /* ======== استخراج الحقول ======== */
 
336
  setTimeout(()=>{ t.hidden = true; }, 2000);
337
  }
338
 
339
+ /* ======== ExcelJS (كما سابقًا) ======== */
340
  async function exportExcel(){
341
  const rows = readTable();
342
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
 
449
  }
450
  }
451
 
452
+ /* ======== مثال ======== */
453
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
454
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
455
  اسم صاحب المشكلة : نوف الناصر
 
457
  رقم الجهاز: 01234
458
  رقم الجوال: 0558174717
459
  اسم المسح: الخبر 2025
460
+ اسم المنطقة: الشرقية`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
  /* ======== تخزين محلي ======== */
463
+ const STATE_KEY = "ticketParserState_v10_2";
464
+ const ALL_STATE_KEYS = [
465
+ "ticketParserState_v8","ticketParserState_v9","ticketParserState_v10",
466
+ "ticketParserState_v10_1","ticketParserState_v10_2"
467
+ ];
468
+
469
  function ensureColumns(rows, agentName, defaultRegion){
470
  if(!Array.isArray(rows)) return rows||[];
471
  return rows.map(r=>{
 
507
  }catch{ return false; }
508
  }
509
 
510
+ /* ======== مسح الكل (يمسح كل المفاتيح القديمة والجديدة) ======== */
511
  function clearAll(){
512
  const rawEl = document.getElementById("raw");
513
  const tbody = document.getElementById("tbody");
 
520
  if(agentEl) agentEl.value = "";
521
  if(regionEl) regionEl.value = "";
522
  updateBadge(0); setButtonsEnabled(false);
523
+ try{
524
+ ALL_STATE_KEYS.forEach(k=>localStorage.removeItem(k));
525
+ }catch{}
526
+ toast("تم مسح كل البيانات والتخزين.");
527
  }
528
 
529
  /* ======== لصق مُنظَّم (يمكن الضغط عدة مرات — يضيف ولا يستبدل) ======== */
 
567
  smartPaste.addEventListener("click", ()=> smartPasteInto(rawEl));
568
 
569
  parseBtn.addEventListener("click", ()=>{
570
+ const raw = (rawEl.value || "").trim();
571
+ if(!raw){ toast("فضلاً الصق/ي تذاكر أولاً (أو استخدمي «إدراج المثال»)."); return; }
572
  const agent = agentEl.value || "";
573
  const defRegion = regionEl.value || "";
574
  const rows = parseTicketsWithExtras(raw, agent, defRegion);