stat2025 commited on
Commit
a088adf
·
verified ·
1 Parent(s): 625802c

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +66 -61
app.js CHANGED
@@ -1,6 +1,6 @@
1
- /* app.js — v9: smarter split, time parser, Smart Paste, keep all prior features */
2
 
3
- /* ======== أعمدة الجدول (نفسها) ======== */
4
  const EXPORT_COLUMNS = [
5
  "التصنيف",
6
  "نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
@@ -47,7 +47,8 @@ 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 "";
50
- return s.replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ")
 
51
  .replace(/[٠-٩]/g, d => arabicDigitsMap[d] )
52
  .replace(/[ــ]+/g,"")
53
  .trim();
@@ -60,10 +61,9 @@ function alnumAr(s){
60
  }
61
  function digitsOnly(s){ return (s||"").replace(/\D+/g,""); }
62
 
63
- /* وقت بدون دقايق مثل "7 صباحا" / "٧ مساء" */
64
  function normalizeTimeLoose(val){
65
  const t = normalizeText(val);
66
- // HH:MM (مع أو بدون am/pm / ص/م)
67
  let m = t.match(/(\d{1,2})[:٫\.\-:](\d{2})\s*(ص|صباح|صباحا|am|م|مساء|pm)?/i);
68
  if(m){
69
  let h = parseInt(m[1],10), mn = m[2], ampm = (m[3]||"").toLowerCase();
@@ -73,7 +73,6 @@ function normalizeTimeLoose(val){
73
  }
74
  return `${String(h).padStart(2,"0")}:${mn}`;
75
  }
76
- // HH فقط مع صباح/مساء
77
  m = t.match(/(\d{1,2})\s*(ص|صباح|صباحا|am|م|مساء|pm)/i);
78
  if(m){
79
  let h = parseInt(m[1],10);
@@ -101,14 +100,14 @@ function normalizeDate(v){
101
  function parseDateTime(raw){
102
  const t = normalizeText(raw);
103
  const d = normalizeDate(t);
104
- const hhmmLoose = normalizeTimeLoose(t);
105
- if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && hhmmLoose) return `${d} ${hhmmLoose}`;
106
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
107
- if(hhmmLoose) return hhmmLoose;
108
  return t;
109
  }
110
 
111
- /* ابحث قيمة بعد مسافة أو بعد نقطتين */
112
  function findAfterLabel(text, labels){
113
  const hay = "\n" + normalizeText(text) + "\n";
114
  for(const rawLbl of labels){
@@ -121,43 +120,54 @@ function findAfterLabel(text, labels){
121
  return "";
122
  }
123
 
124
- /* تقسيم تذاكر ذكي:
125
- - إن وجد فواصل معروفة → نستخدمها
126
- - وإلا نكتشف كل سطر يبدأ بـ "نوع المشكلة" كحد فاصل */
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  function splitTickets(raw){
128
- raw = (raw || "").replace(/\r\n/g,"\n");
129
- const normalized = normalizeText(raw);
130
- if(!normalized) return [];
131
 
132
  // 1) فواصل معروفة
133
- if(TICKET_SEP.test(normalized)){
134
- return normalized.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
135
  }
136
 
137
- // 2) اكتشاف بدايات "نوع المشكلة"
138
- const starts = [];
139
- const re = /(^|\n)\s*(?:نوع المشكلة|نوع المشكله)\s*[::]/g;
140
- let m;
141
- while((m = re.exec(normalized))){
142
- const pos = m.index + (m[1] ? m[1].length : 0);
143
- starts.push(pos);
144
- }
145
  if(starts.length >= 2){
146
- const parts = [];
 
147
  for(let i=0;i<starts.length;i++){
148
  const s = starts[i];
149
- const e = i+1<starts.length ? starts[i+1] : normalized.length;
150
- const slice = normalized.slice(s,e).trim();
151
  if(slice) parts.push(slice);
152
  }
153
  return parts;
154
  }
155
 
156
- // 3) fallback: أسطر فارغة
157
- return normalized.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean);
158
  }
159
 
160
- /* استخراج الحقول بالأنواع المطلوبة */
161
  function extractFields(ticketText){
162
  const text = normalizeText(ticketText);
163
  const out = {
@@ -204,7 +214,7 @@ function extractFields(ticketText){
204
  return out;
205
  }
206
 
207
- /* التصنيف */
208
  function classifyTicket(text, fields){
209
  const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
210
  for(const label of CLASS_PRIORITY){
@@ -217,14 +227,13 @@ function classifyTicket(text, fields){
217
  return "استفسار";
218
  }
219
 
220
- /* طبّق المنطقة المختارة على الجميع، وإلا استخدم ما في التذكرة */
221
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
222
  const regionChosen = (defaultRegion || "").toString();
223
  return splitTickets(raw||"").map(t => {
224
  const f = extractFields(t);
225
  const cls = classifyTicket(t, f);
226
  const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
227
-
228
  return {
229
  "التصنيف": cls,
230
  "نوع المشكلة": f["نوع المشكلة"] || "",
@@ -241,7 +250,7 @@ function parseTicketsWithExtras(raw, agentName, defaultRegion){
241
  });
242
  }
243
 
244
- /* ======== جدول ======== */
245
  function buildTable(rows){
246
  const theadRow = document.getElementById("theadRow");
247
  const tbody = document.getElementById("tbody");
@@ -274,7 +283,7 @@ function readTable(){
274
  return rows;
275
  }
276
 
277
- /* ======== شارة + أزرار + تحقق ======== */
278
  function updateBadge(n){
279
  const b = document.getElementById("countBadge");
280
  b.textContent = n; b.hidden = (n===0);
@@ -311,7 +320,7 @@ document.addEventListener("input",(e)=>{
311
  }
312
  });
313
 
314
- /* ======== Toast ======== */
315
  function toast(msg){
316
  const t = document.getElementById("toast");
317
  t.textContent = msg; t.hidden = false;
@@ -319,7 +328,7 @@ function toast(msg){
319
  setTimeout(()=>{ t.hidden = true; }, 2000);
320
  }
321
 
322
- /* ======== تصدير Excel (ExcelJS بالتنسيق) ======== */
323
  async function exportExcel(){
324
  const rows = readTable();
325
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
@@ -408,7 +417,7 @@ async function exportExcel(){
408
  toast("تم تنزيل الملف بتنسيق القالب.");
409
  }
410
 
411
- /* ======== نسخ إلى الحافظة (TSV) ======== */
412
  async function copyToClipboardTSV(){
413
  const rows = readTable();
414
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
@@ -432,7 +441,7 @@ async function copyToClipboardTSV(){
432
  }
433
  }
434
 
435
- /* ======== مثال ======== */
436
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
437
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
438
  اسم صاحب المشكلة : نوف الناصر
@@ -441,7 +450,6 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
441
  رقم الجوال: 0558174717
442
  اسم المسح: الخبر 2025
443
  اسم المنطقة: الشرقية
444
-
445
  نوع المشكلة: مشكله بالدخول (حدث خطأ في التطبيق)
446
  وقت حدوث المشكله:5:00 م
447
  اسم صاحب المشكله: شيماء عبدالرحمن
@@ -450,7 +458,6 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
450
  رقم الجوال:0562974417
451
  المنطقه: الخبر
452
  المسح: تحديث الاسعار
453
-
454
  نوع المشكلة: معلق على صفحه البدايه
455
  وقت حدوث المشكله: 10:00
456
  اسم صاحب المشكله: امال صالح العبدالعزيز
@@ -460,8 +467,8 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
460
  المنطقه: الخبر
461
  المسح:اسعار الجمله`;
462
 
463
- /* ======== تخزين الحالة ======== */
464
- const STATE_KEY = "ticketParserState_v9";
465
  function ensureColumns(rows, agentName, defaultRegion){
466
  if(!Array.isArray(rows)) return rows||[];
467
  return rows.map(r=>{
@@ -503,7 +510,7 @@ function loadState(){
503
  }catch{ return false; }
504
  }
505
 
506
- /* مسح الكل (مسح التخزين كذلك) */
507
  function clearAll(){
508
  const rawEl = document.getElementById("raw");
509
  const tbody = document.getElementById("tbody");
@@ -520,19 +527,19 @@ function clearAll(){
520
  toast("تم مسح كل البيانات.");
521
  }
522
 
523
- /* لصق مُنظَّم: يضيف فواصل بين كل تذكرة تلقائيًا */
524
  function normalizeForPaste(text){
525
- const src = (text||"").replace(/\r\n/g,"\n");
526
- const norm = normalizeText(src);
527
- const starts = [];
528
- const re = /(^|\n)\s*(?:نوع المشكلة|نوع المشكله)\s*[::]/g;
529
- let m; while((m=re.exec(norm))){ starts.push(m.index + (m[1] ? m[1].length : 0)); }
530
- if(starts.length>=2){
 
531
  const parts=[];
532
- for(let i=0;i<starts.length;i++){
533
- const s=starts[i], e=i+1<starts.length?starts[i+1]:norm.length;
534
- const slice = norm.slice(s,e).trim();
535
- if(slice) parts.push(slice);
536
  }
537
  return parts.join("\n\n🔴🔴🔴\n");
538
  }
@@ -544,12 +551,10 @@ async function smartPasteInto(el){
544
  const formatted = normalizeForPaste(txt || "");
545
  if(formatted){ el.value = formatted; saveState(); toast("تم اللصق والتنظيم."); }
546
  else { toast("الحافظة فارغة."); }
547
- }catch{
548
- toast("تعذّر قراءة الحافظة — الصق/ي يدويًا.");
549
- }
550
  }
551
 
552
- /* ======== تهيئة ======== */
553
  function init(){
554
  const parseBtn = document.getElementById("btn-parse");
555
  const exportBtn = document.getElementById("btn-export");
 
1
+ /* app.js — v10: Robust ticket splitting (no/one-line gaps), keep all previous features */
2
 
3
+ /* ======== أعمدة الجدول ======== */
4
  const EXPORT_COLUMNS = [
5
  "التصنيف",
6
  "نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
 
47
  const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
48
  function normalizeText(s){
49
  if(typeof s!=="string") return "";
50
+ return s.replace(/\r\n/g,"\n")
51
+ .replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ")
52
  .replace(/[٠-٩]/g, d => arabicDigitsMap[d] )
53
  .replace(/[ــ]+/g,"")
54
  .trim();
 
61
  }
62
  function digitsOnly(s){ return (s||"").replace(/\D+/g,""); }
63
 
64
+ /* وقت مثل "7:10 ص" أو "7 صباحا" أو "7مساء" */
65
  function normalizeTimeLoose(val){
66
  const t = normalizeText(val);
 
67
  let m = t.match(/(\d{1,2})[:٫\.\-:](\d{2})\s*(ص|صباح|صباحا|am|م|مساء|pm)?/i);
68
  if(m){
69
  let h = parseInt(m[1],10), mn = m[2], ampm = (m[3]||"").toLowerCase();
 
73
  }
74
  return `${String(h).padStart(2,"0")}:${mn}`;
75
  }
 
76
  m = t.match(/(\d{1,2})\s*(ص|صباح|صباحا|am|م|مساء|pm)/i);
77
  if(m){
78
  let h = parseInt(m[1],10);
 
100
  function parseDateTime(raw){
101
  const t = normalizeText(raw);
102
  const d = normalizeDate(t);
103
+ const hhmm = normalizeTimeLoose(t);
104
+ if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && hhmm) return `${d} ${hhmm}`;
105
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
106
+ if(hhmm) return hhmm;
107
  return t;
108
  }
109
 
110
+ /* قيمة بعد اسم حقل */
111
  function findAfterLabel(text, labels){
112
  const hay = "\n" + normalizeText(text) + "\n";
113
  for(const rawLbl of labels){
 
120
  return "";
121
  }
122
 
123
+ /* ==== تقسيم تذاكر قوي ==== */
124
+ const START_LABEL_GROUPS = [
125
+ ["نوع المشكلة","نوع المشكله"],
126
+ ["وقت حدوث المشكلة","وقت حدوث المشكله","وقت المشكلة","وقت حدوث"],
127
+ ["اسم صاحب المشكلة","اسم صاحب المشكله","اسم صاحب البلاغ","الاسم"],
128
+ ];
129
+ function findStartsByLabels(text, labels){
130
+ const lblRe = labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|");
131
+ const re = new RegExp(`(^|\\n)\\s*(?:-+|\\*+|•+)?\\s*(?:${lblRe})\\s*[::]`, "gi");
132
+ const starts = [];
133
+ let m;
134
+ while((m = re.exec(text))){
135
+ starts.push(m.index + (m[1] ? m[1].length : 0));
136
+ }
137
+ return starts;
138
+ }
139
  function splitTickets(raw){
140
+ const text = normalizeText(raw);
141
+ if(!text) return [];
 
142
 
143
  // 1) فواصل معروفة
144
+ if(TICKET_SEP.test(text)){
145
+ return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
146
  }
147
 
148
+ // 2) ابحث عن بدايات حسب مجموعات الحقول (نوع/وقت/اسم)
149
+ let starts = findStartsByLabels(text, START_LABEL_GROUPS[0]); // نوع المشكلة
150
+ if(starts.length < 2) starts = [...new Set([...starts, ...findStartsByLabels(text, START_LABEL_GROUPS[1])])]; // وقت حدوث
151
+ if(starts.length < 2) starts = [...new Set([...starts, ...findStartsByLabels(text, START_LABEL_GROUPS[2])])]; // اسم صاحب
152
+
153
+ // لو طلعنا بأكثر من بداية، قصّي بينها
 
 
154
  if(starts.length >= 2){
155
+ starts.sort((a,b)=>a-b);
156
+ const parts=[];
157
  for(let i=0;i<starts.length;i++){
158
  const s = starts[i];
159
+ const e = i+1<starts.length ? starts[i+1] : text.length;
160
+ const slice = text.slice(s,e).trim();
161
  if(slice) parts.push(slice);
162
  }
163
  return parts;
164
  }
165
 
166
+ // 3) fallback: أسطر فارغة/تجميع تلقائي
167
+ return text.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean);
168
  }
169
 
170
+ /* ==== استخراج الحقول ==== */
171
  function extractFields(ticketText){
172
  const text = normalizeText(ticketText);
173
  const out = {
 
214
  return out;
215
  }
216
 
217
+ /* ==== التصنيف ==== */
218
  function classifyTicket(text, fields){
219
  const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
220
  for(const label of CLASS_PRIORITY){
 
227
  return "استفسار";
228
  }
229
 
230
+ /* ==== تطبيق المنطقة/الاسم ==== */
231
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
232
  const regionChosen = (defaultRegion || "").toString();
233
  return splitTickets(raw||"").map(t => {
234
  const f = extractFields(t);
235
  const cls = classifyTicket(t, f);
236
  const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
 
237
  return {
238
  "التصنيف": cls,
239
  "نوع المشكلة": f["نوع المشكلة"] || "",
 
250
  });
251
  }
252
 
253
+ /* ==== جدول ==== */
254
  function buildTable(rows){
255
  const theadRow = document.getElementById("theadRow");
256
  const tbody = document.getElementById("tbody");
 
283
  return rows;
284
  }
285
 
286
+ /* ==== شارة + أزرار + تحقق ==== */
287
  function updateBadge(n){
288
  const b = document.getElementById("countBadge");
289
  b.textContent = n; b.hidden = (n===0);
 
320
  }
321
  });
322
 
323
+ /* ==== Toast ==== */
324
  function toast(msg){
325
  const t = document.getElementById("toast");
326
  t.textContent = msg; t.hidden = false;
 
328
  setTimeout(()=>{ t.hidden = true; }, 2000);
329
  }
330
 
331
+ /* ==== تصدير ExcelJS كما هو سابقًا (قالب منسّق) ==== */
332
  async function exportExcel(){
333
  const rows = readTable();
334
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
 
417
  toast("تم تنزيل الملف بتنسيق القالب.");
418
  }
419
 
420
+ /* ==== نسخ إلى الحافظة (TSV) ==== */
421
  async function copyToClipboardTSV(){
422
  const rows = readTable();
423
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
 
441
  }
442
  }
443
 
444
+ /* ==== مثال ==== */
445
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
446
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
447
  اسم صاحب المشكلة : نوف الناصر
 
450
  رقم الجوال: 0558174717
451
  اسم المسح: الخبر 2025
452
  اسم المنطقة: الشرقية
 
453
  نوع المشكلة: مشكله بالدخول (حدث خطأ في التطبيق)
454
  وقت حدوث المشكله:5:00 م
455
  اسم صاحب المشكله: شيماء عبدالرحمن
 
458
  رقم الجوال:0562974417
459
  المنطقه: الخبر
460
  المسح: تحديث الاسعار
 
461
  نوع المشكلة: معلق على صفحه البدايه
462
  وقت حدوث المشكله: 10:00
463
  اسم صاحب المشكله: امال صالح العبدالعزيز
 
467
  المنطقه: الخبر
468
  المسح:اسعار الجمله`;
469
 
470
+ /* ==== تخزين ==== */
471
+ const STATE_KEY = "ticketParserState_v10";
472
  function ensureColumns(rows, agentName, defaultRegion){
473
  if(!Array.isArray(rows)) return rows||[];
474
  return rows.map(r=>{
 
510
  }catch{ return false; }
511
  }
512
 
513
+ /* ==== مسح الكل ==== */
514
  function clearAll(){
515
  const rawEl = document.getElementById("raw");
516
  const tbody = document.getElementById("tbody");
 
527
  toast("تم مسح كل البيانات.");
528
  }
529
 
530
+ /* ==== لصق مُنظم (يضيف فواصل) ==== */
531
  function normalizeForPaste(text){
532
+ const norm = normalizeText(text||"");
533
+ const starts = findStartsByLabels(norm, ["نوع المشكلة","نوع المشكله"]);
534
+ let s = starts;
535
+ if(s.length<2) s = [...new Set([...s, ...findStartsByLabels(norm, START_LABEL_GROUPS[1])])];
536
+ if(s.length<2) s = [...new Set([...s, ...findStartsByLabels(norm, START_LABEL_GROUPS[2])])];
537
+ if(s.length>=2){
538
+ s.sort((a,b)=>a-b);
539
  const parts=[];
540
+ for(let i=0;i<s.length;i++){
541
+ const st=s[i], en=i+1<s.length?s[i+1]:norm.length;
542
+ const slice = norm.slice(st,en).trim(); if(slice) parts.push(slice);
 
543
  }
544
  return parts.join("\n\n🔴🔴🔴\n");
545
  }
 
551
  const formatted = normalizeForPaste(txt || "");
552
  if(formatted){ el.value = formatted; saveState(); toast("تم اللصق والتنظيم."); }
553
  else { toast("الحافظة فارغة."); }
554
+ }catch{ toast("تعذّر قراءة الحافظة — الصق/ي يدويًا."); }
 
 
555
  }
556
 
557
+ /* ==== تهيئة ==== */
558
  function init(){
559
  const parseBtn = document.getElementById("btn-parse");
560
  const exportBtn = document.getElementById("btn-export");