EQ / index.html
stat2025's picture
Update index.html
04f004a verified
<!doctype html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>الإقرارات</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Excel Export -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
:root{
--gastat-green:#53CD3F; /* R83 G205 B63 */
--gastat-blue:#00B2DF; /* R0 G178 B223 */
--gastat-purple:#4137A8;/* R65 G55 B168 */
--gastat-gray:#8492A2; /* R132 G146 B162 */
--bg:#071321;
--card:#0b1d34;
--card2:#081a2f;
--text:#eef6ff;
--muted:#c3d1ea;
--line:rgba(255,255,255,.12);
--shadow: 0 18px 60px rgba(0,0,0,.35);
--radius:18px;
--warn:#f59e0b;
--bad:#ef4444;
}
*{ box-sizing:border-box; }
body{
margin:0;
font-family:"Cairo", system-ui, -apple-system, Segoe UI, Arial, sans-serif;
background:
radial-gradient(900px 520px at 15% 15%, rgba(0,178,223,.22), transparent 60%),
radial-gradient(900px 520px at 85% 10%, rgba(83,205,63,.18), transparent 60%),
radial-gradient(1000px 650px at 60% 70%, rgba(65,55,168,.18), transparent 60%),
var(--bg);
color:var(--text);
}
.wrap{ max-width:1200px; margin:18px auto 60px; padding:0 14px; }
.hero{
border:1px solid rgba(255,255,255,.12);
box-shadow: var(--shadow);
border-radius: var(--radius);
padding:18px 18px 14px;
background: linear-gradient(135deg, rgba(0,178,223,.20), rgba(65,55,168,.18));
position:relative; overflow:hidden;
}
.hero:before{
content:"";
position:absolute; inset:-60px -80px auto auto;
width:260px; height:260px;
background: radial-gradient(circle, rgba(255,255,255,.18), transparent 60%);
transform: rotate(12deg);
pointer-events:none;
}
.creditLine{
margin:0 0 6px;
text-align:center;
font-size:12px;
color: rgba(238,246,255,.9);
letter-spacing:.2px;
opacity:.92;
}
.titleTop{ text-align:center; margin:0 0 8px; color:rgba(255,255,255,.9); font-size:16px; }
.titleBar{
margin:0; text-align:center; font-size:18px; font-weight:800;
background: rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.12);
padding:12px 10px; border-radius:14px;
}
.subNote{ margin:10px 0 0; text-align:center; color:rgba(195,209,234,.92); font-size:13px; line-height:1.7; }
.grid{
display:grid; grid-template-columns: 1.2fr 1fr; gap:14px; margin-top:14px;
}
@media (max-width: 980px){ .grid{ grid-template-columns:1fr; } }
.card{
background: linear-gradient(180deg, rgba(11,29,52,.92), rgba(8,26,47,.92));
border:1px solid rgba(255,255,255,.12);
box-shadow: var(--shadow);
border-radius: var(--radius);
padding:14px;
}
.card h3{ margin:0 0 10px; font-size:15px; display:flex; gap:10px; align-items:center; }
.badge{
font-size:12px; padding:4px 10px; border-radius:999px;
border:1px solid rgba(255,255,255,.16);
background: rgba(255,255,255,.06);
color: var(--muted);
}
.formGrid{ display:grid; grid-template-columns: 1fr 1fr; gap:10px; }
@media (max-width: 680px){ .formGrid{ grid-template-columns:1fr; } }
label{ display:block; font-size:12px; color: var(--muted); margin:0 0 6px; }
input, select{
width:100%;
padding:12px 12px;
border-radius:14px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: var(--text);
outline:none;
transition:.15s;
font-size:14px;
}
input:focus, select:focus{
border-color: rgba(0,178,223,.55);
box-shadow: 0 0 0 4px rgba(0,178,223,.16);
}
input::placeholder{ color: rgba(195,209,234,.7); }
.actions{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.btn{
border:0; border-radius:14px; padding:11px 14px;
font-family:inherit; font-weight:800; cursor:pointer;
display:inline-flex; align-items:center; gap:8px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.08);
color: var(--text);
transition:.15s;
}
.btn:hover{ transform: translateY(-1px); }
.btn:active{ transform: translateY(0px); }
.btn.primary{
background: linear-gradient(135deg, rgba(83,205,63,.92), rgba(34,197,94,.78));
border-color: rgba(83,205,63,.35);
}
.btn.export{
background: linear-gradient(135deg, rgba(0,178,223,.92), rgba(65,55,168,.72));
border-color: rgba(0,178,223,.35);
}
.btn.warn{
background: linear-gradient(135deg, rgba(245,158,11,.95), rgba(217,119,6,.82));
border-color: rgba(245,158,11,.35);
}
.btn.danger{
background: linear-gradient(135deg, rgba(239,68,68,.95), rgba(220,38,38,.82));
border-color: rgba(239,68,68,.35);
}
.noteBox{
margin-top:10px;
border-radius:14px;
border:1px solid rgba(255,255,255,.14);
background: rgba(0,178,223,.10);
padding:10px 12px;
color: rgba(238,246,255,.95);
font-size:13px;
line-height:1.7;
}
.noteBox.warn{
background: rgba(245,158,11,.12);
border-color: rgba(245,158,11,.28);
}
.signatureBox{
width:100%;
height:180px;
background:#ffffff;
border-radius:14px;
border:2px dashed rgba(0,178,223,.6);
overflow:hidden;
position:relative;
}
.signatureBox canvas{
width:100%;
height:100%;
display:block;
touch-action:none;
}
.sigActions{
margin-top:8px;
text-align:left;
}
.tableWrap{
overflow:auto;
border-radius: var(--radius);
border:1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.04);
}
table{ width:100%; border-collapse:collapse; min-width:1100px; }
thead th{
position:sticky; top:0;
background: rgba(255,255,255,.08);
backdrop-filter: blur(8px);
font-size:12px; color: rgba(238,246,255,.95);
text-align:center;
padding:10px 8px;
border-bottom:1px solid rgba(255,255,255,.12);
white-space:nowrap;
}
tbody td{
font-size:13px; color: rgba(238,246,255,.92);
padding:10px 8px;
border-bottom:1px solid rgba(255,255,255,.08);
text-align:center;
vertical-align:middle;
white-space:nowrap;
}
tbody tr:hover{ background: rgba(255,255,255,.05); }
.tinyBtn{
padding:7px 10px; border-radius:12px; font-weight:800; font-size:12px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.07);
color: var(--text);
cursor:pointer;
}
.tinyBtn.danger{ background: rgba(239,68,68,.18); border-color: rgba(239,68,68,.35); }
.tinyBtn.edit{ background: rgba(245,158,11,.16); border-color: rgba(245,158,11,.35); }
.statusRow{
display:flex; align-items:center; justify-content:space-between;
gap:10px; flex-wrap:wrap; margin-bottom:10px;
}
.pill{
font-size:12px; padding:6px 10px; border-radius:999px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: rgba(195,209,234,.95);
}
.searchBox input{
width:320px; max-width:70vw;
padding:10px 12px; border-radius:999px;
}
@media (max-width: 480px){
.wrap{ padding:0 10px; }
.titleBar{ font-size:16px; }
.btn{ width:100%; justify-content:center; }
.searchBox input{ width:100%; }
}
</style>
</head>
<body>
<div class="wrap">
<div class="hero">
<p class="titleTop">"بيان استلام"</p>
<h1 class="titleBar">إدارة حالات الإقرارات وتوثيق الاستلام</h1>
<p class="creditLine">تصميم وإعداد: نوف الناصر</p>
<p class="subNote">
أدخل البيانات ثم اختر الحالة. يتم الحفظ تلقائيًا ولا تختفي السجلات عند إغلاق الصفحة إلا بالحذف.
</p>
</div>
<div class="grid">
<!-- نموذج الإدخال -->
<div class="card">
<h3>إدخال بيانات <span class="badge">يدوي + حفظ</span></h3>
<div class="formGrid">
<div>
<label>اسم الشركة (مطلوب)</label>
<input id="company" type="text" placeholder="مثال: شركة ...." />
</div>
<div>
<label>رقم السجل التجاري</label>
<input id="cr" type="text" inputmode="numeric" placeholder="مثال: 1010xxxxxx" />
</div>
<div style="grid-column:1/-1">
<label>الحالة</label>
<select id="status">
<option value="RESPONDED">استجاب</option>
<option value="BRANCH_NO_SEPARATE">فرع ليس له حسابات مستقلة</option>
<option value="TEMP_CLOSED">مغلقة مؤقتًا</option>
<option value="PERM_CLOSED">مغلقة نهائيًا</option>
</select>
</div>
<div id="respondedBlock" style="grid-column:1/-1; display:none;">
<div class="formGrid">
<div style="grid-column:1/-1">
<label>نوع الكيان عند الاستجابة</label>
<select id="respondedType">
<option value="">— اختر —</option>
<option value="MAIN_CENTER">مركز رئيسي</option>
<option value="BRANCH_SEPARATE">فرع له حسابات مستقلة</option>
<option value="SINGLE">مفردة</option>
</select>
</div>
<div>
<label>اسم المستلم</label>
<input id="receiver" type="text" placeholder="الاسم الثلاثي" />
</div>
<div>
<label>رقم الجوال</label>
<input id="mobile" type="tel" inputmode="numeric" placeholder="05xxxxxxxx أو 9665xxxxxxxx" />
</div>
<div>
<label>البريد الإلكتروني</label>
<input id="email" type="email" placeholder="name@example.com" />
</div>
<div>
<label>التاريخ</label>
<input id="date" type="date" />
</div>
<div style="grid-column:1/-1">
<label>التوقيع اليدوي</label>
<div class="signatureBox">
<canvas id="signaturePad"></canvas>
</div>
<div class="sigActions">
<button type="button" class="tinyBtn" id="clearSignature">مسح التوقيع</button>
</div>
</div>
</div>
<div class="noteBox" id="respondedHelp">
عند اختيار <b>استجاب</b> يُفضّل تحديد (مركز/فرع مستقل/مفردة)، ثم تُكمل بيانات الاستلام إن توفرت.
</div>
</div>
<div id="photoNote" style="grid-column:1/-1; display:none;">
<div class="noteBox warn">
📸 <b>ملاحظة:</b> التقط صورة وسترفق لاحقًا بالنظام من قبلك.
</div>
</div>
<div style="grid-column:1/-1">
<label>ملاحظة إضافية (اختياري)</label>
<input id="extraNote" type="text" placeholder="أي تعليق أو توضيح..." />
</div>
</div>
<div class="actions">
<button class="btn primary" id="saveBtn">💾 حفظ</button>
<button class="btn warn" id="clearFormBtn">🧹 تفريغ الحقول</button>
<button class="btn export" id="exportBtn">📤 تصدير Excel</button>
</div>
</div>
<div class="card">
<h3>إدارة السجلات <span class="badge">بحث + حذف</span></h3>
<div class="statusRow">
<div class="pill" id="countPill">عدد السجلات: 0</div>
<div class="searchBox">
<input id="search" type="text" placeholder="🔎 بحث: شركة / سجل / حالة / جوال / بريد" />
</div>
</div>
<div class="actions">
<button class="btn danger" id="deleteSelectedBtn">🗑️ حذف المحدد</button>
<button class="btn danger" id="deleteAllBtn">⚠️ حذف الكل</button>
</div>
</div>
</div>
<div class="card" style="margin-top:14px;">
<h3>الجدول</h3>
<div class="tableWrap">
<table>
<thead>
<tr>
<th>تحديد</th>
<th>م</th>
<th>اسم الشركة</th>
<th>رقم السجل التجاري</th>
<th>الحالة</th>
<th>تفصيل الاستجابة</th>
<th>اسم المستلم</th>
<th>رقم الجوال</th>
<th>البريد الإلكتروني</th>
<th>التاريخ</th>
<th>التوقيع</th>
<th>ملاحظة</th>
<th>إجراءات</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
</div>
</div>
<script>
const STORAGE_KEY = "eq_records_v3";
const el = {
company: document.getElementById("company"),
cr: document.getElementById("cr"),
status: document.getElementById("status"),
respondedBlock: document.getElementById("respondedBlock"),
respondedType: document.getElementById("respondedType"),
receiver: document.getElementById("receiver"),
mobile: document.getElementById("mobile"),
email: document.getElementById("email"),
date: document.getElementById("date"),
extraNote: document.getElementById("extraNote"),
photoNote: document.getElementById("photoNote"),
saveBtn: document.getElementById("saveBtn"),
clearFormBtn: document.getElementById("clearFormBtn"),
exportBtn: document.getElementById("exportBtn"),
tbody: document.getElementById("tbody"),
countPill: document.getElementById("countPill"),
search: document.getElementById("search"),
deleteSelectedBtn: document.getElementById("deleteSelectedBtn"),
deleteAllBtn: document.getElementById("deleteAllBtn"),
};
const canvas = document.getElementById("signaturePad");
const ctx = canvas.getContext("2d");
let drawing = false;
let signatureData = null;
let scaledOnce = false;
let records = [];
let editId = null;
function todayISO(){
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth()+1).padStart(2,"0");
const day = String(d.getDate()).padStart(2,"0");
return `${y}-${m}-${day}`;
}
function escapeHtml(str){
return String(str ?? "")
.replaceAll("&","&amp;")
.replaceAll("<","&lt;")
.replaceAll(">","&gt;")
.replaceAll('"',"&quot;")
.replaceAll("'","&#039;");
}
function sanitize(s){ return String(s ?? "").trim(); }
function load(){
try{
const raw = localStorage.getItem(STORAGE_KEY);
records = raw ? JSON.parse(raw) : [];
}catch(e){
records = [];
}
}
function persist(){
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
}
function isValidEmail(v){
if(!v) return true;
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
}
function normalizeMobile(v){
return sanitize(v).replace(/\s+/g,"").replace(/[^\d+]/g,"");
}
function isValidMobile(v){
if(!v) return true;
const x = normalizeMobile(v);
return /^(05\d{8}|9665\d{8}|\+9665\d{8})$/.test(x);
}
function statusLabel(code){
switch(code){
case "RESPONDED": return "استجاب";
case "BRANCH_NO_SEPARATE": return "فرع ليس له حسابات مستقلة";
case "TEMP_CLOSED": return "مغلقة مؤقتًا";
case "PERM_CLOSED": return "مغلقة نهائيًا";
default: return code || "";
}
}
function respondedTypeLabel(code){
switch(code){
case "MAIN_CENTER": return "مركز رئيسي";
case "BRANCH_SEPARATE": return "فرع له حسابات مستقلة";
case "SINGLE": return "مفردة";
default: return "";
}
}
function updateConditionalUI(){
const s = el.status.value;
const isResponded = (s === "RESPONDED");
const needsPhoto = (s === "TEMP_CLOSED" || s === "PERM_CLOSED");
el.respondedBlock.style.display = isResponded ? "block" : "none";
el.photoNote.style.display = needsPhoto ? "block" : "none";
if(!isResponded){
el.respondedType.value = "";
if(el.receiver) el.receiver.value = "";
if(el.mobile) el.mobile.value = "";
if(el.email) el.email.value = "";
clearSignaturePad();
}
}
function resetForm(){
el.company.value = "";
el.cr.value = "";
el.status.value = "RESPONDED";
el.respondedType.value = "";
if(el.receiver) el.receiver.value = "";
if(el.mobile) el.mobile.value = "";
if(el.email) el.email.value = "";
el.date.value = todayISO();
el.extraNote.value = "";
editId = null;
el.saveBtn.textContent = "💾 حفظ";
clearSignaturePad();
updateConditionalUI();
}
function fillForm(rec){
el.company.value = rec.company || "";
el.cr.value = rec.cr || "";
el.status.value = rec.status || "RESPONDED";
el.respondedType.value = rec.respondedType || "";
if(el.receiver) el.receiver.value = rec.receiver || "";
if(el.mobile) el.mobile.value = rec.mobile || "";
if(el.email) el.email.value = rec.email || "";
el.date.value = rec.date || todayISO();
el.extraNote.value = rec.extraNote || "";
editId = rec.id;
el.saveBtn.textContent = "✅ حفظ التعديل";
updateConditionalUI();
clearSignaturePad();
if(rec.signature){
const img = new Image();
img.onload = () => {
const rect = canvas.getBoundingClientRect();
ctx.drawImage(img, 0, 0, rect.width, rect.height);
signatureData = rec.signature;
};
img.src = rec.signature;
}
window.scrollTo({ top: 0, behavior: "smooth" });
}
function filteredRecords(){
const q = sanitize(el.search.value).toLowerCase();
if(!q) return records;
return records.filter(r => {
const hay = [
r.company, r.cr, statusLabel(r.status), respondedTypeLabel(r.respondedType),
r.receiver, r.mobile, r.email, r.date, r.note, r.extraNote
].join(" ").toLowerCase();
return hay.includes(q);
});
}
function render(){
const list = filteredRecords();
el.tbody.innerHTML = "";
list.forEach((r, idx) => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td><input type="checkbox" data-id="${r.id}" class="rowCheck"></td>
<td>${idx + 1}</td>
<td>${escapeHtml(r.company)}</td>
<td>${escapeHtml(r.cr)}</td>
<td>${escapeHtml(statusLabel(r.status))}</td>
<td>${escapeHtml(respondedTypeLabel(r.respondedType))}</td>
<td>${escapeHtml(r.receiver)}</td>
<td>${escapeHtml(r.mobile)}</td>
<td>${escapeHtml(r.email)}</td>
<td>${escapeHtml(r.date)}</td>
<td>${r.signature ? `<img src="${r.signature}" style="height:50px; background:#fff; border-radius:8px; padding:2px;">` : ""}</td>
<td>${escapeHtml(r.note || "")}${r.extraNote ? " | " + escapeHtml(r.extraNote) : ""}</td>
<td>
<button class="tinyBtn edit" data-action="edit" data-id="${r.id}">تعديل</button>
<button class="tinyBtn danger" data-action="delete" data-id="${r.id}">حذف</button>
</td>
`;
el.tbody.appendChild(tr);
});
el.countPill.textContent = `عدد السجلات: ${records.length}`;
}
function buildAutoNote(status){
if(status === "BRANCH_NO_SEPARATE"){
return "فرع ليس له حسابات مستقلة";
}
if(status === "TEMP_CLOSED"){
return "مغلقة مؤقتًا - التقط صورة وسترفق لاحقًا بالنظام من قبلك";
}
if(status === "PERM_CLOSED"){
return "مغلقة نهائيًا - التقط صورة وسترفق لاحقًا بالنظام من قبلك";
}
return "";
}
function resizeCanvas(){
const ratio = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
const oldData = signatureData;
canvas.width = Math.max(1, Math.floor(rect.width * ratio));
canvas.height = Math.max(1, Math.floor(rect.height * ratio));
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.lineWidth = 2;
ctx.lineCap = "round";
ctx.strokeStyle = "#000";
if(oldData){
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, rect.width, rect.height);
signatureData = oldData;
};
img.src = oldData;
}
}
function clearSignaturePad(){
const rect = canvas.getBoundingClientRect();
ctx.clearRect(0, 0, rect.width, rect.height);
signatureData = null;
}
function getPos(e){
const rect = canvas.getBoundingClientRect();
if(e.touches && e.touches[0]){
return { x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top };
}
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
}
function startDraw(e){
drawing = true;
const p = getPos(e);
ctx.beginPath();
ctx.moveTo(p.x, p.y);
}
function draw(e){
if(!drawing) return;
e.preventDefault();
const p = getPos(e);
ctx.lineTo(p.x, p.y);
ctx.stroke();
}
function endDraw(){
if(!drawing) return;
drawing = false;
signatureData = canvas.toDataURL("image/png");
}
canvas.addEventListener("mousedown", startDraw);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", endDraw);
canvas.addEventListener("mouseleave", endDraw);
canvas.addEventListener("touchstart", startDraw, { passive:false });
canvas.addEventListener("touchmove", draw, { passive:false });
canvas.addEventListener("touchend", endDraw);
document.getElementById("clearSignature").addEventListener("click", clearSignaturePad);
function save(){
const company = sanitize(el.company.value);
if(!company){
alert("فضلاً أدخل اسم الشركة (مطلوب).");
return;
}
const status = el.status.value;
const cr = sanitize(el.cr.value);
const respondedType = sanitize(el.respondedType.value);
const receiver = el.receiver ? sanitize(el.receiver.value) : "";
const mobileRaw = el.mobile ? sanitize(el.mobile.value) : "";
const mobile = normalizeMobile(mobileRaw);
const email = el.email ? sanitize(el.email.value) : "";
const date = el.date.value || todayISO();
const extraNote = sanitize(el.extraNote.value);
if(email && !isValidEmail(email)){
alert("البريد الإلكتروني غير صحيح.");
return;
}
if(mobileRaw && !isValidMobile(mobileRaw)){
alert("رقم الجوال غير صحيح. مثال: 05xxxxxxxx أو 9665xxxxxxxx أو +9665xxxxxxxx");
return;
}
let note = buildAutoNote(status);
const rec = {
id: editId ?? crypto.randomUUID(),
company,
cr,
status,
respondedType: status === "RESPONDED" ? respondedType : "",
receiver: status === "RESPONDED" ? receiver : "",
mobile: status === "RESPONDED" ? mobile : "",
email: status === "RESPONDED" ? email : "",
date: status === "RESPONDED" ? date : (date || todayISO()),
signature: (status === "RESPONDED") ? (signatureData || "") : "",
note,
extraNote,
updatedAt: new Date().toISOString()
};
if(editId){
const i = records.findIndex(x => x.id === editId);
if(i !== -1) records[i] = { ...records[i], ...rec };
}else{
records.push(rec);
}
persist();
render();
resetForm();
}
function deleteOne(id){
const r = records.find(x => x.id === id);
if(!r) return;
if(!confirm(`تأكيد حذف سجل الشركة: ${r.company} ؟`)) return;
records = records.filter(x => x.id !== id);
persist();
render();
if(editId === id) resetForm();
}
function deleteSelected(){
const checks = [...document.querySelectorAll(".rowCheck:checked")];
if(checks.length === 0){ alert("لم يتم تحديد أي سجلات."); return; }
if(!confirm(`تأكيد حذف (${checks.length}) سجل/سجلات؟`)) return;
const ids = new Set(checks.map(c => c.getAttribute("data-id")));
records = records.filter(r => !ids.has(r.id));
persist();
render();
if(editId && ids.has(editId)) resetForm();
}
function deleteAll(){
if(records.length === 0){ alert("لا توجد سجلات للحذف."); return; }
if(!confirm("تحذير: سيتم حذف جميع السجلات نهائيًا من هذه الصفحة. هل أنت متأكد؟")) return;
records = [];
persist();
render();
resetForm();
}
function exportExcel(){
const list = filteredRecords();
if(list.length === 0){ alert("لا توجد بيانات للتصدير."); return; }
const rows = list.map((r, idx) => ({
"م": idx + 1,
"اسم الشركة": r.company,
"رقم السجل التجاري": r.cr,
"الحالة": statusLabel(r.status),
"تفصيل الاستجابة": respondedTypeLabel(r.respondedType),
"اسم المستلم": r.receiver,
"رقم الجوال": r.mobile,
"البريد الإلكتروني": r.email,
"التاريخ": r.date,
"التوقيع": r.signature ? "مرفق (صورة)" : "",
"ملاحظة": (r.note || "") + (r.extraNote ? " | " + r.extraNote : "")
}));
const ws = XLSX.utils.json_to_sheet(rows, { cellDates: true });
ws["!cols"] = [
{ wch: 5 },
{ wch: 32 },
{ wch: 18 },
{ wch: 18 },
{ wch: 20 },
{ wch: 22 },
{ wch: 18 },
{ wch: 28 },
{ wch: 14 },
{ wch: 14 },
{ wch: 42 },
];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "الإقرارات");
XLSX.writeFile(wb, `EQ_${todayISO()}.xlsx`);
}
// Events
el.status.addEventListener("change", updateConditionalUI);
el.saveBtn.addEventListener("click", save);
el.clearFormBtn.addEventListener("click", resetForm);
el.exportBtn.addEventListener("click", exportExcel);
el.search.addEventListener("input", render);
el.deleteSelectedBtn.addEventListener("click", deleteSelected);
el.deleteAllBtn.addEventListener("click", deleteAll);
el.tbody.addEventListener("click", (e) => {
const btn = e.target.closest("button");
if(!btn) return;
const action = btn.getAttribute("data-action");
const id = btn.getAttribute("data-id");
if(action === "delete") deleteOne(id);
if(action === "edit"){
const rec = records.find(x => x.id === id);
if(rec) fillForm(rec);
}
});
// Init
el.date.value = todayISO();
load();
updateConditionalUI();
render();
setTimeout(() => { resizeCanvas(); }, 0);
window.addEventListener("resize", () => { resizeCanvas(); });
</script>
</body>
</html>