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

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +80 -60
app.js CHANGED
@@ -1,4 +1,4 @@
1
- /* app.js — v10: Robust ticket splitting (no/one-line gaps), keep all previous features */
2
 
3
  /* ======== أعمدة الجدول ======== */
4
  const EXPORT_COLUMNS = [
@@ -107,28 +107,14 @@ function parseDateTime(raw){
107
  return t;
108
  }
109
 
110
- /* قيمة بعد اسم حقل */
111
- function findAfterLabel(text, labels){
112
- const hay = "\n" + normalizeText(text) + "\n";
113
- for(const rawLbl of labels){
114
- const lbl = rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
115
- let m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`, "i"));
116
- if(m) return m[1].trim();
117
- m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`, "i"));
118
- if(m) return m[1].trim();
119
- }
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))){
@@ -136,23 +122,34 @@ function findStartsByLabels(text, labels){
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];
@@ -163,11 +160,38 @@ function splitTickets(raw){
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,7 +238,7 @@ function extractFields(ticketText){
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,7 +251,7 @@ function classifyTicket(text, fields){
227
  return "استفسار";
228
  }
229
 
230
- /* ==== تطبيق المنطقة/الاسم ==== */
231
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
232
  const regionChosen = (defaultRegion || "").toString();
233
  return splitTickets(raw||"").map(t => {
@@ -250,7 +274,7 @@ function parseTicketsWithExtras(raw, agentName, defaultRegion){
250
  });
251
  }
252
 
253
- /* ==== جدول ==== */
254
  function buildTable(rows){
255
  const theadRow = document.getElementById("theadRow");
256
  const tbody = document.getElementById("tbody");
@@ -283,7 +307,7 @@ function readTable(){
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,7 +344,7 @@ document.addEventListener("input",(e)=>{
320
  }
321
  });
322
 
323
- /* ==== Toast ==== */
324
  function toast(msg){
325
  const t = document.getElementById("toast");
326
  t.textContent = msg; t.hidden = false;
@@ -328,7 +352,7 @@ function toast(msg){
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,7 +441,7 @@ async function exportExcel(){
417
  toast("تم تنزيل الملف بتنسيق القالب.");
418
  }
419
 
420
- /* ==== نسخ إلى الحافظة (TSV) ==== */
421
  async function copyToClipboardTSV(){
422
  const rows = readTable();
423
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
@@ -441,7 +465,7 @@ async function copyToClipboardTSV(){
441
  }
442
  }
443
 
444
- /* ==== مثال ==== */
445
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
446
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
447
  اسم صاحب المشكلة : نوف الناصر
@@ -467,8 +491,8 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
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,7 +534,7 @@ function loadState(){
510
  }catch{ return false; }
511
  }
512
 
513
- /* ==== مسح الكل ==== */
514
  function clearAll(){
515
  const rawEl = document.getElementById("raw");
516
  const tbody = document.getElementById("tbody");
@@ -527,34 +551,30 @@ function clearAll(){
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
- }
546
- return norm;
547
  }
548
  async function smartPasteInto(el){
549
  try{
550
  const txt = await navigator.clipboard.readText();
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");
 
1
+ /* app.js — v10.1: تقسيم ذكي جدًا + لصق متعدد + كل التحسينات السابقة */
2
 
3
  /* ======== أعمدة الجدول ======== */
4
  const EXPORT_COLUMNS = [
 
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))){
 
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=[];
154
  for(let i=0;i<starts.length;i++){
155
  const s = starts[i];
 
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
+ /* ======== استخراج الحقول ======== */
184
+ function findAfterLabel(text, labels){
185
+ const hay = "\n" + normalizeText(text) + "\n";
186
+ for(const rawLbl of labels){
187
+ const lbl = rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
188
+ let m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`, "i"));
189
+ if(m) return m[1].trim();
190
+ m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`, "i"));
191
+ if(m) return m[1].trim();
192
+ }
193
+ return "";
194
+ }
195
  function extractFields(ticketText){
196
  const text = normalizeText(ticketText);
197
  const out = {
 
238
  return out;
239
  }
240
 
241
+ /* ======== التصنيف ======== */
242
  function classifyTicket(text, fields){
243
  const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
244
  for(const label of CLASS_PRIORITY){
 
251
  return "استفسار";
252
  }
253
 
254
+ /* ======== تطبيق المنطقة / الاسم ======== */
255
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
256
  const regionChosen = (defaultRegion || "").toString();
257
  return splitTickets(raw||"").map(t => {
 
274
  });
275
  }
276
 
277
+ /* ======== بناء/قراءة الجدول ======== */
278
  function buildTable(rows){
279
  const theadRow = document.getElementById("theadRow");
280
  const tbody = document.getElementById("tbody");
 
307
  return rows;
308
  }
309
 
310
+ /* ======== شارة + أزرار + تحقق ======== */
311
  function updateBadge(n){
312
  const b = document.getElementById("countBadge");
313
  b.textContent = n; b.hidden = (n===0);
 
344
  }
345
  });
346
 
347
+ /* ======== Toast ======== */
348
  function toast(msg){
349
  const t = document.getElementById("toast");
350
  t.textContent = msg; t.hidden = false;
 
352
  setTimeout(()=>{ t.hidden = true; }, 2000);
353
  }
354
 
355
+ /* ======== تصدير ExcelJS بتنسيق القالب ======== */
356
  async function exportExcel(){
357
  const rows = readTable();
358
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
 
441
  toast("تم تنزيل الملف بتنسيق القالب.");
442
  }
443
 
444
+ /* ======== نسخ إلى الحافظة (TSV) ======== */
445
  async function copyToClipboardTSV(){
446
  const rows = readTable();
447
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
 
465
  }
466
  }
467
 
468
+ /* ======== مثال جاهز للاختبار ======== */
469
  const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
470
  وقت حدوث المشكلة: 21/8/2025 7 صباحا
471
  اسم صاحب المشكلة : نوف الناصر
 
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
  }catch{ return false; }
535
  }
536
 
537
+ /* ======== مسح الكل (يشمل الاسم والمنطقة) ======== */
538
  function clearAll(){
539
  const rawEl = document.getElementById("raw");
540
  const tbody = document.getElementById("tbody");
 
551
  toast("تم مسح كل البيانات.");
552
  }
553
 
554
+ /* ======== لصق مُنظَّم (يمكن الضغط عدة مرات — يضيف ولا يستبدل) ======== */
555
  function normalizeForPaste(text){
556
  const norm = normalizeText(text||"");
557
+ const parts = splitTickets(norm);
558
+ return parts.length ? parts.join("\n\n🔴🔴🔴\n") : norm;
 
 
 
 
 
 
 
 
 
 
 
 
559
  }
560
  async function smartPasteInto(el){
561
  try{
562
  const txt = await navigator.clipboard.readText();
563
  const formatted = normalizeForPaste(txt || "");
564
+ if(!formatted){ toast("الحافظة فارغة."); return; }
565
+ if(el.value && el.value.trim()){
566
+ el.value = el.value.trimEnd() + "\n\n🔴🔴🔴\n" + formatted;
567
+ }else{
568
+ el.value = formatted;
569
+ }
570
+ saveState();
571
+ toast("تم اللصق والتنظيم (يمكن اللصق عدة مرات).");
572
+ }catch{
573
+ toast("تعذّر قراءة الحافظة — الصق/ي يدويًا.");
574
+ }
575
  }
576
 
577
+ /* ======== تهيئة ======== */
578
  function init(){
579
  const parseBtn = document.getElementById("btn-parse");
580
  const exportBtn = document.getElementById("btn-export");