stat2025 commited on
Commit
cd3155e
·
verified ·
1 Parent(s): 9704982

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +53 -33
app.js CHANGED
@@ -1,5 +1,4 @@
1
  /* ========= منطق التحليل والتصدير والنسخ (Static فقط) ========= */
2
- /* أعمدة الجدول */
3
  const EXPORT_COLUMNS = [
4
  "التصنيف",
5
  "نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
@@ -19,7 +18,7 @@ const FIELD_ALIASES = {
19
  "المنطقة": ["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"]
20
  };
21
 
22
- /* تصنيف بالكلمات المفتاحية */
23
  const CLASS_RULES = {
24
  "استفسار": ["استفسار","سؤال","استعلام","معلومة","استفسارات"],
25
  "إضافة أجهزة": ["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"],
@@ -102,18 +101,16 @@ function parseDateTime(raw){
102
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && time) return `${d} ${time}`;
103
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
104
  if(time) return time;
105
- return t; // إن لم نتعرّف صيغة
106
  }
107
 
108
- /* البحث عن قيمة بعد مسافة أو بعد نقطتين */
109
  function findAfterLabel(text, labels){
110
  const hay = "\n" + normalizeText(text) + "\n";
111
  for(const rawLbl of labels){
112
  const lbl = rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
113
- // 1) label : value
114
  let m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`, "i"));
115
  if(m) return m[1].trim();
116
- // 2) label value (بعد مسافة)
117
  m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`, "i"));
118
  if(m) return m[1].trim();
119
  }
@@ -129,7 +126,7 @@ function splitTickets(raw){
129
  return parts.map(p=>p.trim()).filter(Boolean);
130
  }
131
 
132
- /* استخراج الحقول بدقّة الأنواع */
133
  function extractFields(ticketText){
134
  const text = normalizeText(ticketText);
135
  const out = {
@@ -137,7 +134,7 @@ function extractFields(ticketText){
137
  "رقم الهوية":"", "رقم الجهاز":"", "رقم الجوال":"", "المسح":"", "المنطقة":""
138
  };
139
 
140
- // نوع المشكلة: حروف/أرقام/كلاهما
141
  let v = findAfterLabel(text, FIELD_ALIASES["نوع المشكلة"]);
142
  if(v) out["نوع المشكلة"] = alnumAr(v);
143
 
@@ -145,15 +142,15 @@ function extractFields(ticketText){
145
  v = findAfterLabel(text, FIELD_ALIASES["وقت حدوث المشكلة"]);
146
  if(v) out["وقت حدوث المشكلة"] = parseDateTime(v);
147
 
148
- // اسم صاحب المشكلة: نتركه كما هو (نظيف)
149
  v = findAfterLabel(text, FIELD_ALIASES["اسم صاحب المشكلة"]);
150
  if(v) out["اسم صاحب المشكلة"] = v;
151
 
152
- // رقم الهوية: أرقام فقط
153
  v = findAfterLabel(text, FIELD_ALIASES["رقم الهوية"]);
154
  if(v) out["رقم الهوية"] = digitsOnly(v);
155
  if(!out["رقم الهوية"]){
156
- const m = text.match(/(?:^|\D)(1\d{9})(?:\D|$)/);
157
  if(m) out["رقم الهوية"] = m[1];
158
  }
159
 
@@ -161,7 +158,7 @@ function extractFields(ticketText){
161
  v = findAfterLabel(text, FIELD_ALIASES["رقم الجهاز"]);
162
  if(v) out["رقم الجهاز"] = digitsOnly(v);
163
  if(!out["رقم الجهاز"]){
164
- const m = text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/); // احتياط
165
  if(m) out["رقم الجهاز"] = m[1];
166
  }
167
 
@@ -169,15 +166,15 @@ function extractFields(ticketText){
169
  v = findAfterLabel(text, FIELD_ALIASES["رقم الجوال"]);
170
  if(v) out["رقم الجوال"] = digitsOnly(v);
171
  if(!out["رقم الجوال"]){
172
- const m = text.match(/(?:^|\D)(05\d{8})(?:\D|$)/);
173
  if(m) out["رقم الجوال"] = m[1];
174
  }
175
 
176
- // اسم المسح: حروف/أرقام/كلاهما
177
  v = findAfterLabel(text, FIELD_ALIASES["المسح"]);
178
  if(v) out["المسح"] = alnumAr(v);
179
 
180
- // اسم المنطقة: إن لم تُختر من القائمة لاحقًا نأخذها من التذكرة "حروف فقط"
181
  v = findAfterLabel(text, FIELD_ALIASES["المنطقة"]);
182
  if(v) out["المنطقة"] = lettersOnly(v);
183
 
@@ -197,13 +194,12 @@ function classifyTicket(text, fields){
197
  return "استفسار";
198
  }
199
 
200
- /* المنطقة المختارة تُطبّق على الجميع، وإلا نستخدم ما في التذكرة (حروف فقط) */
201
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
202
- const regionChosen = (defaultRegion || "").toString(); // مثال: "4. الشرقية"
203
  return splitTickets(raw||"").map(t => {
204
  const f = extractFields(t);
205
  const cls = classifyTicket(t, f);
206
-
207
  const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
208
 
209
  return {
@@ -222,7 +218,7 @@ function parseTicketsWithExtras(raw, agentName, defaultRegion){
222
  });
223
  }
224
 
225
- /* بناء الجدول/القراءة */
226
  function buildTable(rows){
227
  const theadRow = document.getElementById("theadRow");
228
  const tbody = document.getElementById("tbody");
@@ -269,13 +265,21 @@ function validateCells(){
269
  const idxPhone = EXPORT_COLUMNS.indexOf("رقم الجوال");
270
  const idxID = EXPORT_COLUMNS.indexOf("رقم الهوية");
271
  [...tbody.rows].forEach(tr=>{
 
272
  if(idxPhone>=0){
273
- const td=tr.children[idxPhone], v=td.textContent.trim();
274
- const ok=/^05\d{8}$/.test(v); td.classList.toggle("invalid", v && !ok);
 
 
 
275
  }
 
276
  if(idxID>=0){
277
- const td=tr.children[idxID], v=td.textContent.trim();
278
- const ok=/^1\d{9}$/.test(v); td.classList.toggle("invalid", v && !ok);
 
 
 
279
  }
280
  });
281
  }
@@ -294,7 +298,7 @@ function toast(msg){
294
  setTimeout(()=>{ t.hidden = true; }, 2000);
295
  }
296
 
297
- /* تصدير Excel مع حفظ الأصفار كنص للأعمدة الرقمية */
298
  async function exportExcel(){
299
  const rows = readTable();
300
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
@@ -323,8 +327,7 @@ async function exportExcel(){
323
  const wb = XLSX.utils.book_new();
324
  XLSX.utils.book_append_sheet(wb, ws, "التذاكر");
325
 
326
- const now = new Date();
327
- const ts = now.toISOString().replace(/\D/g,'').slice(0,14);
328
  const base = (document.getElementById("fname").value || "Ticket").trim() || "Ticket";
329
  const filename = `${base}_${ts}.xlsx`;
330
 
@@ -343,7 +346,7 @@ async function exportExcel(){
343
  toast("تم تنزيل الملف.");
344
  }
345
 
346
- /* نسخ إلى الحافظة (TSV) مع BOM */
347
  async function copyToClipboardTSV(){
348
  const rows = readTable();
349
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
@@ -381,7 +384,9 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
381
  اسم المنطقة: الشرقية`;
382
 
383
  /* حفظ الحالة */
384
- const STATE_KEY = "ticketParserState_v4";
 
 
385
  function ensureColumns(rows, agentName, defaultRegion){
386
  if(!Array.isArray(rows)) return rows||[];
387
  return rows.map(r=>{
@@ -396,6 +401,7 @@ function ensureColumns(rows, agentName, defaultRegion){
396
  return out;
397
  });
398
  }
 
399
  function saveState(){
400
  try{
401
  const raw = document.getElementById("raw")?.value || "";
@@ -406,6 +412,7 @@ function saveState(){
406
  localStorage.setItem(STATE_KEY, JSON.stringify({ raw, fname, agent, region, rows }));
407
  }catch{}
408
  }
 
409
  function loadState(){
410
  try{
411
  const s = localStorage.getItem(STATE_KEY);
@@ -422,13 +429,26 @@ function loadState(){
422
  return true;
423
  }catch{ return false; }
424
  }
425
- function clearState(){ try{ localStorage.removeItem(STATE_KEY); }catch{} }
426
- function wipeUI(){
 
 
 
 
427
  const rawEl = document.getElementById("raw");
428
  const tbody = document.getElementById("tbody");
 
429
  if(rawEl) rawEl.value = "";
430
  if(tbody) tbody.innerHTML = "";
 
431
  updateBadge(0); setButtonsEnabled(false);
 
 
 
 
 
 
 
432
  }
433
 
434
  /* تهيئة */
@@ -453,15 +473,15 @@ function init(){
453
  buildTable(rows); validateCells();
454
  updateBadge(rows.length); setButtonsEnabled(rows.length>0);
455
  saveState();
456
- if(defRegion) toast(`تم استخراج ${rows.length} تذكرة وتطبيق المنطقة: ${defRegion}`);
457
- else toast(`تم استخراج ${rows.length} تذكرة.`);
458
  });
459
 
460
  exportBtn.addEventListener("click", exportExcel);
461
  copyBtn.addEventListener("click", copyToClipboardTSV);
462
 
463
  clearBtn.addEventListener("click", ()=>{
464
- clearState(); wipeUI(); toast("تم مسح كل البيانات.");
 
465
  });
466
 
467
  sampleBtn.addEventListener("click", ()=>{ rawEl.value = SAMPLE; saveState(); });
 
1
  /* ========= منطق التحليل والتصدير والنسخ (Static فقط) ========= */
 
2
  const EXPORT_COLUMNS = [
3
  "التصنيف",
4
  "نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
 
18
  "المنطقة": ["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"]
19
  };
20
 
21
+ /* قواعد التصنيف */
22
  const CLASS_RULES = {
23
  "استفسار": ["استفسار","سؤال","استعلام","معلومة","استفسارات"],
24
  "إضافة أجهزة": ["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"],
 
101
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && time) return `${d} ${time}`;
102
  if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
103
  if(time) return time;
104
+ return t;
105
  }
106
 
107
+ /* قيمة بعد مسافة أو بعد نقطتين */
108
  function findAfterLabel(text, labels){
109
  const hay = "\n" + normalizeText(text) + "\n";
110
  for(const rawLbl of labels){
111
  const lbl = rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
 
112
  let m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`, "i"));
113
  if(m) return m[1].trim();
 
114
  m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`, "i"));
115
  if(m) return m[1].trim();
116
  }
 
126
  return parts.map(p=>p.trim()).filter(Boolean);
127
  }
128
 
129
+ /* استخراج بالأنواع المطلوبة */
130
  function extractFields(ticketText){
131
  const text = normalizeText(ticketText);
132
  const out = {
 
134
  "رقم الهوية":"", "رقم الجهاز":"", "رقم الجوال":"", "المسح":"", "المنطقة":""
135
  };
136
 
137
+ // نوع المشكلة: حروف/أرقام/معًا
138
  let v = findAfterLabel(text, FIELD_ALIASES["نوع المشكلة"]);
139
  if(v) out["نوع المشكلة"] = alnumAr(v);
140
 
 
142
  v = findAfterLabel(text, FIELD_ALIASES["وقت حدوث المشكلة"]);
143
  if(v) out["وقت حدوث المشكلة"] = parseDateTime(v);
144
 
145
+ // اسم صاحب المشكلة: كما هو
146
  v = findAfterLabel(text, FIELD_ALIASES["اسم صاحب المشكلة"]);
147
  if(v) out["اسم صاحب المشكلة"] = v;
148
 
149
+ // رقم الهوية: أرقام فقط (10 لاحقًا في التحقق)
150
  v = findAfterLabel(text, FIELD_ALIASES["رقم الهوية"]);
151
  if(v) out["رقم الهوية"] = digitsOnly(v);
152
  if(!out["رقم الهوية"]){
153
+ const m = text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/);
154
  if(m) out["رقم الهوية"] = m[1];
155
  }
156
 
 
158
  v = findAfterLabel(text, FIELD_ALIASES["رقم الجهاز"]);
159
  if(v) out["رقم الجهاز"] = digitsOnly(v);
160
  if(!out["رقم الجهاز"]){
161
+ const m = text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/);
162
  if(m) out["رقم الجهاز"] = m[1];
163
  }
164
 
 
166
  v = findAfterLabel(text, FIELD_ALIASES["رقم الجوال"]);
167
  if(v) out["رقم الجوال"] = digitsOnly(v);
168
  if(!out["رقم الجوال"]){
169
+ const m = text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/); // 05 + 7 أو أكثر
170
  if(m) out["رقم الجوال"] = m[1];
171
  }
172
 
173
+ // اسم المسح: حروف/أرقام/معًا
174
  v = findAfterLabel(text, FIELD_ALIASES["المسح"]);
175
  if(v) out["المسح"] = alnumAr(v);
176
 
177
+ // اسم المنطقة: إن لم تُختر من القائمة لاحقًا نأخذها حروف فقط
178
  v = findAfterLabel(text, FIELD_ALIASES["المنطقة"]);
179
  if(v) out["المنطقة"] = lettersOnly(v);
180
 
 
194
  return "استفسار";
195
  }
196
 
197
+ /* طبّق المنطقة المختارة على الجميع، وإلا استخدم ما في التذكرة */
198
  function parseTicketsWithExtras(raw, agentName, defaultRegion){
199
+ const regionChosen = (defaultRegion || "").toString();
200
  return splitTickets(raw||"").map(t => {
201
  const f = extractFields(t);
202
  const cls = classifyTicket(t, f);
 
203
  const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
204
 
205
  return {
 
218
  });
219
  }
220
 
221
+ /* بناء الجدول وقراءته */
222
  function buildTable(rows){
223
  const theadRow = document.getElementById("theadRow");
224
  const tbody = document.getElementById("tbody");
 
265
  const idxPhone = EXPORT_COLUMNS.indexOf("رقم الجوال");
266
  const idxID = EXPORT_COLUMNS.indexOf("رقم الهوية");
267
  [...tbody.rows].forEach(tr=>{
268
+ // الجوال: خطأ فقط إذا أقل من 9 أرقام (إن كانت الخانة غير فارغة)
269
  if(idxPhone>=0){
270
+ const td=tr.children[idxPhone];
271
+ const raw=(td.textContent||"").trim();
272
+ const digits = raw.replace(/\D/g,"");
273
+ const invalid = !!raw && digits.length < 9;
274
+ td.classList.toggle("invalid", invalid);
275
  }
276
+ // الهوية: خطأ فقط إذا ليست 10 أرقام بالضبط (إن كانت الخانة غير فارغة)
277
  if(idxID>=0){
278
+ const td=tr.children[idxID];
279
+ const raw=(td.textContent||"").trim();
280
+ const digits = raw.replace(/\D/g,"");
281
+ const invalid = !!raw && digits.length !== 10;
282
+ td.classList.toggle("invalid", invalid);
283
  }
284
  });
285
  }
 
298
  setTimeout(()=>{ t.hidden = true; }, 2000);
299
  }
300
 
301
+ /* تصدير Excel (حفظ الأصفار كنص للأعمدة الرقمية) */
302
  async function exportExcel(){
303
  const rows = readTable();
304
  if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
 
327
  const wb = XLSX.utils.book_new();
328
  XLSX.utils.book_append_sheet(wb, ws, "التذاكر");
329
 
330
+ const ts = new Date().toISOString().replace(/\D/g,'').slice(0,14);
 
331
  const base = (document.getElementById("fname").value || "Ticket").trim() || "Ticket";
332
  const filename = `${base}_${ts}.xlsx`;
333
 
 
346
  toast("تم تنزيل الملف.");
347
  }
348
 
349
+ /* نسخ إلى الحافظة (TSV) */
350
  async function copyToClipboardTSV(){
351
  const rows = readTable();
352
  if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
 
384
  اسم المنطقة: الشرقية`;
385
 
386
  /* حفظ الحالة */
387
+ const STATE_KEY = "ticketParserState_v6";
388
+
389
+ /* عند التحميل نضمن الأعمدة والقيم، ونُبقي المنطقة المختارة مطبّقة على كل الصفوف */
390
  function ensureColumns(rows, agentName, defaultRegion){
391
  if(!Array.isArray(rows)) return rows||[];
392
  return rows.map(r=>{
 
401
  return out;
402
  });
403
  }
404
+
405
  function saveState(){
406
  try{
407
  const raw = document.getElementById("raw")?.value || "";
 
412
  localStorage.setItem(STATE_KEY, JSON.stringify({ raw, fname, agent, region, rows }));
413
  }catch{}
414
  }
415
+
416
  function loadState(){
417
  try{
418
  const s = localStorage.getItem(STATE_KEY);
 
429
  return true;
430
  }catch{ return false; }
431
  }
432
+
433
+ /* مسح واجهة فقط مع الحفاظ على اسم الدعم والمنطقة في الواجهة والتخزين */
434
+ function clearKeepAgentRegion(){
435
+ const agent = document.getElementById("agentName")?.value || "";
436
+ const region= document.getElementById("regionDefault")?.value || "";
437
+ // نظّف الواجهة
438
  const rawEl = document.getElementById("raw");
439
  const tbody = document.getElementById("tbody");
440
+ const fnameEl = document.getElementById("fname");
441
  if(rawEl) rawEl.value = "";
442
  if(tbody) tbody.innerHTML = "";
443
+ if(fnameEl) fnameEl.value = "Ticket";
444
  updateBadge(0); setButtonsEnabled(false);
445
+
446
+ // حدّث التخزين مع إبقاء اسم الدعم والمنطقة
447
+ try{
448
+ localStorage.setItem(STATE_KEY, JSON.stringify({
449
+ raw: "", fname: "Ticket", agent, region, rows: []
450
+ }));
451
+ }catch{}
452
  }
453
 
454
  /* تهيئة */
 
473
  buildTable(rows); validateCells();
474
  updateBadge(rows.length); setButtonsEnabled(rows.length>0);
475
  saveState();
476
+ toast(defRegion ? `تم استخراج ${rows.length} وتطبيق المنطقة: ${defRegion}` : `تم استخراج ${rows.length} تذكرة.`);
 
477
  });
478
 
479
  exportBtn.addEventListener("click", exportExcel);
480
  copyBtn.addEventListener("click", copyToClipboardTSV);
481
 
482
  clearBtn.addEventListener("click", ()=>{
483
+ clearKeepAgentRegion();
484
+ toast("تم مسح النص والجدول — تم الإبقاء على اسم الدعم والمنطقة.");
485
  });
486
 
487
  sampleBtn.addEventListener("click", ()=>{ rawEl.value = SAMPLE; saveState(); });