Spaces:
Running
Running
| /* v10.3: Smart Paste append + reliable modal fallback + safe splitting + hard clear */ | |
| const EXPORT_COLUMNS = [ | |
| "التصنيف","نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة", | |
| "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة","اسم الدعم الفني","الحالة" | |
| ]; | |
| const FIELD_ALIASES = { | |
| "نوع المشكلة": ["نوع المشكله","نوع المشكلة","المشكلة"], | |
| "وقت حدوث المشكلة": ["وقت حدوث المشكله","وقت حدوث المشكلة","وقت المشكلة","وقت حدوث"], | |
| "اسم صاحب المشكلة": ["اسم صاحب المشكله","اسم صاحب المشكلة","اسم صاحب البلاغ","الاسم"], | |
| "رقم الهوية": ["رقم الهويه","رقم الهوية","الهوية"], | |
| "رقم الجهاز": ["رقم الجهاز","الجهاز"], | |
| "رقم الجوال": ["رقم الجوال","الجوال","الهاتف"], | |
| "المسح": ["المسح","اسم المسح"], | |
| "المنطقة": ["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"] | |
| }; | |
| const CLASS_RULES = { | |
| "استفسار": ["استفسار","سؤال","استعلام","معلومة","استفسارات"], | |
| "إضافة أجهزة": ["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"], | |
| "الاستمارة": ["الاستمارة","استمارة","النموذج","نموذج","الفورم","تعليق الاستمارة","لا استطيع اكمال الاستمارة","التعبئة"], | |
| "التقييم": ["التقييم","تقييم","feedback","survey","رضا","نجوم"], | |
| "الخرائط": ["الخرائط","خرائط","map","gps","تحديد الموقع","احداثيات","إحداثيات","الموقع الجغرافي"], | |
| "السوتي": ["السوتي","سوتي","soti","soti assist","mobicontrol","soti mobicontrol"], | |
| "الشبكة": ["الشبكة","شبكة","نت","انترنت","إنترنت","wifi","واي فاي","4g","5g","ضعف الشبكة","stc","mobily","زين","weak signal","no signal"], | |
| "النسخة": ["النسخة","نسخة","الإصدار","اصدار","version","build","release","تحديث نسخة","ترقية النسخة"], | |
| "النظام المكتبي": ["النظام المكتبي","نسخة ويندوز","ويندوز","windows","pc app","برنامج المكتب","التطبيق على الكمبيوتر","الديسكتوب"], | |
| "تسجيل دخول": ["تسجيل دخول","تسجيل الدخول","login","signin","رفض تسجيل الدخول","لا يقبل الدخول","اسم المستخدم","كلمة المرور","نسيت كلمة السر","إعادة تعيين"], | |
| "تفعيل حساب": ["تفعيل حساب","تفعيل","activation","activate","رمز التفعيل","كود التفعيل"], | |
| "تناقل البيانات": ["تناقل البيانات","ترحيل البيانات","مزامنة","sync","مزامنه","نقل البيانات","رفع البيانات","sync failed","المزامنة"], | |
| "صيانة وتحديث الأجهزة": ["صيانة","تحديث الأجهزة","تحديث جهاز","ترقية الجهاز","اعطال الجهاز","تصليح","صيانة وتحديث الأجهزة","صيانة الجهاز"], | |
| }; | |
| const CLASS_PRIORITY = [ | |
| "صيانة وتحديث الأجهزة","إضافة أجهزة","تسجيل دخول","تفعيل حساب", | |
| "الاستمارة","التقييم","الخرائط","السوتي","الشبكة","النسخة", | |
| "النظام المكتبي","تناقل البيانات","استفسار", | |
| ]; | |
| const TICKET_SEP = /\n\s*(?:\n|—+|-{3,}|={3,}|🔴+)+\s*\n/; | |
| const MIN_SPLIT_SPAN = 80; | |
| const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"}; | |
| function normalizeText(s){ | |
| if(typeof s!=="string") return ""; | |
| return s.replace(/\r\n/g,"\n") | |
| .replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ") | |
| .replace(/[٠-٩]/g, d => arabicDigitsMap[d] ) | |
| .replace(/[ــ]+/g,"") | |
| .trim(); | |
| } | |
| function lettersOnly(ar){ return (ar||"").replace(/[^A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s]/g,"").replace(/\s{2,}/g," ").trim(); } | |
| function alnumAr(s){ return (s||"").replace(/[^0-9A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s\-\._/]/g,"").replace(/\s{2,}/g," ").trim(); } | |
| function digitsOnly(s){ return (s||"").replace(/\D+/g,""); } | |
| function normalizeTimeLoose(val){ | |
| const t = normalizeText(val); | |
| let m = t.match(/(\d{1,2})[:٫\.\-:](\d{2})\s*(ص|صباح|صباحا|am|م|مساء|pm)?/i); | |
| if(m){ | |
| let h = parseInt(m[1],10), mn = m[2], ampm = (m[3]||"").toLowerCase(); | |
| if(ampm){ | |
| if((/م|مساء|pm/).test(ampm) && h<12) h+=12; | |
| if((/ص|صباح|صباحا|am/).test(ampm) && h===12) h=0; | |
| } | |
| return `${String(h).padStart(2,"0")}:${mn}`; | |
| } | |
| m = t.match(/(\d{1,2})\s*(ص|صباح|صباحا|am|م|مساء|pm)/i); | |
| if(m){ | |
| let h = parseInt(m[1],10); | |
| const ampm = (m[2]||"").toLowerCase(); | |
| if((/م|مساء|pm/).test(ampm) && h<12) h+=12; | |
| if((/ص|صباح|صباحا|am/).test(ampm) && h===12) h=0; | |
| return `${String(h).padStart(2,"0")}:00`; | |
| } | |
| return ""; | |
| } | |
| function normalizeDate(v){ | |
| v=(v||"").trim(); | |
| let m=v.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/); | |
| if(m){ | |
| let d=+m[1], mo=+m[2], y=+m[3]; if(y<100) y+=2000; | |
| return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).toString().padStart(2,"0")}`; | |
| } | |
| m=v.match(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/); | |
| if(m){ | |
| let y=+m[1], mo=+m[2], d=+m[3]; | |
| return `${y.toString().padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).toString().padStart(2,"0")}`; | |
| } | |
| return v; | |
| } | |
| function parseDateTime(raw){ | |
| const t = normalizeText(raw); | |
| const d = normalizeDate(t); | |
| const hhmm = normalizeTimeLoose(t); | |
| if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && hhmm) return `${d} ${hhmm}`; | |
| if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d; | |
| if(hhmm) return hhmm; | |
| return t; | |
| } | |
| function findStartsByLabels(text, labels){ | |
| const lblRe = labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|"); | |
| const re = new RegExp(`(^|\\n)\\s*(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:${lblRe})(?:\\s*[::]|\\s+)`,"gi"); | |
| const idxs=[]; let m; | |
| while((m = re.exec(text))){ idxs.push(m.index + (m[1]?m[1].length:0)); } | |
| return idxs; | |
| } | |
| function splitTickets(raw){ | |
| const text = normalizeText(raw); | |
| if(!text) return []; | |
| if(TICKET_SEP.test(text)){ | |
| return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean); | |
| } | |
| const starts = findStartsByLabels(text, ["نوع المشكلة","نوع المشكله"]) | |
| .sort((a,b)=>a-b) | |
| .filter((pos,i,arr)=> i===0 || (pos - arr[i-1]) >= MIN_SPLIT_SPAN); | |
| if(starts.length >= 2){ | |
| const parts=[]; | |
| for(let i=0;i<starts.length;i++){ | |
| const s = starts[i]; | |
| const e = i+1<starts.length ? starts[i+1] : text.length; | |
| const slice = text.slice(s,e).trim(); | |
| if(slice) parts.push(slice); | |
| } | |
| return parts; | |
| } | |
| const blocks = text.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean); | |
| if(blocks.length>1) return blocks; | |
| return [text]; | |
| } | |
| function findAfterLabel(text, labels){ | |
| const hay = "\n" + normalizeText(text) + "\n"; | |
| for(const rawLbl of labels){ | |
| const lbl = rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); | |
| let m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`, "i")); | |
| if(m) return m[1].trim(); | |
| m = hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`, "i")); | |
| if(m) return m[1].trim(); | |
| } | |
| return ""; | |
| } | |
| function extractFields(ticketText){ | |
| const text = normalizeText(ticketText); | |
| const out = { | |
| "نوع المشكلة":"", "وقت حدوث المشكلة":"", "اسم صاحب المشكلة":"", | |
| "رقم الهوية":"", "رقم الجهاز":"", "رقم الجوال":"", "المسح":"", "المنطقة":"" | |
| }; | |
| let v = findAfterLabel(text, FIELD_ALIASES["نوع المشكلة"]); | |
| if(v) out["نوع المشكلة"] = alnumAr(v); | |
| v = findAfterLabel(text, FIELD_ALIASES["وقت حدوث المشكلة"]); | |
| if(v) out["وقت حدوث المشكلة"] = parseDateTime(v); | |
| v = findAfterLabel(text, FIELD_ALIASES["اسم صاحب المشكلة"]); | |
| if(v) out["اسم صاحب المشكلة"] = v; | |
| v = findAfterLabel(text, FIELD_ALIASES["رقم الهوية"]); | |
| if(v) out["رقم الهوية"] = digitsOnly(v); | |
| if(!out["رقم الهوية"]){ | |
| const m = text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/); | |
| if(m) out["رقم الهوية"] = m[1]; | |
| } | |
| v = findAfterLabel(text, FIELD_ALIASES["رقم الجهاز"]); | |
| if(v) out["رقم الجهاز"] = digitsOnly(v); | |
| if(!out["رقم الجهاز"]){ | |
| const m = text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/); | |
| if(m) out["رقم الجهاز"] = m[1]; | |
| } | |
| v = findAfterLabel(text, FIELD_ALIASES["رقم الجوال"]); | |
| if(v) out["رقم الجوال"] = digitsOnly(v); | |
| if(!out["رقم الجوال"]){ | |
| const m = text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/); | |
| if(m) out["رقم الجوال"] = m[1]; | |
| } | |
| v = findAfterLabel(text, FIELD_ALIASES["المسح"]); | |
| if(v) out["المسح"] = alnumAr(v); | |
| v = findAfterLabel(text, FIELD_ALIASES["المنطقة"]); | |
| if(v) out["المنطقة"] = lettersOnly(v); | |
| return out; | |
| } | |
| function classifyTicket(text, fields){ | |
| const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase(); | |
| for(const label of CLASS_PRIORITY){ | |
| const kws = CLASS_RULES[label] || []; | |
| for(const kw of kws){ | |
| const needle = normalizeText(kw).toLowerCase(); | |
| if(needle && hay.includes(needle)) return label; | |
| } | |
| } | |
| return "استفسار"; | |
| } | |
| function parseTicketsWithExtras(raw, agentName, defaultRegion){ | |
| const regionChosen = (defaultRegion || "").toString(); | |
| return splitTickets(raw||"").map(t => { | |
| const f = extractFields(t); | |
| const cls = classifyTicket(t, f); | |
| const region = regionChosen ? regionChosen : (f["المنطقة"] || ""); | |
| return { | |
| "التصنيف": cls, | |
| "نوع المشكلة": f["نوع المشكلة"] || "", | |
| "وقت حدوث المشكلة": f["وقت حدوث المشكلة"] || "", | |
| "اسم صاحب المشكلة": f["اسم صاحب المشكلة"] || "", | |
| "رقم الهوية": f["رقم الهوية"] || "", | |
| "رقم الجهاز": f["رقم الجهاز"] || "", | |
| "رقم الجوال": f["رقم الجوال"] || "", | |
| "المسح": f["المسح"] || "", | |
| "المنطقة": region, | |
| "اسم الدعم الفني": agentName || "", | |
| "الحالة": "تم الحل", | |
| }; | |
| }); | |
| } | |
| function buildTable(rows){ | |
| const theadRow = document.getElementById("theadRow"); | |
| const tbody = document.getElementById("tbody"); | |
| theadRow.innerHTML = ""; | |
| EXPORT_COLUMNS.forEach(col=>{ | |
| const th=document.createElement("th"); | |
| th.textContent=col; | |
| theadRow.appendChild(th); | |
| }); | |
| tbody.innerHTML = ""; | |
| rows.forEach(r=>{ | |
| const tr=document.createElement("tr"); | |
| EXPORT_COLUMNS.forEach(col=>{ | |
| const td=document.createElement("td"); | |
| td.contentEditable="true"; | |
| td.textContent = r[col]||""; | |
| tr.appendChild(td); | |
| }); | |
| tbody.appendChild(tr); | |
| }); | |
| } | |
| function readTable(){ | |
| const tbody = document.getElementById("tbody"); | |
| const rows = []; | |
| [...tbody.querySelectorAll("tr")].forEach(tr=>{ | |
| const obj={}; | |
| [...tr.children].forEach((td,idx)=>{ obj[EXPORT_COLUMNS[idx]] = td.textContent.trim(); }); | |
| rows.push(obj); | |
| }); | |
| return rows; | |
| } | |
| function updateBadge(n){ | |
| const b = document.getElementById("countBadge"); | |
| b.textContent = n; b.hidden = (n===0); | |
| } | |
| function setButtonsEnabled(hasRows){ | |
| document.getElementById("btn-export").disabled = !hasRows; | |
| document.getElementById("btn-copy").disabled = !hasRows; | |
| } | |
| function validateCells(){ | |
| const tbody=document.getElementById("tbody"); | |
| const idxPhone = EXPORT_COLUMNS.indexOf("رقم الجوال"); | |
| const idxID = EXPORT_COLUMNS.indexOf("رقم الهوية"); | |
| [...tbody.rows].forEach(tr=>{ | |
| if(idxPhone>=0){ | |
| const td=tr.children[idxPhone]; | |
| const raw=(td.textContent||"").trim(); | |
| const digits = raw.replace(/\D/g,""); | |
| const invalid = !!raw && digits.length < 9; | |
| td.classList.toggle("invalid", invalid); | |
| } | |
| if(idxID>=0){ | |
| const td=tr.children[idxID]; | |
| const raw=(td.textContent||"").trim(); | |
| const digits = raw.replace(/\D/g,""); | |
| const invalid = !!raw && digits.length !== 10; | |
| td.classList.toggle("invalid", invalid); | |
| } | |
| }); | |
| } | |
| document.addEventListener("input",(e)=>{ | |
| if(e.target && e.target.closest && e.target.closest("#tbody")){ | |
| validateCells(); | |
| saveState(); | |
| } | |
| }); | |
| function toast(msg){ | |
| const t = document.getElementById("toast"); | |
| t.textContent = msg; t.hidden = false; | |
| t.classList.remove("show"); void t.offsetWidth; t.classList.add("show"); | |
| setTimeout(()=>{ t.hidden = true; }, 2000); | |
| } | |
| async function exportExcel(){ | |
| const rows = readTable(); | |
| if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; } | |
| const TEMPLATE_HEADERS = [ | |
| "نوع المشكلة","وصف المشكلة","المنطقة","اسم المسح","اسم المشغل", | |
| "رقم الجوال","رقم الهوية ID","رقم الجهاز","تاريخ اليوم بالميلادي","الحالة","اسم الدعم الفني", | |
| ]; | |
| const mapRow = (r)=>{ | |
| const today = new Date(); | |
| const yyyy=today.getFullYear(), mm=String(today.getMonth()+1).padStart(2,"0"), dd=String(today.getDate()).padStart(2,"0"); | |
| const todayStr = `${yyyy}-${mm}-${dd}`; | |
| return { | |
| "نوع المشكلة": r["نوع المشكلة"]||"", | |
| "وصف المشكلة": r["التصنيف"] || r["نوع المشكلة"] || "", | |
| "المنطقة": r["المنطقة"]||"", | |
| "اسم المسح": r["المسح"]||"", | |
| "اسم المشغل": r["اسم صاحب المشكلة"]||"", | |
| "رقم الجوال": (r["رقم الجوال"]||"").toString(), | |
| "رقم الهوية ID":(r["رقم الهوية"]||"").toString(), | |
| "رقم الجهاز": (r["رقم الجهاز"]||"").toString(), | |
| "تاريخ اليوم بالميلادي": todayStr, | |
| "الحالة": r["الحالة"]||"تم الحل", | |
| "اسم الدعم الفني": r["اسم الدعم الفني"]||"", | |
| }; | |
| }; | |
| const wb = new ExcelJS.Workbook(); | |
| const ws = wb.addWorksheet("التذاكر", { views: [{ rightToLeft: true }] }); | |
| const colWidths = [18,26,16,18,20,18,18,18,22,14,18]; | |
| TEMPLATE_HEADERS.forEach((h,i)=> ws.getColumn(i+1).width = colWidths[i]||18); | |
| ws.addRow(TEMPLATE_HEADERS); | |
| const headerRow = ws.getRow(1); | |
| headerRow.height = 24; | |
| headerRow.eachCell((cell)=>{ | |
| cell.font = { bold:true, color:{argb:"FFFFFFFF"} }; | |
| cell.alignment = { horizontal:"center", vertical:"middle" }; | |
| cell.fill = { type:"pattern", pattern:"solid", fgColor:{argb:"FF4137A8"} }; | |
| cell.border = { | |
| top:{style:"thin",color:{argb:"FFCDD2E1"}}, | |
| bottom:{style:"thin",color:{argb:"FFCDD2E1"}}, | |
| left:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| right:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| }; | |
| }); | |
| const toTextCols = new Set(["رقم الجوال","رقم الهوية ID","رقم الجهاز"]); | |
| const rawRows = readTable(); | |
| rawRows.forEach((r,idx)=>{ | |
| const m = mapRow(r); | |
| const vals = TEMPLATE_HEADERS.map(h => (m[h] ?? "")); | |
| const row = ws.addRow(vals); | |
| row.alignment = { horizontal:"center", vertical:"middle" }; | |
| const even = (idx % 2) === 1; | |
| row.eachCell((cell, colNumber)=>{ | |
| cell.border = { | |
| top:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| bottom:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| left:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| right:{style:"thin",color:{argb:"FFE5E7EB"}}, | |
| }; | |
| if(even) cell.fill = { type:"pattern", pattern:"solid", fgColor:{argb:"FFF5F8FF"} }; | |
| const header = TEMPLATE_HEADERS[colNumber-1]; | |
| if(toTextCols.has(header)) cell.value = String(cell.value ?? ""); | |
| }); | |
| }); | |
| const ts = new Date().toISOString().replace(/\D/g,"").slice(0,14); | |
| const base = (document.getElementById("fname").value || "Ticket").trim() || "Ticket"; | |
| const filename = `${base}_${ts}.xlsx`; | |
| const buffer = await wb.xlsx.writeBuffer(); | |
| const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); | |
| const file = new File([blob], filename, { type: blob.type }); | |
| if (navigator.canShare && navigator.canShare({ files: [file] })) { | |
| try { await navigator.share({ files: [file], title: "ملف التذاكر" }); toast("تمت المشاركة/الحفظ."); return; } | |
| catch(e){} | |
| } | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); a.href = url; a.download = filename; | |
| document.body.appendChild(a); a.click(); a.remove(); | |
| setTimeout(()=>URL.revokeObjectURL(url), 1000); | |
| toast("تم تنزيل الملف بتنسيق القالب."); | |
| } | |
| async function copyToClipboardTSV(){ | |
| const rows = readTable(); | |
| if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; } | |
| const textCols = new Set(["رقم الهوية","رقم الجهاز","رقم الجوال"]); | |
| const header = EXPORT_COLUMNS.join("\t"); | |
| const body = rows.map(r => | |
| EXPORT_COLUMNS.map(c => { | |
| let v = (r[c] ?? "").toString().replace(/\t/g," "); | |
| if(textCols.has(c) && v && /^[0-9]+$/.test(v)) v = "'" + v; | |
| return v; | |
| }).join("\t") | |
| ).join("\r\n"); | |
| const tsv = "\uFEFF" + header + "\r\n" + body; | |
| try{ await navigator.clipboard.writeText(tsv); toast("تم النسخ — الصق/ي مباشرة في Excel."); } | |
| catch(e){ | |
| const ta = document.createElement("textarea"); | |
| ta.value = tsv; document.body.appendChild(ta); | |
| ta.select(); document.execCommand("copy"); document.body.removeChild(ta); | |
| toast("تم النسخ — الصق/ي مباشرة في Excel."); | |
| } | |
| } | |
| const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق | |
| وقت حدوث المشكلة: 21/8/2025 7 صباحا | |
| اسم صاحب المشكلة : نوف الناصر | |
| رقم الهوية: 1234567890 | |
| رقم الجهاز: 01234 | |
| رقم الجوال: 0558174717 | |
| اسم المسح: الخبر 2025 | |
| اسم المنطقة: الشرقية`; | |
| const STATE_KEY = "ticketParserState_v10_3"; | |
| const ALL_STATE_KEYS = [ | |
| "ticketParserState_v8","ticketParserState_v9","ticketParserState_v10", | |
| "ticketParserState_v10_1","ticketParserState_v10_2","ticketParserState_v10_3" | |
| ]; | |
| function ensureColumns(rows, agentName, defaultRegion){ | |
| if(!Array.isArray(rows)) return rows||[]; | |
| return rows.map(r=>{ | |
| const out = {...r}; | |
| if(!("التصنيف" in out) || !out["التصنيف"]){ | |
| const fakeText = Object.values(out).join("\n"); | |
| out["التصنيف"] = classifyTicket(fakeText, out); | |
| } | |
| if(!("اسم الدعم الفني" in out)) out["اسم الدعم الفني"] = agentName || out["اسم الدعم الفني"] || ""; | |
| if(!("الحالة" in out) || !out["الحالة"]) out["الحالة"] = "تم الحل"; | |
| if(defaultRegion) out["المنطقة"] = defaultRegion; | |
| return out; | |
| }); | |
| } | |
| function saveState(){ | |
| try{ | |
| const raw = document.getElementById("raw")?.value || ""; | |
| const fname = document.getElementById("fname")?.value || "Ticket"; | |
| const agent = document.getElementById("agentName")?.value || ""; | |
| const region= document.getElementById("regionDefault")?.value || ""; | |
| const rows = readTable(); | |
| localStorage.setItem(STATE_KEY, JSON.stringify({ raw, fname, agent, region, rows })); | |
| }catch{} | |
| } | |
| function loadState(){ | |
| try{ | |
| const s = localStorage.getItem(STATE_KEY); | |
| if(!s) return false; | |
| let { raw, fname, agent, region, rows } = JSON.parse(s); | |
| if(typeof raw === "string"){ const el=document.getElementById("raw"); if(el) el.value = raw; } | |
| if(typeof fname === "string"){ const el=document.getElementById("fname"); if(el) el.value = fname; } | |
| if(typeof agent === "string"){ const el=document.getElementById("agentName"); if(el) el.value = agent; } | |
| if(typeof region === "string"){ const el=document.getElementById("regionDefault"); if(el) el.value = region; } | |
| rows = ensureColumns(rows, agent, region); | |
| if(Array.isArray(rows) && rows.length){ | |
| buildTable(rows); validateCells(); updateBadge(rows.length); setButtonsEnabled(true); | |
| } | |
| return true; | |
| }catch{ return false; } | |
| } | |
| function clearAll(){ | |
| const rawEl = document.getElementById("raw"); | |
| const tbody = document.getElementById("tbody"); | |
| const fnameEl = document.getElementById("fname"); | |
| const agentEl = document.getElementById("agentName"); | |
| const regionEl= document.getElementById("regionDefault"); | |
| if(rawEl) rawEl.value = ""; | |
| if(tbody) tbody.innerHTML = ""; | |
| if(fnameEl) fnameEl.value = "Ticket"; | |
| if(agentEl) agentEl.value = ""; | |
| if(regionEl) regionEl.value = ""; | |
| updateBadge(0); setButtonsEnabled(false); | |
| try{ ALL_STATE_KEYS.forEach(k=>localStorage.removeItem(k)); }catch{} | |
| toast("تم مسح كل البيانات والتخزين."); | |
| } | |
| function normalizeForPaste(text){ | |
| const norm = normalizeText(text||""); | |
| const parts = splitTickets(norm); | |
| return parts.length ? parts.join("\n\n🔴🔴🔴\n") : norm; | |
| } | |
| async function smartPasteInto(el){ | |
| try{ | |
| const txt = await navigator.clipboard.readText(); | |
| if(txt && txt.trim()){ | |
| const formatted = normalizeForPaste(txt); | |
| if(el.value && el.value.trim()){ | |
| el.value = el.value.trimEnd() + "\n\n🔴🔴🔴\n" + formatted; | |
| }else{ | |
| el.value = formatted; | |
| } | |
| saveState(); | |
| toast("تم اللصق والتنظيم."); | |
| return; | |
| } | |
| openPasteModal(el); | |
| }catch{ | |
| openPasteModal(el); | |
| } | |
| } | |
| function openPasteModal(targetEl){ | |
| const modal = document.getElementById("pasteModal"); | |
| const input = document.getElementById("pasteInput"); | |
| const add = document.getElementById("pasteAdd"); | |
| const cancel= document.getElementById("pasteCancel"); | |
| input.value = ""; | |
| modal.hidden = false; | |
| input.focus(); | |
| function close(){ modal.hidden = true; add.removeEventListener("click", onAdd); cancel.removeEventListener("click", onCancel); document.removeEventListener("keydown", onEsc); } | |
| function onAdd(){ | |
| const txt = input.value || ""; | |
| const formatted = normalizeForPaste(txt); | |
| if(formatted.trim()){ | |
| if(targetEl.value && targetEl.value.trim()){ | |
| targetEl.value = targetEl.value.trimEnd() + "\n\n🔴🔴🔴\n" + formatted; | |
| }else{ | |
| targetEl.value = formatted; | |
| } | |
| saveState(); | |
| toast("تمت الإضافة."); | |
| } | |
| close(); | |
| } | |
| function onCancel(){ close(); } | |
| function onEsc(e){ if(e.key === "Escape"){ e.preventDefault(); close(); } } | |
| add.addEventListener("click", onAdd); | |
| cancel.addEventListener("click", onCancel); | |
| document.addEventListener("keydown", onEsc); | |
| } | |
| function init(){ | |
| const parseBtn = document.getElementById("btn-parse"); | |
| const exportBtn = document.getElementById("btn-export"); | |
| const copyBtn = document.getElementById("btn-copy"); | |
| const clearBtn = document.getElementById("btn-clear"); | |
| const sampleBtn = document.getElementById("btn-sample"); | |
| const smartPaste = document.getElementById("btn-smartpaste"); | |
| const rawEl = document.getElementById("raw"); | |
| const fnameEl = document.getElementById("fname"); | |
| const agentEl = document.getElementById("agentName"); | |
| const regionEl = document.getElementById("regionDefault"); | |
| loadState(); | |
| smartPaste.addEventListener("click", ()=> smartPasteInto(rawEl)); | |
| parseBtn.addEventListener("click", ()=>{ | |
| const raw = (rawEl.value || "").trim(); | |
| if(!raw){ toast("فضلاً الصق/ي تذاكر أولاً."); return; } | |
| const agent = agentEl.value || ""; | |
| const defRegion = regionEl.value || ""; | |
| const rows = parseTicketsWithExtras(raw, agent, defRegion); | |
| buildTable(rows); validateCells(); | |
| updateBadge(rows.length); setButtonsEnabled(rows.length>0); | |
| saveState(); | |
| toast(`تم استخراج ${rows.length} تذكرة.`); | |
| }); | |
| exportBtn.addEventListener("click", exportExcel); | |
| copyBtn.addEventListener("click", copyToClipboardTSV); | |
| clearBtn.addEventListener("click", clearAll); | |
| sampleBtn.addEventListener("click", ()=>{ rawEl.value = SAMPLE; saveState(); }); | |
| rawEl.addEventListener("input", saveState); | |
| fnameEl.addEventListener("input", saveState); | |
| agentEl.addEventListener("input", saveState); | |
| regionEl.addEventListener("change", saveState); | |
| document.addEventListener("keydown", (e)=>{ | |
| const ctrl = e.ctrlKey || e.metaKey; | |
| if(ctrl && e.key === "Enter"){ e.preventDefault(); parseBtn.click(); } | |
| else if(ctrl && e.key.toLowerCase() === "e"){ e.preventDefault(); exportBtn.click(); } | |
| else if(ctrl && e.shiftKey && e.key.toLowerCase() === "c"){ e.preventDefault(); copyBtn.click(); } | |
| else if(e.key === "Escape"){ e.preventDefault(); clearAll(); } | |
| }); | |
| setButtonsEnabled(!!document.getElementById("tbody")?.children.length); | |
| } | |
| init(); | |